Skip to content

Commit

Permalink
Export all keys command in validator-engine-console (#1412)
Browse files Browse the repository at this point in the history
* Export all keys command in validator-engine-console

* Use OPENSSL_cleanse in Bits256::fill_zero_s
  • Loading branch information
SpyCheese authored Dec 3, 2024
1 parent 4aa6412 commit 9ae88d8
Show file tree
Hide file tree
Showing 11 changed files with 173 additions and 6 deletions.
6 changes: 1 addition & 5 deletions crypto/common/bitstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 11 additions & 1 deletion keyring/keyring.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -190,6 +190,16 @@ void KeyringImpl::decrypt_message(PublicKeyHash key_hash, td::BufferSlice data,
}
}

void KeyringImpl::export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) {
std::vector<PrivateKey> 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> Keyring::create(std::string db_root) {
return td::actor::create_actor<KeyringImpl>("keyring", db_root);
}
Expand Down
2 changes: 2 additions & 0 deletions keyring/keyring.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class Keyring : public td::actor::Actor {

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

virtual void export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) = 0;

static td::actor::ActorOwn<Keyring> create(std::string db_root);
};

Expand Down
3 changes: 3 additions & 0 deletions keyring/keyring.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class KeyringImpl : public Keyring {
td::actor::ActorOwn<DecryptorAsync> decryptor_sign;
td::actor::ActorOwn<DecryptorAsync> decryptor_decrypt;
PublicKey public_key;
PrivateKey private_key;
bool is_temp;
PrivateKeyDescr(PrivateKey private_key, bool is_temp);
};
Expand All @@ -56,6 +57,8 @@ class KeyringImpl : public Keyring {

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

void export_all_private_keys(td::Promise<std::vector<PrivateKey>> promise) override;

KeyringImpl(std::string db_root) : db_root_(db_root) {
}

Expand Down
3 changes: 3 additions & 0 deletions tl/generate/scheme/ton_api.tl
Original file line number Diff line number Diff line change
Expand Up @@ -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---

Expand Down Expand Up @@ -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;
Expand Down
Binary file modified tl/generate/scheme/ton_api.tlo
Binary file not shown.
62 changes: 62 additions & 0 deletions validator-engine-console/validator-engine-console-query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <cctype>
Expand Down Expand Up @@ -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<std::string>());
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<ton::ton_api::engine_validator_exportAllPrivateKeys>(
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<ton::ton_api::engine_validator_exportedPrivateKeys>(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<ton::PrivateKey> private_keys;
while (!slice.empty()) {
if (slice.size() < 4) {
return td::Status::Error("unexpected end of data");
}
td::uint32 size;
td::MutableSlice{reinterpret_cast<char *>(&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<ton::PublicKeyHash>());
TRY_RESULT_ASSIGN(category_, tokenizer_.get_token<td::uint32>());
Expand Down
24 changes: 24 additions & 0 deletions validator-engine-console/validator-engine-console-query.h
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,30 @@ class SignFileQuery : public Query {
std::string out_file_;
};

class ExportAllPrivateKeysQuery : public Query {
public:
ExportAllPrivateKeysQuery(td::actor::ActorId<ValidatorEngineConsole> 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 <directory>\texports all private keys from validator engine and stores them to "
"<directory>";
}
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<ValidatorEngineConsole> console, Tokenizer tokenizer)
Expand Down
1 change: 1 addition & 0 deletions validator-engine-console/validator-engine-console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ void ValidatorEngineConsole::run() {
add_query_runner(std::make_unique<QueryRunnerImpl<ExportPublicKeyFileQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<SignQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<SignFileQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<ExportAllPrivateKeysQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<AddAdnlAddrQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<AddDhtIdQuery>>());
add_query_runner(std::make_unique<QueryRunnerImpl<AddValidatorPermanentKeyQuery>>());
Expand Down
64 changes: 64 additions & 0 deletions validator-engine/validator-engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<td::BufferSlice> 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<std::vector<ton::PrivateKey>> 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<td::SecureString> 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<td::uint32>(s.size()).move_as_ok();
CHECK(slice.size() >= size + 4);
slice.copy_from(td::Slice{reinterpret_cast<const td::uint8 *>(&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<ton::ton_api::engine_validator_exportedPrivateKeys>(
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<td::BufferSlice> promise) {
if (!(perm & ValidatorEnginePermissions::vep_default)) {
Expand Down
2 changes: 2 additions & 0 deletions validator-engine/validator-engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ class ValidatorEngine : public td::actor::Actor {
td::uint32 perm, td::Promise<td::BufferSlice> promise);
void run_control_query(ton::ton_api::engine_validator_sign &query, td::BufferSlice data, ton::PublicKeyHash src,
td::uint32 perm, td::Promise<td::BufferSlice> promise);
void run_control_query(ton::ton_api::engine_validator_exportAllPrivateKeys &query, td::BufferSlice data,
ton::PublicKeyHash src, td::uint32 perm, td::Promise<td::BufferSlice> promise);
void run_control_query(ton::ton_api::engine_validator_setVerbosity &query, td::BufferSlice data,
ton::PublicKeyHash src, td::uint32 perm, td::Promise<td::BufferSlice> promise);
void run_control_query(ton::ton_api::engine_validator_getStats &query, td::BufferSlice data, ton::PublicKeyHash src,
Expand Down

0 comments on commit 9ae88d8

Please sign in to comment.