Skip to content

Commit 9ae88d8

Browse files
authored
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
1 parent 4aa6412 commit 9ae88d8

File tree

11 files changed

+173
-6
lines changed

11 files changed

+173
-6
lines changed

crypto/common/bitstring.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -554,11 +554,7 @@ class BitArray {
554554
set_same(0);
555555
}
556556
void set_zero_s() {
557-
volatile uint8* p = data();
558-
auto x = m;
559-
while (x--) {
560-
*p++ = 0;
561-
}
557+
as_slice().fill_zero_secure();
562558
}
563559
void set_ones() {
564560
set_same(1);

keyring/keyring.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace ton {
2828
namespace keyring {
2929

3030
KeyringImpl::PrivateKeyDescr::PrivateKeyDescr(PrivateKey private_key, bool is_temp)
31-
: public_key(private_key.compute_public_key()), is_temp(is_temp) {
31+
: public_key(private_key.compute_public_key()), private_key(private_key), is_temp(is_temp) {
3232
auto D = private_key.create_decryptor_async();
3333
D.ensure();
3434
decryptor_sign = D.move_as_ok();
@@ -190,6 +190,16 @@ void KeyringImpl::decrypt_message(PublicKeyHash key_hash, td::BufferSlice data,
190190
}
191191
}
192192

193+
void KeyringImpl::export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) {
194+
std::vector<PrivateKey> keys;
195+
for (auto& [_, descr] : map_) {
196+
if (!descr->is_temp && descr->private_key.exportable()) {
197+
keys.push_back(descr->private_key);
198+
}
199+
}
200+
promise.set_value(std::move(keys));
201+
}
202+
193203
td::actor::ActorOwn<Keyring> Keyring::create(std::string db_root) {
194204
return td::actor::create_actor<KeyringImpl>("keyring", db_root);
195205
}

keyring/keyring.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class Keyring : public td::actor::Actor {
4444

4545
virtual void decrypt_message(PublicKeyHash key_hash, td::BufferSlice data, td::Promise<td::BufferSlice> promise) = 0;
4646

47+
virtual void export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) = 0;
48+
4749
static td::actor::ActorOwn<Keyring> create(std::string db_root);
4850
};
4951

keyring/keyring.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class KeyringImpl : public Keyring {
3333
td::actor::ActorOwn<DecryptorAsync> decryptor_sign;
3434
td::actor::ActorOwn<DecryptorAsync> decryptor_decrypt;
3535
PublicKey public_key;
36+
PrivateKey private_key;
3637
bool is_temp;
3738
PrivateKeyDescr(PrivateKey private_key, bool is_temp);
3839
};
@@ -56,6 +57,8 @@ class KeyringImpl : public Keyring {
5657

5758
void decrypt_message(PublicKeyHash key_hash, td::BufferSlice data, td::Promise<td::BufferSlice> promise) override;
5859

60+
void export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) override;
61+
5962
KeyringImpl(std::string db_root) : db_root_(db_root) {
6063
}
6164

tl/generate/scheme/ton_api.tl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,8 @@ engine.validator.perfTimerStats stats:(vector engine.validator.PerfTimerStatsByN
725725

726726
engine.validator.shardOutQueueSize size:long = engine.validator.ShardOutQueueSize;
727727

728+
engine.validator.exportedPrivateKeys encrypted_data:bytes = engine.validator.ExportedPrivateKeys;
729+
728730

729731
---functions---
730732

@@ -755,6 +757,7 @@ engine.validator.delListeningPort ip:int port:int categories:(vector int) priori
755757
engine.validator.delProxy out_ip:int out_port:int categories:(vector int) priority_categories:(vector int) = engine.validator.Success;
756758

757759
engine.validator.sign key_hash:int256 data:bytes = engine.validator.Signature;
760+
engine.validator.exportAllPrivateKeys encryption_key:PublicKey = engine.validator.ExportedPrivateKeys;
758761

759762
engine.validator.getStats = engine.validator.Stats;
760763
engine.validator.getConfig = engine.validator.JsonConfig;

tl/generate/scheme/ton_api.tlo

308 Bytes
Binary file not shown.

validator-engine-console/validator-engine-console-query.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include "ton/ton-tl.hpp"
3636
#include "td/utils/JsonBuilder.h"
3737
#include "auto/tl/ton_api_json.h"
38+
#include "keys/encryptor.h"
39+
#include "td/utils/port/path.h"
3840
#include "tl/tl_json.h"
3941

4042
#include <cctype>
@@ -283,6 +285,66 @@ td::Status SignFileQuery::receive(td::BufferSlice data) {
283285
return td::Status::OK();
284286
}
285287

288+
td::Status ExportAllPrivateKeysQuery::run() {
289+
TRY_RESULT_ASSIGN(directory_, tokenizer_.get_token<std::string>());
290+
TRY_STATUS(tokenizer_.check_endl());
291+
client_pk_ = ton::privkeys::Ed25519::random();
292+
return td::Status::OK();
293+
}
294+
295+
td::Status ExportAllPrivateKeysQuery::send() {
296+
auto b = ton::create_serialize_tl_object<ton::ton_api::engine_validator_exportAllPrivateKeys>(
297+
client_pk_.compute_public_key().tl());
298+
td::actor::send_closure(console_, &ValidatorEngineConsole::envelope_send_query, std::move(b), create_promise());
299+
return td::Status::OK();
300+
}
301+
302+
td::Status ExportAllPrivateKeysQuery::receive(td::BufferSlice data) {
303+
TRY_RESULT_PREFIX(f, ton::fetch_tl_object<ton::ton_api::engine_validator_exportedPrivateKeys>(data.as_slice(), true),
304+
"received incorrect answer: ");
305+
// Private keys are encrypted using client-provided public key to avoid storing them in
306+
// non-secure buffers (not td::SecureString)
307+
TRY_RESULT_PREFIX(decryptor, client_pk_.create_decryptor(), "cannot create decryptor: ");
308+
TRY_RESULT_PREFIX(keys_data, decryptor->decrypt(f->encrypted_data_.as_slice()), "cannot decrypt data: ");
309+
SCOPE_EXIT {
310+
keys_data.as_slice().fill_zero_secure();
311+
};
312+
td::Slice slice = keys_data.as_slice();
313+
if (slice.size() < 32) {
314+
return td::Status::Error("data is too small");
315+
}
316+
slice.remove_suffix(32);
317+
std::vector<ton::PrivateKey> private_keys;
318+
while (!slice.empty()) {
319+
if (slice.size() < 4) {
320+
return td::Status::Error("unexpected end of data");
321+
}
322+
td::uint32 size;
323+
td::MutableSlice{reinterpret_cast<char *>(&size), 4}.copy_from(slice.substr(0, 4));
324+
if (size > slice.size()) {
325+
return td::Status::Error("unexpected end of data");
326+
}
327+
slice.remove_prefix(4);
328+
TRY_RESULT_PREFIX(private_key, ton::PrivateKey::import(slice.substr(0, size)), "cannot parse private key: ");
329+
if (!private_key.exportable()) {
330+
return td::Status::Error("private key is not exportable");
331+
}
332+
private_keys.push_back(std::move(private_key));
333+
slice.remove_prefix(size);
334+
}
335+
336+
TRY_STATUS_PREFIX(td::mkpath(directory_ + "/"), "cannot create directory " + directory_ + ": ");
337+
td::TerminalIO::out() << "exported " << private_keys.size() << " private keys" << "\n";
338+
for (const ton::PrivateKey &private_key : private_keys) {
339+
std::string hash_hex = private_key.compute_short_id().bits256_value().to_hex();
340+
TRY_STATUS_PREFIX(td::write_file(directory_ + "/" + hash_hex, private_key.export_as_slice()),
341+
"failed to write file: ");
342+
td::TerminalIO::out() << "pubkey_hash " << hash_hex << "\n";
343+
}
344+
td::TerminalIO::out() << "written all files to " << directory_ << "\n";
345+
return td::Status::OK();
346+
}
347+
286348
td::Status AddAdnlAddrQuery::run() {
287349
TRY_RESULT_ASSIGN(key_hash_, tokenizer_.get_token<ton::PublicKeyHash>());
288350
TRY_RESULT_ASSIGN(category_, tokenizer_.get_token<td::uint32>());

validator-engine-console/validator-engine-console-query.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,30 @@ class SignFileQuery : public Query {
413413
std::string out_file_;
414414
};
415415

416+
class ExportAllPrivateKeysQuery : public Query {
417+
public:
418+
ExportAllPrivateKeysQuery(td::actor::ActorId<ValidatorEngineConsole> console, Tokenizer tokenizer)
419+
: Query(console, std::move(tokenizer)) {
420+
}
421+
td::Status run() override;
422+
td::Status send() override;
423+
td::Status receive(td::BufferSlice R) override;
424+
static std::string get_name() {
425+
return "exportallprivatekeys";
426+
}
427+
static std::string get_help() {
428+
return "exportallprivatekeys <directory>\texports all private keys from validator engine and stores them to "
429+
"<directory>";
430+
}
431+
std::string name() const override {
432+
return get_name();
433+
}
434+
435+
private:
436+
std::string directory_;
437+
ton::PrivateKey client_pk_;
438+
};
439+
416440
class AddAdnlAddrQuery : public Query {
417441
public:
418442
AddAdnlAddrQuery(td::actor::ActorId<ValidatorEngineConsole> console, Tokenizer tokenizer)

validator-engine-console/validator-engine-console.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ void ValidatorEngineConsole::run() {
112112
add_query_runner(std::make_unique<QueryRunnerImpl<ExportPublicKeyFileQuery>>());
113113
add_query_runner(std::make_unique<QueryRunnerImpl<SignQuery>>());
114114
add_query_runner(std::make_unique<QueryRunnerImpl<SignFileQuery>>());
115+
add_query_runner(std::make_unique<QueryRunnerImpl<ExportAllPrivateKeysQuery>>());
115116
add_query_runner(std::make_unique<QueryRunnerImpl<AddAdnlAddrQuery>>());
116117
add_query_runner(std::make_unique<QueryRunnerImpl<AddDhtIdQuery>>());
117118
add_query_runner(std::make_unique<QueryRunnerImpl<AddValidatorPermanentKeyQuery>>());

validator-engine/validator-engine.cpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,6 +3333,70 @@ void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_sign &que
33333333
std::move(query.data_), std::move(P));
33343334
}
33353335

3336+
void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_exportAllPrivateKeys &query,
3337+
td::BufferSlice data, ton::PublicKeyHash src, td::uint32 perm,
3338+
td::Promise<td::BufferSlice> promise) {
3339+
if (!(perm & ValidatorEnginePermissions::vep_unsafe)) {
3340+
promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::error, "not authorized")));
3341+
return;
3342+
}
3343+
if (keyring_.empty()) {
3344+
promise.set_value(create_control_query_error(td::Status::Error(ton::ErrorCode::notready, "not started keyring")));
3345+
return;
3346+
}
3347+
3348+
ton::PublicKey client_pubkey = ton::PublicKey{query.encryption_key_};
3349+
if (!client_pubkey.is_ed25519()) {
3350+
promise.set_value(
3351+
create_control_query_error(td::Status::Error(ton::ErrorCode::protoviolation, "encryption key is not Ed25519")));
3352+
return;
3353+
}
3354+
3355+
td::actor::send_closure(
3356+
keyring_, &ton::keyring::Keyring::export_all_private_keys,
3357+
[promise = std::move(promise),
3358+
client_pubkey = std::move(client_pubkey)](td::Result<std::vector<ton::PrivateKey>> R) mutable {
3359+
if (R.is_error()) {
3360+
promise.set_value(create_control_query_error(R.move_as_error()));
3361+
return;
3362+
}
3363+
// Private keys are encrypted using client-provided public key to avoid storing them in
3364+
// non-secure buffers (not td::SecureString)
3365+
std::vector<td::SecureString> serialized_keys;
3366+
size_t data_size = 32;
3367+
for (const ton::PrivateKey &key : R.ok()) {
3368+
serialized_keys.push_back(key.export_as_slice());
3369+
data_size += serialized_keys.back().size() + 4;
3370+
}
3371+
td::SecureString data{data_size};
3372+
td::MutableSlice slice = data.as_mutable_slice();
3373+
for (const td::SecureString &s : serialized_keys) {
3374+
td::uint32 size = td::narrow_cast_safe<td::uint32>(s.size()).move_as_ok();
3375+
CHECK(slice.size() >= size + 4);
3376+
slice.copy_from(td::Slice{reinterpret_cast<const td::uint8 *>(&size), 4});
3377+
slice.remove_prefix(4);
3378+
slice.copy_from(s.as_slice());
3379+
slice.remove_prefix(s.size());
3380+
}
3381+
CHECK(slice.size() == 32);
3382+
td::Random::secure_bytes(slice);
3383+
3384+
auto r_encryptor = client_pubkey.create_encryptor();
3385+
if (r_encryptor.is_error()) {
3386+
promise.set_value(create_control_query_error(r_encryptor.move_as_error_prefix("cannot create encryptor: ")));
3387+
return;
3388+
}
3389+
auto encryptor = r_encryptor.move_as_ok();
3390+
auto r_encrypted = encryptor->encrypt(data.as_slice());
3391+
if (r_encryptor.is_error()) {
3392+
promise.set_value(create_control_query_error(r_encrypted.move_as_error_prefix("cannot encrypt data: ")));
3393+
return;
3394+
}
3395+
promise.set_value(ton::create_serialize_tl_object<ton::ton_api::engine_validator_exportedPrivateKeys>(
3396+
r_encrypted.move_as_ok()));
3397+
});
3398+
}
3399+
33363400
void ValidatorEngine::run_control_query(ton::ton_api::engine_validator_setVerbosity &query, td::BufferSlice data,
33373401
ton::PublicKeyHash src, td::uint32 perm, td::Promise<td::BufferSlice> promise) {
33383402
if (!(perm & ValidatorEnginePermissions::vep_default)) {

0 commit comments

Comments
 (0)