From a7168de6d1f8748ab5f993c4cd427aabb632d382 Mon Sep 17 00:00:00 2001 From: Sztergbaum Roman Date: Mon, 21 Nov 2022 06:58:46 +0100 Subject: [PATCH] [Encryption]: add support for aes-256-ctr (#2726) --- .../core/app/utils/TestKeyStore.kt | 11 ++++ include/TrustWalletCore/TWBase.h | 7 ++ include/TrustWalletCore/TWStoredKey.h | 49 +++++++++++++- .../TrustWalletCore/TWStoredKeyEncryption.h | 22 +++++++ src/Keystore/AESParameters.cpp | 41 ++++++++++-- src/Keystore/AESParameters.h | 29 ++++++-- src/Keystore/EncryptionParameters.cpp | 40 +++++++---- src/Keystore/EncryptionParameters.h | 36 +++++----- src/Keystore/StoredKey.cpp | 24 +++---- src/Keystore/StoredKey.h | 13 ++-- src/interface/TWStoredKey.cpp | 27 ++++++-- swift/Sources/KeyStore.swift | 30 ++++----- swift/Tests/Keystore/KeyStoreTests.swift | 24 +++++++ tests/chains/XRP/TWAnySignerTests.cpp | 33 ++++------ tests/common/Keystore/StoredKeyTests.cpp | 51 +++++++++++++- tests/interface/TWStoredKeyTests.cpp | 66 +++++++++++++++++-- wasm/src/keystore/default-impl.ts | 16 +++-- wasm/src/keystore/types.ts | 8 ++- wasm/tests/KeyStore+extension.test.ts | 60 ++++++++++++++++- wasm/tests/KeyStore+fs.test.ts | 62 ++++++++++++++++- 20 files changed, 531 insertions(+), 118 deletions(-) create mode 100644 include/TrustWalletCore/TWStoredKeyEncryption.h diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt index 3832db95139..599d3369216 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/utils/TestKeyStore.kt @@ -4,6 +4,7 @@ import org.junit.Assert.* import org.junit.Test import wallet.core.jni.StoredKey import wallet.core.jni.CoinType +import wallet.core.jni.StoredKeyEncryption class TestKeyStore { @@ -21,6 +22,16 @@ class TestKeyStore { assertNotNull(result2) } + @Test + fun testDecryptMnemonicAes256() { + val keyStore = StoredKey("Test Wallet", "password".toByteArray(), StoredKeyEncryption.AES256CTR) + val result = keyStore.decryptMnemonic("wrong".toByteArray()) + val result2 = keyStore.decryptMnemonic("password".toByteArray()) + + assertNull(result) + assertNotNull(result2) + } + @Test fun testRemoveCoins() { val password = "password".toByteArray() diff --git a/include/TrustWalletCore/TWBase.h b/include/TrustWalletCore/TWBase.h index 17e618ace45..de300b6779b 100644 --- a/include/TrustWalletCore/TWBase.h +++ b/include/TrustWalletCore/TWBase.h @@ -55,6 +55,13 @@ #define TW_ASSUME_NONNULL_END #endif +#if defined(__cplusplus) && (__cplusplus >= 201402L) +# define TW_DEPRECATED(since) [[deprecated("Since " #since)]] +# define TW_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]] +#else +# define TW_DEPRECATED(since) +# define TW_DEPRECATED_FOR(since, replacement) +#endif #if !__has_feature(nullability) #ifndef _Nullable diff --git a/include/TrustWalletCore/TWStoredKey.h b/include/TrustWalletCore/TWStoredKey.h index 02bd0ae4dad..7d916be9e91 100644 --- a/include/TrustWalletCore/TWStoredKey.h +++ b/include/TrustWalletCore/TWStoredKey.h @@ -13,6 +13,7 @@ #include "TWHDWallet.h" #include "TWPrivateKey.h" #include "TWStoredKeyEncryptionLevel.h" +#include "TWStoredKeyEncryption.h" #include "TWString.h" TW_EXTERN_C_BEGIN @@ -40,6 +41,18 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path); TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); +/// Imports a private key. +/// +/// \param privateKey Non-null Block of data private key +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + /// Imports an HD wallet. /// /// \param mnemonic Non-null bip39 mnemonic @@ -51,6 +64,18 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin); +/// Imports an HD wallet. +/// +/// \param mnemonic Non-null bip39 mnemonic +/// \param name The name of the stored key to import as a non-null string +/// \param password Non-null block of data, password of the stored key +/// \param coin the coin type +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return Nullptr if the key can't be imported, the stored key otherwise +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption); + /// Imports a key from JSON. /// /// \param json Json stored key import format as a non-null block of data @@ -59,16 +84,28 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json); -/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. /// /// \param name The name of the key to be stored /// \param password Non-null block of data, password of the stored key /// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel /// \note Returned object needs to be deleted with \TWStoredKeyDelete /// \return The stored key as a non-null pointer +TW_DEPRECATED_FOR("3.1.1", "TWStoredKeyCreateLevelAndEncryption") TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel); +/// Creates a new key, with given encryption strength level. Returned object needs to be deleted. +/// +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_EXPORT_STATIC_METHOD +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevelAndEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel, enum TWStoredKeyEncryption encryption); + /// Creates a new key. /// /// \deprecated use TWStoredKeyCreateLevel. @@ -78,6 +115,16 @@ struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWD /// \return The stored key as a non-null pointer TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password); +/// Creates a new key. +/// +/// \deprecated use TWStoredKeyCreateLevel. +/// \param name The name of the key to be stored +/// \param password Non-null block of data, password of the stored key +/// \param encryption cipher encryption mode +/// \note Returned object needs to be deleted with \TWStoredKeyDelete +/// \return The stored key as a non-null pointer +TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreateEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryption encryption); + /// Delete a stored key /// /// \param key The key to be deleted diff --git a/include/TrustWalletCore/TWStoredKeyEncryption.h b/include/TrustWalletCore/TWStoredKeyEncryption.h new file mode 100644 index 00000000000..b2db9e795e0 --- /dev/null +++ b/include/TrustWalletCore/TWStoredKeyEncryption.h @@ -0,0 +1,22 @@ +// Copyright © 2017-2022 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#pragma once + +#include "TWBase.h" + +TW_EXTERN_C_BEGIN + +/// Preset encryption kind +TW_EXPORT_ENUM(uint32_t) +enum TWStoredKeyEncryption { + TWStoredKeyEncryptionAes128Ctr = 0, + TWStoredKeyEncryptionAes128Cbc = 1, + TWStoredKeyEncryptionAes192Ctr = 2, + TWStoredKeyEncryptionAes256Ctr = 3, +}; + +TW_EXTERN_C_END diff --git a/src/Keystore/AESParameters.cpp b/src/Keystore/AESParameters.cpp index 6aa7bfdc5b0..22f0df40697 100644 --- a/src/Keystore/AESParameters.cpp +++ b/src/Keystore/AESParameters.cpp @@ -12,20 +12,44 @@ using namespace TW; -namespace TW::Keystore { +namespace { -AESParameters::AESParameters() { - iv = Data(blockSize, 0); +Data generateIv(std::size_t blockSize = TW::Keystore::gBlockSize) { + auto iv = Data(blockSize, 0); random_buffer(iv.data(), blockSize); + return iv; +} + +static TWStoredKeyEncryption getCipher(const std::string& cipher) { + if (cipher == Keystore::gAes128Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes128Ctr; + } else if (cipher == Keystore::gAes192Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes192Ctr; + } else if (cipher == Keystore::gAes256Ctr) { + return TWStoredKeyEncryption::TWStoredKeyEncryptionAes256Ctr; + } + return TWStoredKeyEncryptionAes128Ctr; } +const std::unordered_map gEncryptionRegistry{ + {TWStoredKeyEncryptionAes128Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes128Ctr}}, + {TWStoredKeyEncryptionAes128Cbc, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Cbc, .mCipherEncryption = TWStoredKeyEncryptionAes128Cbc}}, + {TWStoredKeyEncryptionAes192Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A192, .mCipher = Keystore::gAes192Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes192Ctr}}, + {TWStoredKeyEncryptionAes256Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A256, .mCipher = Keystore::gAes256Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes256Ctr}} +}; +} // namespace + +namespace TW::Keystore { + namespace CodingKeys { static const auto iv = "iv"; } // namespace CodingKeys /// Initializes `AESParameters` with a JSON object. -AESParameters::AESParameters(const nlohmann::json& json) { - iv = parse_hex(json[CodingKeys::iv].get()); +AESParameters AESParameters::AESParametersFromJson(const nlohmann::json& json, const std::string& cipher) { + auto parameters = AESParameters::AESParametersFromEncryption(getCipher(cipher)); + parameters.iv = parse_hex(json[CodingKeys::iv].get()); + return parameters; } /// Saves `this` as a JSON object. @@ -35,4 +59,11 @@ nlohmann::json AESParameters::json() const { return j; } +AESParameters AESParameters::AESParametersFromEncryption(TWStoredKeyEncryption encryption) { + auto parameters = gEncryptionRegistry.at(encryption); + // be sure to regenerate an iv. + parameters.iv = generateIv(); + return parameters; +} + } // namespace TW::Keystore diff --git a/src/Keystore/AESParameters.h b/src/Keystore/AESParameters.h index edae9ab9b4f..878700782ee 100644 --- a/src/Keystore/AESParameters.h +++ b/src/Keystore/AESParameters.h @@ -9,20 +9,37 @@ #include "Data.h" #include +#include namespace TW::Keystore { -// AES128 parameters. -struct AESParameters { - static const std::size_t blockSize = 128 / 8; +enum AESKeySize : std::int32_t { + Uninitialized = 0, + A128 = 16, + A192 = 24, + A256 = 32, +}; +inline constexpr std::size_t gBlockSize{16}; +inline constexpr const char* gAes128Ctr{"aes-128-ctr"}; +inline constexpr const char* gAes128Cbc{"aes-128-cbc"}; +inline constexpr const char* gAes192Ctr{"aes-192-ctr"}; +inline constexpr const char* gAes256Ctr{"aes-256-ctr"}; + +// AES128/192/256 parameters. +struct AESParameters { + // For AES, your block length is always going to be 128 bits/16 bytes + std::int32_t mBlockSize{gBlockSize}; + std::int32_t mKeyLength{A128}; + std::string mCipher{gAes128Ctr}; + TWStoredKeyEncryption mCipherEncryption{TWStoredKeyEncryptionAes128Ctr}; Data iv; - /// Initializes `AESParameters` with a random `iv` for AES 128. - AESParameters(); + /// Initializes `AESParameters` with a encryption cipher. + static AESParameters AESParametersFromEncryption(TWStoredKeyEncryption encryption);; /// Initializes `AESParameters` with a JSON object. - AESParameters(const nlohmann::json& json); + static AESParameters AESParametersFromJson(const nlohmann::json& json, const std::string& cipher); /// Saves `this` as a JSON object. nlohmann::json json() const; diff --git a/src/Keystore/EncryptionParameters.cpp b/src/Keystore/EncryptionParameters.cpp index ad7e6ecf348..9ea7a4fa6d1 100644 --- a/src/Keystore/EncryptionParameters.cpp +++ b/src/Keystore/EncryptionParameters.cpp @@ -40,8 +40,8 @@ static const auto mac = "mac"; } // namespace CodingKeys EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { - cipher = json[CodingKeys::cipher].get(); - cipherParams = AESParameters(json[CodingKeys::cipherParams]); + auto cipher = json[CodingKeys::cipher].get(); + cipherParams = AESParameters::AESParametersFromJson(json[CodingKeys::cipherParams], cipher); auto kdf = json[CodingKeys::kdf].get(); if (kdf == "scrypt") { @@ -53,7 +53,7 @@ EncryptionParameters::EncryptionParameters(const nlohmann::json& json) { nlohmann::json EncryptionParameters::json() const { nlohmann::json j; - j[CodingKeys::cipher] = cipher; + j[CodingKeys::cipher] = cipher(); j[CodingKeys::cipherParams] = cipherParams.json(); if (auto* scryptParams = std::get_if(&kdfParams); scryptParams) { @@ -76,14 +76,25 @@ EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const scryptParams.desiredKeyLength); aes_encrypt_ctx ctx; - auto result = aes_encrypt_key128(derivedKey.data(), &ctx); + auto result = 0; + switch(this->params.cipherParams.mCipherEncryption) { + case TWStoredKeyEncryptionAes128Ctr: + case TWStoredKeyEncryptionAes128Cbc: + result = aes_encrypt_key128(derivedKey.data(), &ctx); + break; + case TWStoredKeyEncryptionAes192Ctr: + result = aes_encrypt_key192(derivedKey.data(), &ctx); + break; + case TWStoredKeyEncryptionAes256Ctr: + result = aes_encrypt_key256(derivedKey.data(), &ctx); + break; + } assert(result == EXIT_SUCCESS); if (result == EXIT_SUCCESS) { Data iv = this->params.cipherParams.iv; encrypted = Data(data.size()); aes_ctr_encrypt(data.data(), encrypted.data(), static_cast(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - - _mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + _mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } } @@ -101,13 +112,13 @@ Data EncryptedPayload::decrypt(const Data& password) const { scrypt(password.data(), password.size(), scryptParams->salt.data(), scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(), scryptParams->defaultDesiredKeyLength); - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else if (auto* pbkdf2Params = std::get_if(¶ms.kdfParams); pbkdf2Params) { derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength); pbkdf2_hmac_sha256(password.data(), static_cast(password.size()), pbkdf2Params->salt.data(), static_cast(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(), pbkdf2Params->defaultDesiredKeyLength); - mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted); + mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted); } else { throw DecryptionError::unsupportedKDF; } @@ -118,20 +129,21 @@ Data EncryptedPayload::decrypt(const Data& password) const { Data decrypted(encrypted.size()); Data iv = params.cipherParams.iv; - if (params.cipher == "aes-128-ctr") { + const auto encryption = params.cipherParams.mCipherEncryption; + if (encryption == TWStoredKeyEncryptionAes128Ctr || encryption == TWStoredKeyEncryptionAes256Ctr) { aes_encrypt_ctx ctx; - auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); assert(result != EXIT_FAILURE); aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast(encrypted.size()), iv.data(), aes_ctr_cbuf_inc, &ctx); - } else if (params.cipher == "aes-128-cbc") { + } else if (encryption == TWStoredKeyEncryptionAes128Cbc) { aes_decrypt_ctx ctx; - auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx); + [[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx); assert(result != EXIT_FAILURE); - for (auto i = 0ul; i < encrypted.size(); i += 16) { - aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, 16, iv.data(), &ctx); + for (auto i = 0ul; i < encrypted.size(); i += params.getKeyBytesSize()) { + aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, params.getKeyBytesSize(), iv.data(), &ctx); } } else { throw DecryptionError::unsupportedCipher; diff --git a/src/Keystore/EncryptionParameters.h b/src/Keystore/EncryptionParameters.h index 6592b9756cd..a3e3ac59750 100644 --- a/src/Keystore/EncryptionParameters.h +++ b/src/Keystore/EncryptionParameters.h @@ -7,34 +7,40 @@ #pragma once #include "AESParameters.h" +#include "Data.h" #include "PBKDF2Parameters.h" #include "ScryptParameters.h" -#include "Data.h" +#include #include -#include #include #include +#include namespace TW::Keystore { /// Set of parameters used when encoding struct EncryptionParameters { - static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset) { + static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset, enum TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { switch (preset) { - case TWStoredKeyEncryptionLevelMinimal: - return EncryptionParameters(AESParameters(), ScryptParameters::Minimal); - case TWStoredKeyEncryptionLevelWeak: - case TWStoredKeyEncryptionLevelDefault: - default: - return EncryptionParameters(AESParameters(), ScryptParameters::Weak); - case TWStoredKeyEncryptionLevelStandard: - return EncryptionParameters(AESParameters(), ScryptParameters::Standard); + case TWStoredKeyEncryptionLevelMinimal: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Minimal); + case TWStoredKeyEncryptionLevelWeak: + case TWStoredKeyEncryptionLevelDefault: + default: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Weak); + case TWStoredKeyEncryptionLevelStandard: + return EncryptionParameters(AESParameters::AESParametersFromEncryption(encryption), ScryptParameters::Standard); } } - /// Cipher algorithm. - std::string cipher = "aes-128-ctr"; + std::int32_t getKeyBytesSize() const noexcept { + return cipherParams.mKeyLength; + } + + std::string cipher() const noexcept { + return cipherParams.mCipher; + } /// Cipher parameters. AESParameters cipherParams = AESParameters(); @@ -46,8 +52,8 @@ struct EncryptionParameters { /// Initializes with standard values. EncryptionParameters(AESParameters cipherParams, std::variant kdfParams) - : cipherParams(std::move(cipherParams)) - , kdfParams(std::move(kdfParams)) {} + : cipherParams(std::move(cipherParams)), kdfParams(std::move(kdfParams)) { + } /// Initializes with a JSON object. EncryptionParameters(const nlohmann::json& json); diff --git a/src/Keystore/StoredKey.cpp b/src/Keystore/StoredKey.cpp index add26c2ca26..9b761186ce4 100644 --- a/src/Keystore/StoredKey.cpp +++ b/src/Keystore/StoredKey.cpp @@ -26,41 +26,41 @@ using namespace TW; namespace TW::Keystore { -StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel) { +StoredKey StoredKey::createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) { if (!Mnemonic::isValid(mnemonic)) { throw std::invalid_argument("Invalid mnemonic"); } Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); + return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); } -StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel) { +StoredKey StoredKey::createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) { const auto wallet = TW::HDWallet(128, ""); const auto& mnemonic = wallet.getMnemonic(); assert(Mnemonic::isValid(mnemonic)); Data mnemonicData = TW::Data(mnemonic.begin(), mnemonic.end()); - return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel); + return StoredKey(StoredKeyType::mnemonicPhrase, name, password, mnemonicData, encryptionLevel, encryption); } -StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin) { - StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault); +StoredKey StoredKey::createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin, TWStoredKeyEncryption encryption) { + StoredKey key = createWithMnemonic(name, password, mnemonic, TWStoredKeyEncryptionLevelDefault, encryption); const auto wallet = key.wallet(password); key.account(coin, &wallet); return key; } -StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData) { - return StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault); +StoredKey StoredKey::createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData, TWStoredKeyEncryption encryption) { + return StoredKey(StoredKeyType::privateKey, name, password, privateKeyData, TWStoredKeyEncryptionLevelDefault, encryption); } -StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData) { +StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption) { const auto curve = TW::curve(coin); if (!PrivateKey::isValid(privateKeyData, curve)) { throw std::invalid_argument("Invalid private key data"); } - StoredKey key = createWithPrivateKey(name, password, privateKeyData); + StoredKey key = createWithPrivateKey(name, password, privateKeyData, encryption); const auto derivationPath = TW::derivationPath(coin); const auto pubKeyType = TW::publicKeyType(coin); const auto pubKey = PrivateKey(privateKeyData).getPublicKey(pubKeyType); @@ -69,9 +69,9 @@ StoredKey StoredKey::createWithPrivateKeyAddDefaultAddress(const std::string& na return key; } -StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel) +StoredKey::StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption) : type(type), id(), name(std::move(name)), accounts() { - const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel); + const auto encryptionParams = EncryptionParameters::getPreset(encryptionLevel, encryption); payload = EncryptedPayload(password, data, encryptionParams); boost::uuids::random_generator gen; id = boost::lexical_cast(gen()); diff --git a/src/Keystore/StoredKey.h b/src/Keystore/StoredKey.h index 96d3010064b..58a5dca3f77 100644 --- a/src/Keystore/StoredKey.h +++ b/src/Keystore/StoredKey.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -45,23 +46,23 @@ class StoredKey { /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel); + static StoredKey createWithMnemonic(const std::string& name, const Data& password, const std::string& mnemonic, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name, mnemonic and password. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel); + static StoredKey createWithMnemonicRandom(const std::string& name, const Data& password, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name, mnemonic and password, and also add the default address for the given coin.. /// @throws std::invalid_argument if mnemonic is invalid - static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin); + static StoredKey createWithMnemonicAddDefaultAddress(const std::string& name, const Data& password, const std::string& mnemonic, TWCoinType coin, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name and private key. /// @throws std::invalid_argument if privateKeyData is not a valid private key - static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData); + static StoredKey createWithPrivateKey(const std::string& name, const Data& password, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a new StoredKey, with the given name and private key, and also add the default address for the given coin.. /// @throws std::invalid_argument if privateKeyData is not a valid private key - static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData); + static StoredKey createWithPrivateKeyAddDefaultAddress(const std::string& name, const Data& password, TWCoinType coin, const Data& privateKeyData, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Create a StoredKey from a JSON object. static StoredKey createWithJson(const nlohmann::json& json); @@ -152,7 +153,7 @@ class StoredKey { /// Initializes a `StoredKey` with a type, an encryption password, and unencrypted data. /// This constructor will encrypt the provided data with default encryption /// parameters. - StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel); + StoredKey(StoredKeyType type, std::string name, const Data& password, const Data& data, TWStoredKeyEncryptionLevel encryptionLevel, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr); /// Find default account for coin, if exists. If multiple exist, default is returned. /// Optional wallet is needed to derive default address diff --git a/src/interface/TWStoredKey.cpp b/src/interface/TWStoredKey.cpp index bccd49a9a03..d07378fb9e4 100644 --- a/src/interface/TWStoredKey.cpp +++ b/src/interface/TWStoredKey.cpp @@ -25,33 +25,50 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path) { } } -struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevelAndEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel, enum TWStoredKeyEncryption encryption) { const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicRandom(nameString, passwordData, encryptionLevel, encryption) }; +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel) { + return TWStoredKeyCreateLevelAndEncryption(name, password, encryptionLevel, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nonnull TWStoredKeyCreateEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryption encryption) { + return TWStoredKeyCreateLevelAndEncryption(name, password, TWStoredKeyEncryptionLevelDefault, encryption); } struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password) { - return TWStoredKeyCreateLevel(name, password, TWStoredKeyEncryptionLevelDefault); + return TWStoredKeyCreateLevelAndEncryption(name, password, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Ctr); } struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportPrivateKeyWithEncryption(privateKey, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + +struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { try { const auto& privateKeyData = *reinterpret_cast(privateKey); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithPrivateKeyAddDefaultAddress(nameString, passwordData, coin, privateKeyData, encryption) }; } catch (...) { return nullptr; } } struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin) { + return TWStoredKeyImportHDWalletWithEncryption(mnemonic, name, password, coin, TWStoredKeyEncryptionAes128Ctr); +} + + +struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption) { try { const auto& mnemonicString = *reinterpret_cast(mnemonic); const auto& nameString = *reinterpret_cast(name); const auto passwordData = TW::data(TWDataBytes(password), TWDataSize(password)); - return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin) }; + return new TWStoredKey{ KeyStore::StoredKey::createWithMnemonicAddDefaultAddress(nameString, passwordData, mnemonicString, coin, encryption) }; } catch (...) { return nullptr; } diff --git a/swift/Sources/KeyStore.swift b/swift/Sources/KeyStore.swift index 815843c87de..0b6c3368d77 100644 --- a/swift/Sources/KeyStore.swift +++ b/swift/Sources/KeyStore.swift @@ -74,8 +74,8 @@ public final class KeyStore { } /// Creates a new wallet. HD default by default - public func createWallet(name: String, password: String, coins: [CoinType]) throws -> Wallet { - let key = StoredKey(name: name, password: Data(password.utf8)) + public func createWallet(name: String, password: String, coins: [CoinType], encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + let key = StoredKey(name: name, password: Data(password.utf8), encryption: encryption) return try saveCreatedWallet(for: key, password: password, coins: coins) } @@ -158,8 +158,8 @@ public final class KeyStore { /// - password: password to use for the imported private key /// - coin: coin to use for this wallet /// - Returns: new wallet - public func `import`(privateKey: PrivateKey, name: String, password: String, coin: CoinType) throws -> Wallet { - guard let newKey = StoredKey.importPrivateKey(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin) else { + public func `import`(privateKey: PrivateKey, name: String, password: String, coin: CoinType, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKey.data, name: name, password: Data(password.utf8), coin: coin, encryption: encryption) else { throw Error.invalidKey } let url = makeAccountURL() @@ -179,8 +179,8 @@ public final class KeyStore { /// - encryptPassword: password to use for encrypting /// - coins: coins to add /// - Returns: new account - public func `import`(mnemonic: String, name: String, encryptPassword: String, coins: [CoinType]) throws -> Wallet { - guard let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum) else { + public func `import`(mnemonic: String, name: String, encryptPassword: String, coins: [CoinType], encryption: StoredKeyEncryption = .aes128Ctr) throws -> Wallet { + guard let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: name, password: Data(encryptPassword.utf8), coin: coins.first ?? .ethereum, encryption: encryption) else { throw Error.invalidMnemonic } let url = makeAccountURL() @@ -201,7 +201,7 @@ public final class KeyStore { /// - password: account password /// - newPassword: password to use for exported key /// - Returns: encrypted JSON key - public func export(wallet: Wallet, password: String, newPassword: String) throws -> Data { + public func export(wallet: Wallet, password: String, newPassword: String, encryption: StoredKeyEncryption = .aes128Ctr) throws -> Data { var privateKeyData = try exportPrivateKey(wallet: wallet, password: password) defer { privateKeyData.resetBytes(in: 0 ..< privateKeyData.count) @@ -211,12 +211,12 @@ public final class KeyStore { throw Error.accountNotFound } - if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWallet(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin) { + if let mnemonic = checkMnemonic(privateKeyData), let newKey = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } return json - } else if let newKey = StoredKey.importPrivateKey(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin) { + } else if let newKey = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "", password: Data(newPassword.utf8), coin: coin, encryption: encryption) { guard let json = newKey.exportJSON() else { throw Error.invalidKey } @@ -269,11 +269,11 @@ public final class KeyStore { /// - wallet: wallet to update /// - password: current password /// - newName: new name - public func update(wallet: Wallet, password: String, newName: String) throws { - try update(wallet: wallet, password: password, newPassword: password, newName: newName) + public func update(wallet: Wallet, password: String, newName: String, encryption: StoredKeyEncryption = .aes128Ctr) throws { + try update(wallet: wallet, password: password, newPassword: password, newName: newName, encryption: encryption) } - private func update(wallet: Wallet, password: String, newPassword: String, newName: String) throws { + private func update(wallet: Wallet, password: String, newPassword: String, newName: String, encryption: StoredKeyEncryption = .aes128Ctr) throws { guard let index = wallets.firstIndex(of: wallet) else { fatalError("Missing wallet") } @@ -291,10 +291,10 @@ public final class KeyStore { } if let mnemonic = checkMnemonic(privateKeyData), - let key = StoredKey.importHDWallet(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + let key = StoredKey.importHDWalletWithEncryption(mnemonic: mnemonic, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { wallets[index].key = key - } else if let key = StoredKey.importPrivateKey( - privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0]) { + } else if let key = StoredKey.importPrivateKeyWithEncryption( + privateKey: privateKeyData, name: newName, password: Data(newPassword.utf8), coin: coins[0], encryption: encryption) { wallets[index].key = key } else { throw Error.invalidKey diff --git a/swift/Tests/Keystore/KeyStoreTests.swift b/swift/Tests/Keystore/KeyStoreTests.swift index 3ae5529e79e..62d907ccfaf 100755 --- a/swift/Tests/Keystore/KeyStoreTests.swift +++ b/swift/Tests/Keystore/KeyStoreTests.swift @@ -183,6 +183,20 @@ class KeyStoreTests: XCTestCase { XCTAssertNotNil(storedData) XCTAssertNotNil(PrivateKey(data: storedData!)) } + + func testImportPrivateKeyAES256() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let privateKeyData = Data(hexString: "9cdb5cab19aec3bd0fcd614c5f185e7a1d97634d4225730eba22497dc89a716c")! + let key = StoredKey.importPrivateKeyWithEncryption(privateKey: privateKeyData, name: "name", password: Data("password".utf8), coin: .ethereum, encryption: StoredKeyEncryption.aes256Ctr)! + let json = key.exportJSON()! + + let wallet = try keyStore.import(json: json, name: "name", password: "password", newPassword: "newPassword", coins: [.ethereum]) + let storedData = wallet.key.decryptPrivateKey(password: Data("newPassword".utf8)) + + XCTAssertNotNil(keyStore.keyWallet) + XCTAssertNotNil(storedData) + XCTAssertNotNil(PrivateKey(data: storedData!)) + } func testImportPrivateKey() throws { let keyStore = try KeyStore(keyDirectory: keyDirectory) @@ -208,6 +222,16 @@ class KeyStoreTests: XCTestCase { XCTAssertEqual(wallet.accounts.count, 1) XCTAssertNotNil(keyStore.hdWallet) } + + func testImportWalletAES256() throws { + let keyStore = try KeyStore(keyDirectory: keyDirectory) + let wallet = try keyStore.import(mnemonic: mnemonic, name: "name", encryptPassword: "newPassword", coins: [.ethereum], encryption: .aes256Ctr) + let storedData = wallet.key.decryptMnemonic(password: Data("newPassword".utf8)) + + XCTAssertNotNil(storedData) + XCTAssertEqual(wallet.accounts.count, 1) + XCTAssertNotNil(keyStore.hdWallet) + } func testImportJSON() throws { diff --git a/tests/chains/XRP/TWAnySignerTests.cpp b/tests/chains/XRP/TWAnySignerTests.cpp index 47fcbd9d232..184c8a21137 100644 --- a/tests/chains/XRP/TWAnySignerTests.cpp +++ b/tests/chains/XRP/TWAnySignerTests.cpp @@ -84,25 +84,20 @@ TEST(TWAnySignerRipple, SignTrustSetPayment) { TEST(TWAnySignerRipple, SignTokenPayment0) { // https://testnet.xrpl.org/transactions/8F7820892294598B58CFA2E1101D15ED98C179B25A2BA6DAEB4F5B727CB00D4E - for (auto value : std::vector({"10", "10e0", "10.0", "10.0e0", "1e1", ".1e2", "0.1e2", "100e-1", "10000000000000000e-15", "0.0000000000000001e17"})) { - auto key = parse_hex("4ba5fd2ebf0f5d7e579b3c354c263ebb39cda4093845125786a280301af14e21"); - Proto::SigningInput input; - - input.mutable_op_payment()->mutable_currency_amount()->set_currency("USD"); - input.mutable_op_payment()->mutable_currency_amount()->set_value("10"); - input.mutable_op_payment()->mutable_currency_amount()->set_issuer("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"); - input.set_fee(10); - input.set_sequence(32268645); - input.set_last_ledger_sequence(32268666); - input.set_account("raPAA61ca99bdwNiZs5JJukR5rvkHWvkBX"); - input.mutable_op_payment()->set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); - input.set_private_key(key.data(), key.size()); - - Proto::SigningOutput output; - ANY_SIGN(input, TWCoinTypeXRP); - - EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec6165201b01ec617a61d4c38d7ea4c6800000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a7321020652a477b0cca8b74d6e68a6a386a836b226101617481b95180eaffbe841b3227446304402203e925caeb05006afb135254e9ae4e46de2019db6c6f68614ef969885063a777602206af110fc29775256fcad8b14974c6a838141d82193192d3b57324fe1079afa1781143b2fa4f36553e5b7a4f54ff9e6883e44b4b0dbb383148132e4e20aecf29090ac428a9c43f230a829220d"); - } + auto key = parse_hex("4ba5fd2ebf0f5d7e579b3c354c263ebb39cda4093845125786a280301af14e21"); + Proto::SigningInput input; + input.mutable_op_payment()->mutable_currency_amount()->set_currency("USD"); + input.mutable_op_payment()->mutable_currency_amount()->set_value("10"); + input.mutable_op_payment()->mutable_currency_amount()->set_issuer("rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"); + input.set_fee(10); + input.set_sequence(32268645); + input.set_last_ledger_sequence(32268666); + input.set_account("raPAA61ca99bdwNiZs5JJukR5rvkHWvkBX"); + input.mutable_op_payment()->set_destination("rU893viamSnsfP3zjzM2KPxjqZjXSXK6VF"); + input.set_private_key(key.data(), key.size()); + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeXRP); + EXPECT_EQ(hex(output.encoded()), "12000022000000002401ec6165201b01ec617a61d4c38d7ea4c6800000000000000000000000000055534400000000004b4e9c06f24296074f7bc48f92a97916c6dc5ea968400000000000000a7321020652a477b0cca8b74d6e68a6a386a836b226101617481b95180eaffbe841b3227446304402203e925caeb05006afb135254e9ae4e46de2019db6c6f68614ef969885063a777602206af110fc29775256fcad8b14974c6a838141d82193192d3b57324fe1079afa1781143b2fa4f36553e5b7a4f54ff9e6883e44b4b0dbb383148132e4e20aecf29090ac428a9c43f230a829220d"); } TEST(TWAnySignerRipple, SignTokenPayment1) { diff --git a/tests/common/Keystore/StoredKeyTests.cpp b/tests/common/Keystore/StoredKeyTests.cpp index 51cc959968d..677ce21a0e2 100644 --- a/tests/common/Keystore/StoredKeyTests.cpp +++ b/tests/common/Keystore/StoredKeyTests.cpp @@ -84,6 +84,22 @@ TEST(StoredKey, CreateWithMnemonicAddDefaultAddress) { EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); } +TEST(StoredKey, CreateWithMnemonicAddDefaultAddressAes256) { + auto key = StoredKey::createWithMnemonicAddDefaultAddress("name", gPassword, gMnemonic, coinTypeBc, TWStoredKeyEncryptionAes256Ctr); + EXPECT_EQ(key.type, StoredKeyType::mnemonicPhrase); + auto header = key.payload; + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + const Data& mnemo2Data = key.payload.decrypt(gPassword); + + EXPECT_EQ(string(mnemo2Data.begin(), mnemo2Data.end()), string(gMnemonic)); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.accounts[0].publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.accounts[0].extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), "d2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f"); +} + TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey); @@ -99,6 +115,23 @@ TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddress) { EXPECT_EQ(json["version"], 3); } +TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressAes256) { + const auto privateKey = parse_hex("3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"); + auto key = StoredKey::createWithPrivateKeyAddDefaultAddress("name", gPassword, coinTypeBc, privateKey, TWStoredKeyEncryptionAes256Ctr); + auto header = key.payload; + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + EXPECT_EQ(key.type, StoredKeyType::privateKey); + EXPECT_EQ(key.accounts.size(), 1ul); + EXPECT_EQ(key.accounts[0].coin, coinTypeBc); + EXPECT_EQ(key.accounts[0].address, "bc1q375sq4kl2nv0mlmup3vm8znn4eqwu7mt6hkwhr"); + EXPECT_EQ(hex(key.privateKey(coinTypeBc, gPassword).bytes), hex(privateKey)); + + const auto json = key.json(); + EXPECT_EQ(json["name"], "name"); + EXPECT_EQ(json["type"], "private-key"); + EXPECT_EQ(json["version"], 3); +} + TEST(StoredKey, CreateWithPrivateKeyAddDefaultAddressInvalid) { try { const auto privateKeyInvalid = parse_hex("0001020304"); @@ -345,7 +378,7 @@ TEST(StoredKey, ReadWallet) { const auto header = key.payload; - EXPECT_EQ(header.params.cipher, "aes-128-ctr"); + EXPECT_EQ(header.params.cipher(), "aes-128-ctr"); EXPECT_EQ(hex(header.encrypted), "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"); EXPECT_EQ(hex(header._mac), "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"); EXPECT_EQ(hex(header.params.cipherParams.iv), "83dbcc02d8ccb40e466191a123791e0e"); @@ -403,6 +436,22 @@ TEST(StoredKey, CreateAccounts) { EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); } +TEST(StoredKey, CreateAccountsAes256) { + string mnemonicPhrase = "team engine square letter hero song dizzy scrub tornado fabric divert saddle"; + auto key = StoredKey::createWithMnemonic("name", gPassword, mnemonicPhrase, TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes256Ctr); + auto header = key.payload; + const auto wallet = key.wallet(gPassword); + + EXPECT_EQ(header.params.cipher(), "aes-256-ctr"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->address, "0x494f60cb6Ac2c8F5E1393aD9FdBdF4Ad589507F7"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->publicKey, "04cc32a479080d83fdcf69966713f0aad1bc1dc3ecf873b034894e84259841bc1c9b122717803e68905220ff54952d3f5ea2ab2698ca31f843addf94ae73fae9fd"); + EXPECT_EQ(key.account(TWCoinTypeEthereum, &wallet)->extendedPublicKey, ""); + + EXPECT_EQ(key.account(coinTypeBc, &wallet)->address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->publicKey, "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006"); + EXPECT_EQ(key.account(coinTypeBc, &wallet)->extendedPublicKey, "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn"); +} + TEST(StoredKey, DecodingEthereumAddress) { const auto key = StoredKey::load(testDataPath("key.json")); diff --git a/tests/interface/TWStoredKeyTests.cpp b/tests/interface/TWStoredKeyTests.cpp index 6cfa96c04c8..99f2df1ab54 100644 --- a/tests/interface/TWStoredKeyTests.cpp +++ b/tests/interface/TWStoredKeyTests.cpp @@ -25,19 +25,19 @@ using namespace std; /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct std::shared_ptr createAStoredKey(TWCoinType coin, TWData* password) { +struct std::shared_ptr createAStoredKey(TWCoinType coin, TWData* password, TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); - const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWallet(mnemonic.get(), name.get(), password, coin)); + const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonic.get(), name.get(), password, coin, encryption)); return key; } /// Return a StoredKey instance that can be used for further tests. Needs to be deleted at the end. -struct std::shared_ptr createDefaultStoredKey() { +struct std::shared_ptr createDefaultStoredKey(TWStoredKeyEncryption encryption = TWStoredKeyEncryptionAes128Ctr) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - return createAStoredKey(TWCoinTypeBitcoin, password.get()); + return createAStoredKey(TWCoinTypeBitcoin, password.get(), encryption); } TEST(TWStoredKey, loadPBKDF2Key) { @@ -57,7 +57,7 @@ TEST(TWStoredKey, createWallet) { const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); - const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevel(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault)); + const auto key = WRAP(TWStoredKey, TWStoredKeyCreateLevelAndEncryption(name.get(), password.get(), TWStoredKeyEncryptionLevelDefault, TWStoredKeyEncryptionAes128Ctr)); const auto name2 = WRAPS(TWStoredKeyName(key.get())); EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); const auto mnemonic = WRAPS(TWStoredKeyDecryptMnemonic(key.get(), password.get())); @@ -82,6 +82,23 @@ TEST(TWStoredKey, importPrivateKey) { TWPrivateKeyDelete(privateKey3); } +TEST(TWStoredKey, importPrivateKeyAes256) { + const auto privateKeyHex = "3a1076bf45ab87712ad64ccb3b10217737f7faacbf2872e88fdd9a537d8fe266"; + const auto privateKey = WRAPD(TWDataCreateWithHexString(WRAPS(TWStringCreateWithUTF8Bytes(privateKeyHex)).get())); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportPrivateKeyWithEncryption(privateKey.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + const auto privateKey2 = WRAPD(TWStoredKeyDecryptPrivateKey(key.get(), password.get())); + EXPECT_EQ(hex(data(TWDataBytes(privateKey2.get()), TWDataSize(privateKey2.get()))), privateKeyHex); + + const auto privateKey3 = TWStoredKeyPrivateKey(key.get(), coin, password.get()); + const auto pkData3 = WRAPD(TWPrivateKeyData(privateKey3)); + EXPECT_EQ(hex(data(TWDataBytes(pkData3.get()), TWDataSize(pkData3.get()))), privateKeyHex); + TWPrivateKeyDelete(privateKey3); +} + TEST(TWStoredKey, importHDWallet) { const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); @@ -97,6 +114,21 @@ TEST(TWStoredKey, importHDWallet) { EXPECT_EQ(nokey.get(), nullptr); } +TEST(TWStoredKey, importHDWalletAES256) { + const auto mnemonic = WRAPS(TWStringCreateWithUTF8Bytes("team engine square letter hero song dizzy scrub tornado fabric divert saddle")); + const auto name = WRAPS(TWStringCreateWithUTF8Bytes("name")); + const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); + const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); + const auto coin = TWCoinTypeBitcoin; + const auto key = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonic.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + EXPECT_TRUE(TWStoredKeyIsMnemonic(key.get())); + + // invalid mnemonic + const auto mnemonicInvalid = WRAPS(TWStringCreateWithUTF8Bytes("_THIS_IS_AN_INVALID_MNEMONIC_")); + const auto nokey = WRAP(TWStoredKey, TWStoredKeyImportHDWalletWithEncryption(mnemonicInvalid.get(), name.get(), password.get(), coin, TWStoredKeyEncryptionAes256Ctr)); + EXPECT_EQ(nokey.get(), nullptr); +} + TEST(TWStoredKey, addressAddRemove) { const auto passwordString = WRAPS(TWStringCreateWithUTF8Bytes("password")); const auto password = WRAPD(TWDataCreateWithBytes(reinterpret_cast(TWStringUTF8Bytes(passwordString.get())), TWStringSize(passwordString.get()))); @@ -184,6 +216,30 @@ TEST(TWStoredKey, exportJSON) { EXPECT_EQ(TWDataGet(json.get(), 0), '{'); } +TEST(TWStoredKey, storeAndImportJSONAES256) { + const auto key = createDefaultStoredKey(TWStoredKeyEncryptionAes256Ctr); + const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); + const auto outFileNameStr = WRAPS(TWStringCreateWithUTF8Bytes(outFileName.c_str())); + EXPECT_TRUE(TWStoredKeyStore(key.get(), outFileNameStr.get())); + + // read contents of file + ifstream ifs(outFileName); + // get length of file: + ifs.seekg (0, ifs.end); + auto length = ifs.tellg(); + ifs.seekg (0, ifs.beg); + EXPECT_TRUE(length > 20); + + Data json(length); + size_t idx = 0; + // read the slow way, ifs.read gave some false warnings with codacy + while (!ifs.eof() && idx < static_cast(length)) { char c = ifs.get(); json[idx++] = (uint8_t)c; } + + const auto key2 = WRAP(TWStoredKey, TWStoredKeyImportJSON(WRAPD(TWDataCreateWithData(&json)).get())); + const auto name2 = WRAPS(TWStoredKeyName(key2.get())); + EXPECT_EQ(string(TWStringUTF8Bytes(name2.get())), "name"); +} + TEST(TWStoredKey, storeAndImportJSON) { const auto key = createDefaultStoredKey(); const auto outFileName = string(getTestTempDir() + "/TWStoredKey_store.json"); diff --git a/wasm/src/keystore/default-impl.ts b/wasm/src/keystore/default-impl.ts index 91da9b40f16..528b63e5554 100644 --- a/wasm/src/keystore/default-impl.ts +++ b/wasm/src/keystore/default-impl.ts @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -import { WalletCore, CoinType, PrivateKey, StoredKey } from "../wallet-core"; +import {WalletCore, CoinType, PrivateKey, StoredKey, StoredKeyEncryption} from "../wallet-core"; import * as Types from "./types"; export class Default implements Types.IKeyStore { @@ -53,16 +53,17 @@ export class Default implements Types.IKeyStore { mnemonic: string, name: string, password: string, - coins: CoinType[] + coins: CoinType[], + encryption: StoredKeyEncryption ): Promise { return new Promise((resolve, reject) => { - const { Mnemonic, StoredKey, HDWallet } = this.core; + const { Mnemonic, StoredKey, HDWallet, StoredKeyEncryption } = this.core; if (!Mnemonic.isValid(mnemonic)) { throw Types.Error.InvalidMnemonic; } let pass = Buffer.from(password); - let storedKey = StoredKey.importHDWallet(mnemonic, name, pass, coins[0]); + let storedKey = StoredKey.importHDWalletWithEncryption(mnemonic, name, pass, coins[0], encryption); let hdWallet = HDWallet.createWithMnemonic(mnemonic, ""); coins.forEach((coin) => { storedKey.accountForCoin(coin, hdWallet); @@ -82,10 +83,11 @@ export class Default implements Types.IKeyStore { key: Uint8Array, name: string, password: string, - coin: CoinType + coin: CoinType, + encryption: StoredKeyEncryption ): Promise { return new Promise((resolve, reject) => { - const { StoredKey, PrivateKey, Curve } = this.core; + const { StoredKey, PrivateKey, Curve, StoredKeyEncryption } = this.core; // FIXME: get curve from coin if ( @@ -95,7 +97,7 @@ export class Default implements Types.IKeyStore { throw Types.Error.InvalidKey; } let pass = Buffer.from(password); - let storedKey = StoredKey.importPrivateKey(key, name, pass, coin); + let storedKey = StoredKey.importPrivateKeyWithEncryption(key, name, pass, coin, encryption); let wallet = this.mapWallet(storedKey); storedKey.delete(); this.importWallet(wallet) diff --git a/wasm/src/keystore/types.ts b/wasm/src/keystore/types.ts index cc0ce8a21fd..aea0ee3b4ae 100644 --- a/wasm/src/keystore/types.ts +++ b/wasm/src/keystore/types.ts @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -import { CoinType, PrivateKey } from "../wallet-core"; +import { CoinType, PrivateKey, StoredKeyEncryption } from "../wallet-core"; export enum WalletType { Mnemonic = "mnemonic", @@ -54,7 +54,8 @@ export interface IKeyStore { mnemonic: string, name: string, password: string, - coins: CoinType[] + coins: CoinType[], + encryption: StoredKeyEncryption ): Promise; // Import a wallet by private key, name and password @@ -62,7 +63,8 @@ export interface IKeyStore { key: Uint8Array, name: string, password: string, - coin: CoinType + coin: CoinType, + encryption: StoredKeyEncryption ): Promise; // Import a Wallet object directly diff --git a/wasm/tests/KeyStore+extension.test.ts b/wasm/tests/KeyStore+extension.test.ts index fe22c5b7091..cf876dcc448 100644 --- a/wasm/tests/KeyStore+extension.test.ts +++ b/wasm/tests/KeyStore+extension.test.ts @@ -11,7 +11,7 @@ import { ChromeStorageMock } from "./mock"; describe("KeyStore", async () => { it("test ExtensionStorage", async () => { - const { CoinType, HexCoding } = globalThis.core; + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; const mnemonic = globalThis.mnemonic as string; const password = globalThis.password as string; @@ -24,7 +24,7 @@ describe("KeyStore", async () => { var wallet = await keystore.import(mnemonic, "Coolw", password, [ CoinType.bitcoin, - ]); + ], StoredKeyEncryption.aes128Ctr); assert.equal(wallet.name, "Coolw"); assert.equal(wallet.type, "mnemonic"); @@ -65,4 +65,60 @@ describe("KeyStore", async () => { keystore.delete(w.id, password); }); }).timeout(10000); + + it("test ExtensionStorage AES256", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + + const walletIdsKey = "all-wallet-ids"; + const storage = new KeyStore.ExtensionStorage( + walletIdsKey, + new ChromeStorageMock() + ); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes256Ctr); + + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); }); diff --git a/wasm/tests/KeyStore+fs.test.ts b/wasm/tests/KeyStore+fs.test.ts index 743a9a8e4b4..cdadb92940a 100644 --- a/wasm/tests/KeyStore+fs.test.ts +++ b/wasm/tests/KeyStore+fs.test.ts @@ -11,7 +11,7 @@ import { KeyStore } from "../dist"; describe("KeyStore", async () => { it("test FileSystemStorage", async () => { - const { CoinType, HexCoding } = globalThis.core; + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; const mnemonic = globalThis.mnemonic as string; const password = globalThis.password as string; const testDir = "/tmp/wasm-test"; @@ -23,7 +23,7 @@ describe("KeyStore", async () => { var wallet = await keystore.import(mnemonic, "Coolw", password, [ CoinType.bitcoin, - ]); + ], StoredKeyEncryption.aes128Ctr); const stats = fs.statSync(storage.getFilename(wallet.id)); assert.isTrue(stats.isFile()); @@ -67,4 +67,62 @@ describe("KeyStore", async () => { keystore.delete(w.id, password); }); }).timeout(10000); + + it("test FileSystemStorage AES256", async () => { + const { CoinType, HexCoding, StoredKeyEncryption } = globalThis.core; + const mnemonic = globalThis.mnemonic as string; + const password = globalThis.password as string; + const testDir = "/tmp/wasm-test"; + + fs.mkdirSync(testDir, { recursive: true }); + + const storage = new KeyStore.FileSystemStorage(testDir); + const keystore = new KeyStore.Default(globalThis.core, storage); + + var wallet = await keystore.import(mnemonic, "Coolw", password, [ + CoinType.bitcoin, + ], StoredKeyEncryption.aes256Ctr); + const stats = fs.statSync(storage.getFilename(wallet.id)); + + assert.isTrue(stats.isFile()); + assert.isTrue(stats.size > 0); + assert.equal(wallet.name, "Coolw"); + assert.equal(wallet.type, "mnemonic"); + assert.equal(wallet.version, 3); + + const account = wallet.activeAccounts[0]; + const key = await keystore.getKey(wallet.id, password, account); + + assert.equal( + HexCoding.encode(key.data()), + "0xd2568511baea8dc347f14c4e0479eb8ebe29eb5f664ed796e755896250ffd11f" + ); + assert.equal(account.address, "bc1qturc268v0f2srjh4r2zu4t6zk4gdutqd5a6zny"); + assert.equal( + account.extendedPublicKey, + "zpub6qbsWdbcKW9sC6shTKK4VEhfWvDCoWpfLnnVfYKHLHt31wKYUwH3aFDz4WLjZvjHZ5W4qVEyk37cRwzTbfrrT1Gnu8SgXawASnkdQ994atn" + ); + assert.equal( + account.publicKey, + "02df6fc590ab3101bbe0bb5765cbaeab9b5dcfe09ac9315d707047cbd13bc7e006" + ); + + wallet = await keystore.addAccounts(wallet.id, password, [ + CoinType.ethereum, + CoinType.binance, + ]); + + assert.equal(wallet.activeAccounts.length, 3); + assert.isTrue(await keystore.hasWallet(wallet.id)); + assert.isFalse(await keystore.hasWallet("invalid-id")); + + const exported = await keystore.export(wallet.id, password); + assert.equal(exported, mnemonic); + + const wallets = await keystore.loadAll(); + + await wallets.forEach((w) => { + keystore.delete(w.id, password); + }); + }).timeout(10000); });