From 9ae88d87e375222798260157e447a748aac854e7 Mon Sep 17 00:00:00 2001 From: SpyCheese Date: Tue, 3 Dec 2024 17:19:12 +0300 Subject: [PATCH] Export all keys command in validator-engine-console (#1412) * Export all keys command in validator-engine-console * Use OPENSSL_cleanse in Bits256::fill_zero_s --- crypto/common/bitstring.h | 6 +- keyring/keyring.cpp | 12 +++- keyring/keyring.h | 2 + keyring/keyring.hpp | 3 + tl/generate/scheme/ton_api.tl | 3 + tl/generate/scheme/ton_api.tlo | Bin 101456 -> 101764 bytes .../validator-engine-console-query.cpp | 62 +++++++++++++++++ .../validator-engine-console-query.h | 24 +++++++ .../validator-engine-console.cpp | 1 + validator-engine/validator-engine.cpp | 64 ++++++++++++++++++ validator-engine/validator-engine.hpp | 2 + 11 files changed, 173 insertions(+), 6 deletions(-) diff --git a/crypto/common/bitstring.h b/crypto/common/bitstring.h index 25776478..12333522 100644 --- a/crypto/common/bitstring.h +++ b/crypto/common/bitstring.h @@ -554,11 +554,7 @@ class BitArray { set_same(0); } void set_zero_s() { - volatile uint8* p = data(); - auto x = m; - while (x--) { - *p++ = 0; - } + as_slice().fill_zero_secure(); } void set_ones() { set_same(1); diff --git a/keyring/keyring.cpp b/keyring/keyring.cpp index 0f45879d..a31d173e 100644 --- a/keyring/keyring.cpp +++ b/keyring/keyring.cpp @@ -28,7 +28,7 @@ namespace ton { namespace keyring { KeyringImpl::PrivateKeyDescr::PrivateKeyDescr(PrivateKey private_key, bool is_temp) - : public_key(private_key.compute_public_key()), is_temp(is_temp) { + : public_key(private_key.compute_public_key()), private_key(private_key), is_temp(is_temp) { auto D = private_key.create_decryptor_async(); D.ensure(); decryptor_sign = D.move_as_ok(); @@ -190,6 +190,16 @@ void KeyringImpl::decrypt_message(PublicKeyHash key_hash, td::BufferSlice data, } } +void KeyringImpl::export_all_private_keys(td::Promise> promise) { + std::vector keys; + for (auto& [_, descr] : map_) { + if (!descr->is_temp && descr->private_key.exportable()) { + keys.push_back(descr->private_key); + } + } + promise.set_value(std::move(keys)); +} + td::actor::ActorOwn Keyring::create(std::string db_root) { return td::actor::create_actor("keyring", db_root); } diff --git a/keyring/keyring.h b/keyring/keyring.h index 044d8d29..3b9064a7 100644 --- a/keyring/keyring.h +++ b/keyring/keyring.h @@ -44,6 +44,8 @@ class Keyring : public td::actor::Actor { virtual void decrypt_message(PublicKeyHash key_hash, td::BufferSlice data, td::Promise promise) = 0; + virtual void export_all_private_keys(td::Promise> promise) = 0; + static td::actor::ActorOwn create(std::string db_root); }; diff --git a/keyring/keyring.hpp b/keyring/keyring.hpp index ec658305..eca9073a 100644 --- a/keyring/keyring.hpp +++ b/keyring/keyring.hpp @@ -33,6 +33,7 @@ class KeyringImpl : public Keyring { td::actor::ActorOwn decryptor_sign; td::actor::ActorOwn decryptor_decrypt; PublicKey public_key; + PrivateKey private_key; bool is_temp; PrivateKeyDescr(PrivateKey private_key, bool is_temp); }; @@ -56,6 +57,8 @@ class KeyringImpl : public Keyring { void decrypt_message(PublicKeyHash key_hash, td::BufferSlice data, td::Promise promise) override; + void export_all_private_keys(td::Promise> promise) override; + KeyringImpl(std::string db_root) : db_root_(db_root) { } diff --git a/tl/generate/scheme/ton_api.tl b/tl/generate/scheme/ton_api.tl index 16f48345..940dbe0a 100644 --- a/tl/generate/scheme/ton_api.tl +++ b/tl/generate/scheme/ton_api.tl @@ -725,6 +725,8 @@ engine.validator.perfTimerStats stats:(vector engine.validator.PerfTimerStatsByN engine.validator.shardOutQueueSize size:long = engine.validator.ShardOutQueueSize; +engine.validator.exportedPrivateKeys encrypted_data:bytes = engine.validator.ExportedPrivateKeys; + ---functions--- @@ -755,6 +757,7 @@ engine.validator.delListeningPort ip:int port:int categories:(vector int) priori engine.validator.delProxy out_ip:int out_port:int categories:(vector int) priority_categories:(vector int) = engine.validator.Success; engine.validator.sign key_hash:int256 data:bytes = engine.validator.Signature; +engine.validator.exportAllPrivateKeys encryption_key:PublicKey = engine.validator.ExportedPrivateKeys; engine.validator.getStats = engine.validator.Stats; engine.validator.getConfig = engine.validator.JsonConfig; diff --git a/tl/generate/scheme/ton_api.tlo b/tl/generate/scheme/ton_api.tlo index 1689260bc0822fd856cc35a6f40df07a26aba11b..f150221b4de8e0ff867bec49634aeb50fa65a2d4 100644 GIT binary patch delta 219 zcmcaGgRNyY8}Fmp`c@23pt6y-UYvDerNT~?$rHte1qWn-iZaU*OH#d4D~mT5 ziqBSJT)&yu>UbeU-E`k*Msa-$(Nd!TvS;AG%P+Pu_STw zdUshCkebaWR?Ey{?AZR)l`&bA^;2-zSyhOEjyX9<*5WoWGe0jrJGFB1`Lz%Ox7$QB HiWmX_eYZ|v delta 55 zcmZpf&30i18}Fmp`c@23puCZ{UVQTf@dhQvb(;gNju&p8uts1OWBc}nZj8yI+iPMN GISc{u`4V6N diff --git a/validator-engine-console/validator-engine-console-query.cpp b/validator-engine-console/validator-engine-console-query.cpp index 926b7988..bfcd50da 100644 --- a/validator-engine-console/validator-engine-console-query.cpp +++ b/validator-engine-console/validator-engine-console-query.cpp @@ -35,6 +35,8 @@ #include "ton/ton-tl.hpp" #include "td/utils/JsonBuilder.h" #include "auto/tl/ton_api_json.h" +#include "keys/encryptor.h" +#include "td/utils/port/path.h" #include "tl/tl_json.h" #include @@ -283,6 +285,66 @@ td::Status SignFileQuery::receive(td::BufferSlice data) { return td::Status::OK(); } +td::Status ExportAllPrivateKeysQuery::run() { + TRY_RESULT_ASSIGN(directory_, tokenizer_.get_token()); + TRY_STATUS(tokenizer_.check_endl()); + client_pk_ = ton::privkeys::Ed25519::random(); + return td::Status::OK(); +} + +td::Status ExportAllPrivateKeysQuery::send() { + auto b = ton::create_serialize_tl_object( + client_pk_.compute_public_key().tl()); + td::actor::send_closure(console_, &ValidatorEngineConsole::envelope_send_query, std::move(b), create_promise()); + return td::Status::OK(); +} + +td::Status ExportAllPrivateKeysQuery::receive(td::BufferSlice data) { + TRY_RESULT_PREFIX(f, ton::fetch_tl_object(data.as_slice(), true), + "received incorrect answer: "); + // Private keys are encrypted using client-provided public key to avoid storing them in + // non-secure buffers (not td::SecureString) + TRY_RESULT_PREFIX(decryptor, client_pk_.create_decryptor(), "cannot create decryptor: "); + TRY_RESULT_PREFIX(keys_data, decryptor->decrypt(f->encrypted_data_.as_slice()), "cannot decrypt data: "); + SCOPE_EXIT { + keys_data.as_slice().fill_zero_secure(); + }; + td::Slice slice = keys_data.as_slice(); + if (slice.size() < 32) { + return td::Status::Error("data is too small"); + } + slice.remove_suffix(32); + std::vector private_keys; + while (!slice.empty()) { + if (slice.size() < 4) { + return td::Status::Error("unexpected end of data"); + } + td::uint32 size; + td::MutableSlice{reinterpret_cast(&size), 4}.copy_from(slice.substr(0, 4)); + if (size > slice.size()) { + return td::Status::Error("unexpected end of data"); + } + slice.remove_prefix(4); + TRY_RESULT_PREFIX(private_key, ton::PrivateKey::import(slice.substr(0, size)), "cannot parse private key: "); + if (!private_key.exportable()) { + return td::Status::Error("private key is not exportable"); + } + private_keys.push_back(std::move(private_key)); + slice.remove_prefix(size); + } + + TRY_STATUS_PREFIX(td::mkpath(directory_ + "/"), "cannot create directory " + directory_ + ": "); + td::TerminalIO::out() << "exported " << private_keys.size() << " private keys" << "\n"; + for (const ton::PrivateKey &private_key : private_keys) { + std::string hash_hex = private_key.compute_short_id().bits256_value().to_hex(); + TRY_STATUS_PREFIX(td::write_file(directory_ + "/" + hash_hex, private_key.export_as_slice()), + "failed to write file: "); + td::TerminalIO::out() << "pubkey_hash " << hash_hex << "\n"; + } + td::TerminalIO::out() << "written all files to " << directory_ << "\n"; + return td::Status::OK(); +} + td::Status AddAdnlAddrQuery::run() { TRY_RESULT_ASSIGN(key_hash_, tokenizer_.get_token()); TRY_RESULT_ASSIGN(category_, tokenizer_.get_token()); diff --git a/validator-engine-console/validator-engine-console-query.h b/validator-engine-console/validator-engine-console-query.h index 4ea172a3..f85179bd 100644 --- a/validator-engine-console/validator-engine-console-query.h +++ b/validator-engine-console/validator-engine-console-query.h @@ -413,6 +413,30 @@ class SignFileQuery : public Query { std::string out_file_; }; +class ExportAllPrivateKeysQuery : public Query { + public: + ExportAllPrivateKeysQuery(td::actor::ActorId console, Tokenizer tokenizer) + : Query(console, std::move(tokenizer)) { + } + td::Status run() override; + td::Status send() override; + td::Status receive(td::BufferSlice R) override; + static std::string get_name() { + return "exportallprivatekeys"; + } + static std::string get_help() { + return "exportallprivatekeys \texports all private keys from validator engine and stores them to " + ""; + } + std::string name() const override { + return get_name(); + } + + private: + std::string directory_; + ton::PrivateKey client_pk_; +}; + class AddAdnlAddrQuery : public Query { public: AddAdnlAddrQuery(td::actor::ActorId console, Tokenizer tokenizer) diff --git a/validator-engine-console/validator-engine-console.cpp b/validator-engine-console/validator-engine-console.cpp index 1f348e28..85c92564 100644 --- a/validator-engine-console/validator-engine-console.cpp +++ b/validator-engine-console/validator-engine-console.cpp @@ -112,6 +112,7 @@ void ValidatorEngineConsole::run() { add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); + add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); add_query_runner(std::make_unique>()); diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp index 78d3ba2b..9ca22c4b 100644 --- a/validator-engine/validator-engine.cpp +++ b/validator-engine/validator-engine.cpp @@ -3333,6 +3333,70 @@ void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_sign &que std::move(query.data_), std::move(P)); } +void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_exportAllPrivateKeys &query, + td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, + td::Promise promise) { + if (!(perm & ValidatorEnginePermissions::vep_unsafe)) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "not authorized"))); + return; + } + if (keyring_.empty()) { + promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "not started keyring"))); + return; + } + + ton::PublicKey client_pubkey = ton::PublicKey{query.encryption_key_}; + if (!client_pubkey.is_ed25519()) { + promise.set_value( + create_control_query_error(td::Status::Error(ton::ErrorCode::protoviolation, "encryption key is not Ed25519"))); + return; + } + + td::actor::send_closure( + keyring_, &ton::keyring::Keyring::export_all_private_keys, + [promise = std::move(promise), + client_pubkey = std::move(client_pubkey)](td::Result> R) mutable { + if (R.is_error()) { + promise.set_value(create_control_query_error(R.move_as_error())); + return; + } + // Private keys are encrypted using client-provided public key to avoid storing them in + // non-secure buffers (not td::SecureString) + std::vector serialized_keys; + size_t data_size = 32; + for (const ton::PrivateKey &key : R.ok()) { + serialized_keys.push_back(key.export_as_slice()); + data_size += serialized_keys.back().size() + 4; + } + td::SecureString data{data_size}; + td::MutableSlice slice = data.as_mutable_slice(); + for (const td::SecureString &s : serialized_keys) { + td::uint32 size = td::narrow_cast_safe(s.size()).move_as_ok(); + CHECK(slice.size() >= size + 4); + slice.copy_from(td::Slice{reinterpret_cast(&size), 4}); + slice.remove_prefix(4); + slice.copy_from(s.as_slice()); + slice.remove_prefix(s.size()); + } + CHECK(slice.size() == 32); + td::Random::secure_bytes(slice); + + auto r_encryptor = client_pubkey.create_encryptor(); + if (r_encryptor.is_error()) { + promise.set_value(create_control_query_error(r_encryptor.move_as_error_prefix("cannot create encryptor: "))); + return; + } + auto encryptor = r_encryptor.move_as_ok(); + auto r_encrypted = encryptor->encrypt(data.as_slice()); + if (r_encryptor.is_error()) { + promise.set_value(create_control_query_error(r_encrypted.move_as_error_prefix("cannot encrypt data: "))); + return; + } + promise.set_value(ton::create_serialize_tl_object( + r_encrypted.move_as_ok())); + }); +} + void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_setVerbosity &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise) { if (!(perm & ValidatorEnginePermissions::vep_default)) { diff --git a/validator-engine/validator-engine.hpp b/validator-engine/validator-engine.hpp index 65af6178..b7abb0b1 100644 --- a/validator-engine/validator-engine.hpp +++ b/validator-engine/validator-engine.hpp @@ -475,6 +475,8 @@ class ValidatorEngine : public td::actor::Actor { td::uint32 perm, td::Promise promise); void run_control_query(ton::ton_api::engine_validator_sign &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); + void run_control_query(ton::ton_api::engine_validator_exportAllPrivateKeys &query, td::BufferSlice data, + ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); void run_control_query(ton::ton_api::engine_validator_setVerbosity &query, td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm, td::Promise promise); void run_control_query(ton::ton_api::engine_validator_getStats &query, td::BufferSlice data, ton::PublicKeyHash src,