diff --git a/beacon/CMakeLists.txt b/beacon/CMakeLists.txt index 799d7fee..d749e80a 100644 --- a/beacon/CMakeLists.txt +++ b/beacon/CMakeLists.txt @@ -41,6 +41,12 @@ target_link_libraries(beacon INTERFACE Boost::iterator) # Boost::filesystem target_link_libraries(beacon INTERFACE Boost::filesystem) +# cryptopp +target_link_libraries(beacon INTERFACE cryptopp::CryptoPP) + +# rapidjson +target_link_libraries(beacon INTERFACE rapidjson) + install(TARGETS beacon EXPORT marlin-beacon-export LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/beacon/examples/server.cpp b/beacon/examples/server.cpp index f81bfea9..ced697fe 100644 --- a/beacon/examples/server.cpp +++ b/beacon/examples/server.cpp @@ -2,21 +2,44 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include using namespace marlin; class BeaconDelegate {}; struct CliOptions { + std::optional keystore_path; + std::optional keystore_pass_path; std::optional discovery_addr; std::optional heartbeat_addr; std::optional beacon_addr; }; -STRUCTOPT(CliOptions, discovery_addr, heartbeat_addr, beacon_addr); +STRUCTOPT(CliOptions, keystore_path, keystore_pass_path, discovery_addr, heartbeat_addr, beacon_addr); + +std::string get_key(std::string keystore_path, std::string keystore_pass_path); int main(int argc, char** argv) { try { auto options = structopt::app("beacon").parse(argc, argv); + if(options.beacon_addr.has_value()) { + if(options.keystore_path.has_value() && options.keystore_pass_path.has_value()) { + std::string key = get_key(options.keystore_path.value(), options.keystore_pass_path.value()); + if (key.empty()) { + SPDLOG_ERROR("keystore file error"); + return 1; + } + } else { + SPDLOG_ERROR("require keystore and password file"); + return 1; + } + } auto discovery_addr = core::SocketAddress::from_string( options.discovery_addr.value_or("127.0.0.1:8002") ); @@ -43,3 +66,143 @@ int main(int argc, char** argv) { return -1; } + +std::string string_to_hex(const std::string& input) +{ + std::string output; + CryptoPP::StringSource ss2( input, true, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink( output ) + )); // HexEncoder + return output; +} + +std::string hex_to_string(const std::string& input) +{ + std::string output; + CryptoPP::StringSource ss2( input, true, + new CryptoPP::HexDecoder( + new CryptoPP::StringSink( output ) + )); // HexDecoder + return output; +} + +bool isvalid_cipherparams(const rapidjson::Value &cipherparams, const std::string &cipher) { + if(cipher == "aes-128-ctr") { + return cipherparams.HasMember("iv") && cipherparams["iv"].IsString(); + } + return false; +} + +bool isvalid_kdfparams(const rapidjson::Value &kdfparams, const std::string &kdf) { + if(kdf == "scrypt") { + return kdfparams.HasMember("dklen") && kdfparams["dklen"].IsUint64() + && kdfparams.HasMember("n") && kdfparams["n"].IsUint64() + && kdfparams.HasMember("p") && kdfparams["p"].IsUint64() + && kdfparams.HasMember("r") && kdfparams["r"].IsUint64() + && kdfparams.HasMember("salt") && kdfparams["salt"].IsString(); + } + return false; +} + +bool isvalid_keystore(rapidjson::Document &keystore) { + if (keystore.HasMember("crypto") && keystore["crypto"].IsObject() ) { + const rapidjson::Value &crypto = keystore["crypto"]; + const rapidjson::Value &cipherparams = crypto["cipherparams"]; + const rapidjson::Value &kdfparams = crypto["kdfparams"]; + return crypto.HasMember("cipher") && crypto["cipher"].IsString() + && crypto.HasMember("ciphertext") && crypto["ciphertext"].IsString() + && crypto.HasMember("kdf") && crypto["kdf"].IsString() + && crypto.HasMember("mac") && crypto["mac"].IsString() + && crypto.HasMember("cipherparams") && crypto["cipherparams"].IsObject() + && crypto.HasMember("kdfparams") && crypto["kdfparams"].IsObject() + && isvalid_cipherparams(cipherparams, crypto["cipher"].GetString()) + && isvalid_kdfparams(kdfparams, crypto["kdf"].GetString()); + } + return false; +} + +void derivekey_scrypt(CryptoPP::SecByteBlock &derived, const rapidjson::Value &kdfparams, const std::string &pass) { + + CryptoPP::Scrypt pbkdf; + derived = CryptoPP::SecByteBlock(kdfparams["dklen"].GetUint()); + std::string salt(hex_to_string(kdfparams["salt"].GetString())); + pbkdf.DeriveKey(derived, derived.size(), + CryptoPP::ConstBytePtr(pass), CryptoPP::BytePtrSize(pass), + CryptoPP::ConstBytePtr(salt), CryptoPP::BytePtrSize(salt), + CryptoPP::word64(kdfparams["n"].GetUint64()), + CryptoPP::word64(kdfparams["r"].GetUint64()), + CryptoPP::word64(kdfparams["p"].GetUint64())); +} + +std::string decrypt_aes128ctr(const rapidjson::Value &cipherparams, std::string &ciphertext, CryptoPP::SecByteBlock &derived) { + std::string iv = hex_to_string(cipherparams["iv"].GetString()); + + CryptoPP::CTR_Mode::Decryption d; + d.SetKeyWithIV(derived, 16, CryptoPP::ConstBytePtr(iv)); + + CryptoPP::SecByteBlock decrypted(ciphertext.size()); + d.ProcessData(decrypted, CryptoPP::ConstBytePtr(ciphertext), CryptoPP::BytePtrSize(ciphertext)); + return std::string((const char*)decrypted.data(), decrypted.size()); +} + +bool check_mac(const std::string &mac, CryptoPP::SecByteBlock &derived, std::string &ciphertext) { + CryptoPP::Keccak_256 hasher; + auto hashinput = CryptoPP::SecByteBlock((derived.data()+16), 16) + CryptoPP::SecByteBlock((const CryptoPP::byte*)ciphertext.data(), ciphertext.size()); + + CryptoPP::SecByteBlock hash(mac.size()); + return hasher.VerifyTruncatedDigest((const CryptoPP::byte*)mac.data(), mac.size(), hashinput, hashinput.size()); +} + +std::string get_key(std::string keystore_path, std::string keystore_pass_path) { + std::string _pass; + rapidjson::Document _keystore; + + // read password file + if(boost::filesystem::exists(keystore_pass_path)) { + std::ifstream fin(keystore_pass_path); + std::getline(fin, _pass); + fin.close(); + } + if (_pass.empty()) { + SPDLOG_ERROR("Invalid password file"); + return ""; + } + + // read keystore file + if(boost::filesystem::exists(keystore_path)) { + std::string s; + boost::filesystem::load_string_file(keystore_path, s); + rapidjson::StringStream ss(s.c_str()); + _keystore.ParseStream(ss); + } + if (!isvalid_keystore(_keystore)){ + SPDLOG_ERROR("Invalid keystore file"); + return ""; + } + + const rapidjson::Value &crypto = _keystore["crypto"]; + std::string ciphertext = hex_to_string(crypto["ciphertext"].GetString()); + CryptoPP::SecByteBlock derived; + std::string decrypted; + + // get derived keycrypto + if (crypto["kdf"] == "scrypt") { + derivekey_scrypt(derived, crypto["kdfparams"], _pass); + } + if (derived.size() == 0) { + SPDLOG_ERROR("kdf error"); + return ""; + } + if (crypto["cipher"] == "aes-128-ctr") { + decrypted = decrypt_aes128ctr(crypto["cipherparams"], ciphertext, derived); + } + + //validate mac + if (!check_mac(hex_to_string(crypto["mac"].GetString()), derived, ciphertext)) { + SPDLOG_ERROR("Invalid mac"); + return ""; + } + SPDLOG_INFO("decrypted keystore"); + return decrypted; +}