/* This file is part of cpp-ethereum. cpp-ethereum is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. cpp-ethereum is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ /** @file KeyManager.cpp * @author Gav Wood * @date 2014 */ #include "KeyManager.h" #include #include #include #include #include #include #include using namespace std; using namespace dev; using namespace eth; namespace js = json_spirit; namespace fs = boost::filesystem; KeyManager::KeyManager(string const& _keysFile, string const& _secretsPath): m_keysFile(_keysFile), m_store(_secretsPath) {} KeyManager::~KeyManager() {} bool KeyManager::exists() const { return !contents(m_keysFile + ".salt").empty() && !contents(m_keysFile).empty(); } void KeyManager::create(string const& _pass) { m_defaultPasswordDeprecated = asString(h256::random().asBytes()); write(_pass, m_keysFile); } bool KeyManager::recode(Address const& _address, string const& _newPass, string const& _hint, function const& _pass, KDF _kdf) { noteHint(_newPass, _hint); h128 u = uuid(_address); if (!store().recode(u, _newPass, [&](){ return getPassword(u, _pass); }, _kdf)) return false; m_keyInfo[u].passHash = hashPassword(_newPass); write(); return true; } bool KeyManager::recode(Address const& _address, SemanticPassword _newPass, function const& _pass, KDF _kdf) { h128 u = uuid(_address); string p; if (_newPass == SemanticPassword::Existing) p = getPassword(u, _pass); else if (_newPass == SemanticPassword::Master) p = defaultPassword(); else return false; return recode(_address, p, string(), _pass, _kdf); } bool KeyManager::load(string const& _pass) { try { bytes salt = contents(m_keysFile + ".salt"); bytes encKeys = contents(m_keysFile); m_keysFileKey = SecureFixedHash<16>(pbkdf2(_pass, salt, 262144, 16)); bytesSec bs = decryptSymNoAuth(m_keysFileKey, h128(), &encKeys); RLP s(bs.ref()); unsigned version = unsigned(s[0]); if (version == 1) { for (auto const& i: s[1]) { h128 uuid(i[1]); Address addr(i[0]); m_addrLookup[addr] = uuid; m_keyInfo[uuid] = KeyInfo(h256(i[2]), string(i[3])); // cdebug << toString(addr) << toString(uuid) << toString((h256)i[2]) << (string)i[3]; } for (auto const& i: s[2]) m_passwordHint[h256(i[0])] = string(i[1]); m_defaultPasswordDeprecated = string(s[3]); } // cdebug << hashPassword(m_password) << toHex(m_password); cachePassword(m_defaultPasswordDeprecated); // cdebug << hashPassword(asString(m_key.ref())) << m_key.hex(); cachePassword(asString(m_keysFileKey.ref())); // cdebug << hashPassword(_pass) << _pass; m_master = hashPassword(_pass); cachePassword(_pass); return true; } catch (...) { return false; } } Secret KeyManager::secret(Address const& _address, function const& _pass) const { auto it = m_addrLookup.find(_address); if (it == m_addrLookup.end()) return Secret(); return secret(it->second, _pass); } Secret KeyManager::secret(h128 const& _uuid, function const& _pass) const { return Secret(m_store.secret(_uuid, [&](){ return getPassword(_uuid, _pass); })); } string KeyManager::getPassword(h128 const& _uuid, function const& _pass) const { auto kit = m_keyInfo.find(_uuid); h256 ph; if (kit != m_keyInfo.end()) ph = kit->second.passHash; return getPassword(ph, _pass); } string KeyManager::getPassword(h256 const& _passHash, function const& _pass) const { auto it = m_cachedPasswords.find(_passHash); if (it != m_cachedPasswords.end()) return it->second; for (unsigned i = 0; i < 10; ++i) { string p = _pass(); if (p.empty()) break; if (_passHash == UnknownPassword || hashPassword(p) == _passHash) { cachePassword(p); return p; } } return string(); } h128 KeyManager::uuid(Address const& _a) const { auto it = m_addrLookup.find(_a); if (it == m_addrLookup.end()) return h128(); return it->second; } Address KeyManager::address(h128 const& _uuid) const { for (auto const& i: m_addrLookup) if (i.second == _uuid) return i.first; return Address(); } h128 KeyManager::import(Secret const& _s, string const& _accountName, string const& _pass, string const& _passwordHint) { Address addr = KeyPair(_s).address(); auto passHash = hashPassword(_pass); cachePassword(_pass); m_passwordHint[passHash] = _passwordHint; auto uuid = m_store.importSecret(_s.asBytesSec(), _pass); m_keyInfo[uuid] = KeyInfo{passHash, _accountName}; m_addrLookup[addr] = uuid; write(m_keysFile); return uuid; } void KeyManager::importExisting(h128 const& _uuid, string const& _info, string const& _pass, string const& _passwordHint) { bytesSec key = m_store.secret(_uuid, [&](){ return _pass; }); if (key.empty()) return; Address a = KeyPair(Secret(key)).address(); auto passHash = hashPassword(_pass); if (!m_cachedPasswords.count(passHash)) cachePassword(_pass); importExisting(_uuid, _info, a, passHash, _passwordHint); } void KeyManager::importExisting(h128 const& _uuid, string const& _accountName, Address const& _address, h256 const& _passHash, string const& _passwordHint) { if (!m_passwordHint.count(_passHash)) m_passwordHint[_passHash] = _passwordHint; m_addrLookup[_address] = _uuid; m_keyInfo[_uuid].passHash = _passHash; m_keyInfo[_uuid].accountName = _accountName; write(m_keysFile); } void KeyManager::kill(Address const& _a) { auto id = m_addrLookup[_a]; m_addrLookup.erase(_a); m_keyInfo.erase(id); m_store.kill(id); write(m_keysFile); } KeyPair KeyManager::presaleSecret(std::string const& _json, function const& _password) { js::mValue val; json_spirit::read_string(_json, val); auto obj = val.get_obj(); string p = _password(true); if (obj["encseed"].type() == js::str_type) { auto encseed = fromHex(obj["encseed"].get_str()); KeyPair k; for (bool gotit = false; !gotit;) { gotit = true; k = KeyPair::fromEncryptedSeed(&encseed, p); if (obj["ethaddr"].type() == js::str_type) { Address a(obj["ethaddr"].get_str()); Address b = k.address(); if (a != b) { if ((p = _password(false)).empty()) BOOST_THROW_EXCEPTION(PasswordUnknown()); else gotit = false; } } } return k; } else BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("encseed type is not js::str_type")); } Addresses KeyManager::accounts() const { Addresses ret; ret.reserve(m_addrLookup.size()); for (auto const& i: m_addrLookup) if (m_keyInfo.count(i.second) > 0) ret.push_back(i.first); return ret; } bool KeyManager::hasAccount(const Address& _address) const { return m_addrLookup.count(_address) && m_keyInfo.count(m_addrLookup.at(_address)); } string const& KeyManager::accountName(Address const& _address) const { try { return m_keyInfo.at(m_addrLookup.at(_address)).accountName; } catch (...) { return EmptyString; } } string const& KeyManager::passwordHint(Address const& _address) const { try { return m_passwordHint.at(m_keyInfo.at(m_addrLookup.at(_address)).passHash); } catch (...) { return EmptyString; } } h256 KeyManager::hashPassword(string const& _pass) const { // TODO SECURITY: store this a bit more securely; Scrypt perhaps? return h256(pbkdf2(_pass, asBytes(m_defaultPasswordDeprecated), 262144, 32).makeInsecure()); } void KeyManager::cachePassword(string const& _password) const { m_cachedPasswords[hashPassword(_password)] = _password; } bool KeyManager::write(string const& _keysFile) const { if (!m_keysFileKey) return false; write(m_keysFileKey, _keysFile); return true; } void KeyManager::write(string const& _pass, string const& _keysFile) const { bytes salt = h256::random().asBytes(); writeFile(_keysFile + ".salt", salt); auto key = SecureFixedHash<16>(pbkdf2(_pass, salt, 262144, 16)); cachePassword(_pass); m_master = hashPassword(_pass); write(key, _keysFile); } void KeyManager::write(SecureFixedHash<16> const& _key, string const& _keysFile) const { RLPStream s(4); s << 1; // version s.appendList(accounts().size()); for (auto const& address: accounts()) { h128 id = uuid(address); auto const& ki = m_keyInfo.at(id); s.appendList(4) << address << id << ki.passHash << ki.accountName; } s.appendList(m_passwordHint.size()); for (auto const& i: m_passwordHint) s.appendList(2) << i.first << i.second; s.append(m_defaultPasswordDeprecated); writeFile(_keysFile, encryptSymNoAuth(_key, h128(), &s.out())); m_keysFileKey = _key; cachePassword(defaultPassword()); }