diff --git a/src/keystore.cpp b/src/keystore.cpp index a38c2f88e..37bf7a1c8 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -1,104 +1,130 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "keystore.h" #include "key.h" #include "pubkey.h" #include "util.h" bool CKeyStore::AddKey(const CKey &key) { return AddKeyPubKey(key, key.GetPubKey()); } bool CBasicKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { CKey key; if (!GetKey(address, key)) { LOCK(cs_KeyStore); WatchKeyMap::const_iterator it = mapWatchKeys.find(address); if (it != mapWatchKeys.end()) { vchPubKeyOut = it->second; return true; } return false; } vchPubKeyOut = key.GetPubKey(); return true; } bool CBasicKeyStore::AddKeyPubKey(const CKey &key, const CPubKey &pubkey) { LOCK(cs_KeyStore); mapKeys[pubkey.GetID()] = key; return true; } +bool CBasicKeyStore::HaveKey(const CKeyID &address) const { + LOCK(cs_KeyStore); + return mapKeys.count(address) > 0; +} + +std::set CBasicKeyStore::GetKeys() const { + LOCK(cs_KeyStore); + std::set set_address; + for (const auto &mi : mapKeys) { + set_address.insert(mi.first); + } + return set_address; +} + +bool CBasicKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const { + { + LOCK(cs_KeyStore); + KeyMap::const_iterator mi = mapKeys.find(address); + if (mi != mapKeys.end()) { + keyOut = mi->second; + return true; + } + } + return false; +} + bool CBasicKeyStore::AddCScript(const CScript &redeemScript) { if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) return error("CBasicKeyStore::AddCScript(): redeemScripts > %i bytes " "are invalid", MAX_SCRIPT_ELEMENT_SIZE); LOCK(cs_KeyStore); mapScripts[CScriptID(redeemScript)] = redeemScript; return true; } bool CBasicKeyStore::HaveCScript(const CScriptID &hash) const { LOCK(cs_KeyStore); return mapScripts.count(hash) > 0; } bool CBasicKeyStore::GetCScript(const CScriptID &hash, CScript &redeemScriptOut) const { LOCK(cs_KeyStore); ScriptMap::const_iterator mi = mapScripts.find(hash); if (mi != mapScripts.end()) { redeemScriptOut = (*mi).second; return true; } return false; } static bool ExtractPubKey(const CScript &dest, CPubKey &pubKeyOut) { // TODO: Use Solver to extract this? CScript::const_iterator pc = dest.begin(); opcodetype opcode; std::vector vch; if (!dest.GetOp(pc, opcode, vch) || vch.size() < 33 || vch.size() > 65) return false; pubKeyOut = CPubKey(vch); if (!pubKeyOut.IsFullyValid()) return false; if (!dest.GetOp(pc, opcode, vch) || opcode != OP_CHECKSIG || dest.GetOp(pc, opcode, vch)) return false; return true; } bool CBasicKeyStore::AddWatchOnly(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.insert(dest); CPubKey pubKey; if (ExtractPubKey(dest, pubKey)) mapWatchKeys[pubKey.GetID()] = pubKey; return true; } bool CBasicKeyStore::RemoveWatchOnly(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.erase(dest); CPubKey pubKey; if (ExtractPubKey(dest, pubKey)) mapWatchKeys.erase(pubKey.GetID()); return true; } bool CBasicKeyStore::HaveWatchOnly(const CScript &dest) const { LOCK(cs_KeyStore); return setWatchOnly.count(dest) > 0; } bool CBasicKeyStore::HaveWatchOnly() const { LOCK(cs_KeyStore); return (!setWatchOnly.empty()); } diff --git a/src/keystore.h b/src/keystore.h index 8e793260e..ce4aa1d9b 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -1,109 +1,85 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2015 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_KEYSTORE_H #define BITCOIN_KEYSTORE_H #include "key.h" #include "pubkey.h" #include "script/script.h" #include "script/standard.h" #include "sync.h" #include /** A virtual base class for key stores */ class CKeyStore { protected: mutable CCriticalSection cs_KeyStore; public: virtual ~CKeyStore() {} //! Add a key to the store. virtual bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) = 0; virtual bool AddKey(const CKey &key); //! Check whether a key corresponding to a given address is present in the //! store. virtual bool HaveKey(const CKeyID &address) const = 0; virtual bool GetKey(const CKeyID &address, CKey &keyOut) const = 0; virtual std::set GetKeys() const = 0; virtual bool GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const = 0; //! Support for BIP 0013 : see //! https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki virtual bool AddCScript(const CScript &redeemScript) = 0; virtual bool HaveCScript(const CScriptID &hash) const = 0; virtual bool GetCScript(const CScriptID &hash, CScript &redeemScriptOut) const = 0; //! Support for Watch-only addresses virtual bool AddWatchOnly(const CScript &dest) = 0; virtual bool RemoveWatchOnly(const CScript &dest) = 0; virtual bool HaveWatchOnly(const CScript &dest) const = 0; virtual bool HaveWatchOnly() const = 0; }; typedef std::map KeyMap; typedef std::map WatchKeyMap; typedef std::map ScriptMap; typedef std::set WatchOnlySet; /** Basic key store, that keeps keys in an address->secret map */ class CBasicKeyStore : public CKeyStore { protected: KeyMap mapKeys; WatchKeyMap mapWatchKeys; ScriptMap mapScripts; WatchOnlySet setWatchOnly; public: bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override; bool GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const override; - bool HaveKey(const CKeyID &address) const override { - bool result; - { - LOCK(cs_KeyStore); - result = (mapKeys.count(address) > 0); - } - return result; - } - std::set GetKeys() const override { - LOCK(cs_KeyStore); - std::set set_address; - for (const auto &mi : mapKeys) { - set_address.insert(mi.first); - } - return set_address; - } - bool GetKey(const CKeyID &address, CKey &keyOut) const override { - { - LOCK(cs_KeyStore); - KeyMap::const_iterator mi = mapKeys.find(address); - if (mi != mapKeys.end()) { - keyOut = mi->second; - return true; - } - } - return false; - } + bool HaveKey(const CKeyID &address) const override; + std::set GetKeys() const override; + bool GetKey(const CKeyID &address, CKey &keyOut) const override; virtual bool AddCScript(const CScript &redeemScript) override; virtual bool HaveCScript(const CScriptID &hash) const override; virtual bool GetCScript(const CScriptID &hash, CScript &redeemScriptOut) const override; virtual bool AddWatchOnly(const CScript &dest) override; virtual bool RemoveWatchOnly(const CScript &dest) override; virtual bool HaveWatchOnly(const CScript &dest) const override; virtual bool HaveWatchOnly() const override; }; typedef std::vector> CKeyingMaterial; typedef std::map>> CryptedKeyMap; #endif // BITCOIN_KEYSTORE_H diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index a691031ee..3fb09127f 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -1,280 +1,312 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "crypter.h" #include "crypto/aes.h" #include "crypto/sha512.h" #include "script/script.h" #include "script/standard.h" #include "util.h" #include #include int CCrypter::BytesToKeySHA512AES(const std::vector &chSalt, const SecureString &strKeyData, int count, uint8_t *key, uint8_t *iv) const { // This mimics the behavior of openssl's EVP_BytesToKey with an aes256cbc // cipher and sha512 message digest. Because sha512's output size (64b) is // greater than the aes256 block size (16b) + aes256 key size (32b), there's // no need to process more than once (D_0). if (!count || !key || !iv) return 0; uint8_t buf[CSHA512::OUTPUT_SIZE]; CSHA512 di; di.Write((const uint8_t *)strKeyData.c_str(), strKeyData.size()); if (chSalt.size()) di.Write(&chSalt[0], chSalt.size()); di.Finalize(buf); for (int i = 0; i != count - 1; i++) di.Reset().Write(buf, sizeof(buf)).Finalize(buf); memcpy(key, buf, WALLET_CRYPTO_KEY_SIZE); memcpy(iv, buf + WALLET_CRYPTO_KEY_SIZE, WALLET_CRYPTO_IV_SIZE); memory_cleanse(buf, sizeof(buf)); return WALLET_CRYPTO_KEY_SIZE; } bool CCrypter::SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector &chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod) { if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE) return false; int i = 0; if (nDerivationMethod == 0) i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, vchKey.data(), vchIV.data()); if (i != (int)WALLET_CRYPTO_KEY_SIZE) { memory_cleanse(vchKey.data(), vchKey.size()); memory_cleanse(vchIV.data(), vchIV.size()); return false; } fKeySet = true; return true; } bool CCrypter::SetKey(const CKeyingMaterial &chNewKey, const std::vector &chNewIV) { if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE) return false; memcpy(vchKey.data(), chNewKey.data(), chNewKey.size()); memcpy(vchIV.data(), chNewIV.data(), chNewIV.size()); fKeySet = true; return true; } bool CCrypter::Encrypt(const CKeyingMaterial &vchPlaintext, std::vector &vchCiphertext) const { if (!fKeySet) return false; // max ciphertext len for a n bytes of plaintext is // n + AES_BLOCKSIZE bytes vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE); AES256CBCEncrypt enc(vchKey.data(), vchIV.data(), true); size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), &vchCiphertext[0]); if (nLen < vchPlaintext.size()) return false; vchCiphertext.resize(nLen); return true; } bool CCrypter::Decrypt(const std::vector &vchCiphertext, CKeyingMaterial &vchPlaintext) const { if (!fKeySet) return false; // plaintext will always be equal to or lesser than length of ciphertext int nLen = vchCiphertext.size(); vchPlaintext.resize(nLen); AES256CBCDecrypt dec(vchKey.data(), vchIV.data(), true); nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]); if (nLen == 0) return false; vchPlaintext.resize(nLen); return true; } static bool EncryptSecret(const CKeyingMaterial &vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256 &nIV, std::vector &vchCiphertext) { CCrypter cKeyCrypter; std::vector chIV(WALLET_CRYPTO_IV_SIZE); memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE); if (!cKeyCrypter.SetKey(vMasterKey, chIV)) return false; return cKeyCrypter.Encrypt(*((const CKeyingMaterial *)&vchPlaintext), vchCiphertext); } static bool DecryptSecret(const CKeyingMaterial &vMasterKey, const std::vector &vchCiphertext, const uint256 &nIV, CKeyingMaterial &vchPlaintext) { CCrypter cKeyCrypter; std::vector chIV(WALLET_CRYPTO_IV_SIZE); memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE); if (!cKeyCrypter.SetKey(vMasterKey, chIV)) return false; return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial *)&vchPlaintext)); } static bool DecryptKey(const CKeyingMaterial &vMasterKey, const std::vector &vchCryptedSecret, const CPubKey &vchPubKey, CKey &key) { CKeyingMaterial vchSecret; if (!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret)) return false; if (vchSecret.size() != 32) return false; key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed()); return key.VerifyPubKey(vchPubKey); } bool CCryptoKeyStore::SetCrypted() { LOCK(cs_KeyStore); if (fUseCrypto) return true; if (!mapKeys.empty()) return false; fUseCrypto = true; return true; } +bool CCryptoKeyStore::IsLocked() const { + if (!IsCrypted()) { + return false; + } + bool result; + { + LOCK(cs_KeyStore); + result = vMasterKey.empty(); + } + return result; +} + bool CCryptoKeyStore::Lock() { if (!SetCrypted()) return false; { LOCK(cs_KeyStore); vMasterKey.clear(); } NotifyStatusChanged(this); return true; } bool CCryptoKeyStore::Unlock(const CKeyingMaterial &vMasterKeyIn) { { LOCK(cs_KeyStore); if (!SetCrypted()) return false; bool keyPass = false; bool keyFail = false; CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { const CPubKey &vchPubKey = (*mi).second.first; const std::vector &vchCryptedSecret = (*mi).second.second; CKey key; if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) { keyFail = true; break; } keyPass = true; if (fDecryptionThoroughlyChecked) break; } if (keyPass && keyFail) { LogPrintf("The wallet is probably corrupted: Some keys decrypt but " "not all.\n"); assert(false); } if (keyFail || !keyPass) return false; vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; } NotifyStatusChanged(this); return true; } bool CCryptoKeyStore::AddKeyPubKey(const CKey &key, const CPubKey &pubkey) { { LOCK(cs_KeyStore); if (!IsCrypted()) return CBasicKeyStore::AddKeyPubKey(key, pubkey); if (IsLocked()) return false; std::vector vchCryptedSecret; CKeyingMaterial vchSecret(key.begin(), key.end()); if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) return false; if (!AddCryptedKey(pubkey, vchCryptedSecret)) return false; } return true; } bool CCryptoKeyStore::AddCryptedKey( const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) { { LOCK(cs_KeyStore); if (!SetCrypted()) return false; mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); } return true; } +bool CCryptoKeyStore::HaveKey(const CKeyID &address) const { + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::HaveKey(address); + } + return mapCryptedKeys.count(address) > 0; +} + bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey &keyOut) const { { LOCK(cs_KeyStore); if (!IsCrypted()) return CBasicKeyStore::GetKey(address, keyOut); CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { const CPubKey &vchPubKey = (*mi).second.first; const std::vector &vchCryptedSecret = (*mi).second.second; return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); } } return false; } bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { { LOCK(cs_KeyStore); if (!IsCrypted()) return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { vchPubKeyOut = (*mi).second.first; return true; } // Check for watch-only pubkeys return CBasicKeyStore::GetPubKey(address, vchPubKeyOut); } return false; } +std::set CCryptoKeyStore::GetKeys() const { + LOCK(cs_KeyStore); + if (!IsCrypted()) { + return CBasicKeyStore::GetKeys(); + } + std::set set_address; + for (const auto &mi : mapCryptedKeys) { + set_address.insert(mi.first); + } + return set_address; +} + bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial &vMasterKeyIn) { { LOCK(cs_KeyStore); if (!mapCryptedKeys.empty() || IsCrypted()) return false; fUseCrypto = true; for (KeyMap::value_type &mKey : mapKeys) { const CKey &key = mKey.second; CPubKey vchPubKey = key.GetPubKey(); CKeyingMaterial vchSecret(key.begin(), key.end()); std::vector vchCryptedSecret; if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) return false; if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) return false; } mapKeys.clear(); } return true; } diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 066ba42b4..76efa6e9a 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -1,189 +1,162 @@ // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_CRYPTER_H #define BITCOIN_WALLET_CRYPTER_H #include "keystore.h" #include "serialize.h" #include "support/allocators/secure.h" #include class uint256; const unsigned int WALLET_CRYPTO_KEY_SIZE = 32; const unsigned int WALLET_CRYPTO_SALT_SIZE = 8; const unsigned int WALLET_CRYPTO_IV_SIZE = 16; /** * Private key encryption is done based on a CMasterKey, which holds a salt and * random encryption key. * * CMasterKeys are encrypted using AES-256-CBC using a key derived using * derivation method nDerivationMethod (0 == EVP_sha512()) and derivation * iterations nDeriveIterations. vchOtherDerivationParameters is provided for * alternative algorithms which may require more parameters (such as scrypt). * * Wallet Private Keys are then encrypted using AES-256-CBC with the * double-sha256 of the public key as the IV, and the master key's key as the * encryption key (see keystore.[ch]). */ /** Master key for wallet encryption */ class CMasterKey { public: std::vector vchCryptedKey; std::vector vchSalt; //! 0 = EVP_sha512() //! 1 = scrypt() unsigned int nDerivationMethod; unsigned int nDeriveIterations; //! Use this for more parameters to key derivation, such as the various //! parameters to scrypt std::vector vchOtherDerivationParameters; ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { READWRITE(vchCryptedKey); READWRITE(vchSalt); READWRITE(nDerivationMethod); READWRITE(nDeriveIterations); READWRITE(vchOtherDerivationParameters); } CMasterKey() { // 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M // ie slightly lower than the lowest hardware we need bother supporting nDeriveIterations = 25000; nDerivationMethod = 0; vchOtherDerivationParameters = std::vector(0); } }; typedef std::vector> CKeyingMaterial; namespace wallet_crypto { class TestCrypter; } /** Encryption/decryption context with key information */ class CCrypter { // for test access to chKey/chIV friend class wallet_crypto::TestCrypter; private: std::vector> vchKey; std::vector> vchIV; bool fKeySet; int BytesToKeySHA512AES(const std::vector &chSalt, const SecureString &strKeyData, int count, uint8_t *key, uint8_t *iv) const; public: bool SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector &chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); bool Encrypt(const CKeyingMaterial &vchPlaintext, std::vector &vchCiphertext) const; bool Decrypt(const std::vector &vchCiphertext, CKeyingMaterial &vchPlaintext) const; bool SetKey(const CKeyingMaterial &chNewKey, const std::vector &chNewIV); void CleanKey() { memory_cleanse(vchKey.data(), vchKey.size()); memory_cleanse(vchIV.data(), vchIV.size()); fKeySet = false; } CCrypter() { fKeySet = false; vchKey.resize(WALLET_CRYPTO_KEY_SIZE); vchIV.resize(WALLET_CRYPTO_IV_SIZE); } ~CCrypter() { CleanKey(); } }; /** * Keystore which keeps the private keys encrypted. * It derives from the basic key store, which is used if no encryption is * active. */ class CCryptoKeyStore : public CBasicKeyStore { private: CKeyingMaterial vMasterKey; //! if fUseCrypto is true, mapKeys must be empty //! if fUseCrypto is false, vMasterKey must be empty std::atomic fUseCrypto; //! keeps track of whether Unlock has run a thorough check before bool fDecryptionThoroughlyChecked; protected: bool SetCrypted(); //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial &vMasterKeyIn); bool Unlock(const CKeyingMaterial &vMasterKeyIn); CryptedKeyMap mapCryptedKeys; public: CCryptoKeyStore() : fUseCrypto(false), fDecryptionThoroughlyChecked(false) {} bool IsCrypted() const { return fUseCrypto; } - - bool IsLocked() const { - if (!IsCrypted()) return false; - bool result; - { - LOCK(cs_KeyStore); - result = vMasterKey.empty(); - } - return result; - } - + bool IsLocked() const; bool Lock(); virtual bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override; - bool HaveKey(const CKeyID &address) const override { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::HaveKey(address); - } - - return mapCryptedKeys.count(address) > 0; - } + bool HaveKey(const CKeyID &address) const override; bool GetKey(const CKeyID &address, CKey &keyOut) const override; bool GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const override; - std::set GetKeys() const override { - LOCK(cs_KeyStore); - if (!IsCrypted()) { - return CBasicKeyStore::GetKeys(); - } - std::set set_address; - for (const auto &mi : mapCryptedKeys) { - set_address.insert(mi.first); - } - return set_address; - } + std::set GetKeys() const override; /** * Wallet status (encrypted, locked) changed. * Note: Called without locks held. */ boost::signals2::signal NotifyStatusChanged; }; #endif // BITCOIN_WALLET_CRYPTER_H