25 #include <boost/algorithm/string.hpp> 26 #include <boost/filesystem.hpp> 35 namespace js = json_spirit;
36 namespace fs = boost::filesystem;
38 static const int c_keyFileVersion = 3;
41 static js::mValue upgraded(
string const& _s)
44 js::read_string(_s, v);
45 if (v.type() != js::obj_type)
47 js::mObject ret = v.get_obj();
48 unsigned version = ret.count(
"Version") ? stoi(ret[
"Version"].get_str()) : ret.count(
"version") ? ret[
"version"].get_int() : 0;
55 ret[
"id"] = old[
"Id"];
57 c[
"ciphertext"] = old[
"Crypto"].get_obj()[
"CipherText"];
58 c[
"cipher"] =
"aes-128-cbc";
61 cp[
"iv"] = old[
"Crypto"].get_obj()[
"IV"];
62 c[
"cipherparams"] = cp;
64 c[
"kdf"] = old[
"Crypto"].get_obj()[
"KeyHeader"].get_obj()[
"Kdf"];
67 kp[
"salt"] = old[
"Crypto"].get_obj()[
"Salt"];
68 for (
auto const& i: old[
"Crypto"].get_obj()[
"KeyHeader"].get_obj()[
"KdfParams"].get_obj())
69 if (i.first !=
"SaltLen")
70 kp[boost::to_lower_copy(i.first)] = i.second;
73 c[
"sillymac"] = old[
"Crypto"].get_obj()[
"MAC"];
74 c[
"sillymacjson"] = _s;
78 if (ret.count(
"Crypto") && !ret.count(
"crypto"))
80 ret[
"crypto"] = ret[
"Crypto"];
85 ret[
"crypto"].get_obj()[
"cipher"] =
"aes-128-ctr";
86 ret[
"crypto"].get_obj()[
"compat"] =
"2";
89 if (version == c_keyFileVersion)
94 SecretStore::SecretStore(fs::path
const& _path): m_path(_path)
107 auto rit = m_cached.find(_uuid);
108 if (_useCache && rit != m_cached.end())
110 auto it = m_keys.find(_uuid);
112 if (it != m_keys.end())
117 m_cached[_uuid] = key;
128 if (
auto k = key(_address))
137 js::mValue u = upgraded(_content);
138 if (u.type() != js::obj_type)
140 return decrypt(js::write_string(u.get_obj()[
"crypto"],
false), _pass);
153 m_keys[r] = move(key);
163 m_keys[r] = move(key);
170 m_cached.erase(_uuid);
171 if (m_keys.count(_uuid))
173 fs::remove(m_keys[_uuid].filename);
185 fs::create_directories(_keysPath);
187 for (
auto& k: m_keys)
189 string uuid =
toUUID(k.first);
190 fs::path filename = (_keysPath / uuid).
string() +
".json";
193 js::read_string(k.second.encryptedKey, crypto);
194 v[
"address"] = k.second.address.hex();
195 v[
"crypto"] = crypto;
197 v[
"version"] = c_keyFileVersion;
198 writeFile(filename, js::write_string(js::mValue(v),
true));
199 swap(k.second.filename, filename);
200 if (!filename.empty() && !fs::equivalent(filename, k.second.filename))
201 fs::remove(filename);
207 auto it = m_keys.find(_uuid);
208 if (it != m_keys.end() && it->second.address ==
ZeroAddress)
210 it->second.address = _address;
216 void SecretStore::load(fs::path
const& _keysPath)
220 for (fs::directory_iterator it(_keysPath); it != fs::directory_iterator(); ++it)
221 if (fs::is_regular_file(it->path()))
222 readKey(it->path().string(),
true);
229 ctrace <<
"Reading" << _file.string();
237 js::mValue u = upgraded(_content);
238 if (u.type() == js::obj_type)
240 js::mObject& o = u.get_obj();
241 auto uuid =
fromUUID(o[
"id"].get_str());
243 if (o.find(
"address") != o.end() &&
isHex(o[
"address"].get_str()))
246 cwarn <<
"Account address is either not defined or not in hex format" << _file.string();
251 cwarn <<
"Invalid JSON in key file" << _file.string();
262 if (
auto k = key(_address))
269 k->second.encryptedKey =
encrypt(s.
ref(), _newPass, _kdf);
277 pair<h128 const, SecretStore::EncryptedKey>
const* SecretStore::key(
Address const& _address)
const 279 for (
auto const& k: m_keys)
280 if (k.second.address == _address)
285 pair<h128 const, SecretStore::EncryptedKey>* SecretStore::key(
Address const& _address)
287 for (
auto& k: m_keys)
288 if (k.second.address == _address)
298 m_cached.erase(_uuid);
299 m_keys[_uuid].encryptedKey =
encrypt(s.
ref(), _newPass, _kdf);
304 static bytesSec deriveNewKey(
string const& _pass,
KDF _kdf, js::mObject& o_ret)
307 unsigned iterations = 1 << 18;
313 o_ret[
"kdf"] =
"scrypt";
316 params[
"n"] = int64_t(iterations);
317 params[
"r"] = int(r);
318 params[
"p"] = int(p);
319 params[
"dklen"] = int(dklen);
320 params[
"salt"] =
toHex(salt);
321 o_ret[
"kdfparams"] = params;
323 return scrypt(_pass, salt, iterations, r, p, dklen);
327 o_ret[
"kdf"] =
"pbkdf2";
330 params[
"prf"] =
"hmac-sha256";
331 params[
"c"] = int(iterations);
332 params[
"salt"] =
toHex(salt);
333 params[
"dklen"] = int(dklen);
334 o_ret[
"kdfparams"] = params;
336 return pbkdf2(_pass, salt, iterations, dklen);
344 bytesSec derivedKey = deriveNewKey(_pass, _kdf, ret);
345 if (derivedKey.
empty())
346 BOOST_THROW_EXCEPTION(crypto::CryptoException() <<
errinfo_comment(
"Key derivation failed."));
348 ret[
"cipher"] =
"aes-128-ctr";
354 ret[
"cipherparams"] = params;
359 if (cipherText.empty())
360 BOOST_THROW_EXCEPTION(crypto::CryptoException() <<
errinfo_comment(
"Key encryption failed."));
361 ret[
"ciphertext"] =
toHex(cipherText);
364 h256 mac =
sha3(derivedKey.
ref().cropped(16, 16).toBytes() + cipherText);
367 return js::write_string(js::mValue(ret),
true);
375 js::read_string(_v, ov);
381 if (o[
"kdf"].get_str() ==
"pbkdf2")
383 auto params = o[
"kdfparams"].get_obj();
384 if (params[
"prf"].get_str() !=
"hmac-sha256")
386 cwarn <<
"Unknown PRF for PBKDF2" << params[
"prf"].get_str() <<
"not supported.";
389 unsigned iterations = params[
"c"].get_int();
391 derivedKey =
pbkdf2(_pass, salt, iterations, params[
"dklen"].get_int());
393 else if (o[
"kdf"].get_str() ==
"scrypt")
395 auto p = o[
"kdfparams"].get_obj();
396 derivedKey =
scrypt(_pass,
fromHex(p[
"salt"].get_str()), p[
"n"].get_int(), p[
"r"].get_int(), p[
"p"].get_int(), p[
"dklen"].get_int());
400 cwarn <<
"Unknown KDF" << o[
"kdf"].get_str() <<
"not supported.";
404 if (derivedKey.
size() < 32 && !(o.count(
"compat") && o[
"compat"].get_str() ==
"2"))
406 cwarn <<
"Derived key's length too short (<32 bytes)";
415 h256 mac(o[
"mac"].get_str());
417 if (o.count(
"compat") && o[
"compat"].get_str() ==
"2")
418 macExp =
sha3(derivedKey.
ref().cropped(derivedKey.
size() - 16).toBytes() + cipherText);
420 macExp =
sha3(derivedKey.
ref().cropped(16, 16).toBytes() + cipherText);
427 else if (o.count(
"sillymac"))
429 h256 mac(o[
"sillymac"].get_str());
430 h256 macExp =
sha3(
asBytes(o[
"sillymacjson"].get_str()) + derivedKey.
ref().cropped(derivedKey.
size() - 16).toBytes() + cipherText);
438 cwarn <<
"No MAC. Proceeding anyway.";
441 if (o[
"cipher"].get_str() ==
"aes-128-ctr")
443 auto params = o[
"cipherparams"].get_obj();
444 h128 iv(params[
"iv"].get_str());
445 if (o.count(
"compat") && o[
"compat"].get_str() ==
"2")
455 cwarn <<
"Unknown cipher" << o[
"cipher"].get_str() <<
"not supported.";
h128 fromUUID(std::string const &_uuid)
void swap(dev::eth::Watch &_a, dev::eth::Watch &_b)
Address address(h128 const &_uuid) const
void save()
Store all keys in the managed directory.
string contentsString(boost::filesystem::path const &_file)
Address const ZeroAddress
The zero address.
bytesSec decryptSymNoAuth(SecureFixedHash< 16 > const &_k, h128 const &_iv, bytesConstRef _cipher)
Decrypts payload with specified IV/ctr using AES128-CTR.
bytesSec pbkdf2(std::string const &_pass, bytes const &_salt, unsigned _iterations, unsigned _dkLen=32)
Derive key via PBKDF2.
SecureFixedHash< 32 > sha3Secure(bytesConstRef _input) noexcept
secure_vector< byte > bytesSec
std::pair< bytes, h128 > encryptSymNoAuth(SecureFixedHash< 16 > const &_k, bytesConstRef _plain)
Encrypts payload with random IV/ctr using AES128-CTR.
SecureFixedHash< 32 > Secret
std::string toString(std::chrono::time_point< T > const &_e, std::string const &_format="%F %T")
bytesSec secret(h128 const &_uuid, std::function< std::string()> const &_pass, bool _useCache=true) const
bool sha3(bytesConstRef _input, bytesRef o_output) noexcept
bytes fromHex(std::string const &_s, WhenError _throw=WhenError::DontThrow)
h128 readKeyContent(std::string const &_content, boost::filesystem::path const &_file=boost::filesystem::path())
bool isHex(std::string const &_s) noexcept
h128 readKey(boost::filesystem::path const &_file, bool _takeFileOwnership)
std::vector< byte > bytes
bytes asBytes(std::string const &_b)
Converts a string to a byte array containing the string's (byte) data.
void setPath(boost::filesystem::path const &_path)
Set a path for finding secrets.
h128 importSecret(bytesSec const &_s, std::string const &_pass)
Address toAddress(Public const &_public)
Convert a public key to address.
bool recode(h128 const &_uuid, std::string const &_newPass, std::function< std::string()> const &_pass, KDF _kdf=KDF::Scrypt)
Decrypts and re-encrypts the key identified by _uuid.
bytesSec scrypt(std::string const &_pass, bytes const &_salt, uint64_t _n, uint32_t _r, uint32_t _p, unsigned _dkLen)
Derive key via Scrypt.
static FixedHash random()
void encrypt(Public const &_k, bytesConstRef _plain, bytes &o_cipher)
Encrypts plain text using Public key.
bool decrypt(Secret const &_k, bytesConstRef _cipher, bytes &o_plaintext)
Decrypts cipher using Secret key.
Address const & address() const
Retrieve the associated address of the public key.
boost::error_info< struct tag_comment, std::string > errinfo_comment
void kill(h128 const &_uuid)
Removes the key specified by _uuid from both memory and disk.
std::string toHex(Iterator _it, Iterator _end, std::string const &_prefix)
void writeFile(boost::filesystem::path const &_file, bytesConstRef _data, bool _writeDeleteRename)
#define DEV_IGNORE_EXCEPTIONS(X)
bool noteAddress(h128 const &_uuid, Address const &_address)
std::string toUUID(h128 const &_uuid)