Ethereum  PoC-8
The C++ Implementation of Ethereum
SecretStore.cpp
Go to the documentation of this file.
1 /*
2  This file is part of cpp-ethereum.
3 
4  cpp-ethereum is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  cpp-ethereum is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
16 */
22 #include "SecretStore.h"
23 #include <thread>
24 #include <mutex>
25 #include <boost/algorithm/string.hpp>
26 #include <boost/filesystem.hpp>
27 #include <libdevcore/Log.h>
28 #include <libdevcore/Guards.h>
29 #include <libdevcore/SHA3.h>
30 #include <libdevcore/FileSystem.h>
33 using namespace std;
34 using namespace dev;
35 namespace js = json_spirit;
36 namespace fs = boost::filesystem;
37 
38 static const int c_keyFileVersion = 3;
39 
41 static js::mValue upgraded(string const& _s)
42 {
43  js::mValue v;
44  js::read_string(_s, v);
45  if (v.type() != js::obj_type)
46  return js::mValue();
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;
49  if (version == 1)
50  {
51  // upgrade to version 2
52  js::mObject old;
53  swap(old, ret);
54 
55  ret["id"] = old["Id"];
56  js::mObject c;
57  c["ciphertext"] = old["Crypto"].get_obj()["CipherText"];
58  c["cipher"] = "aes-128-cbc";
59  {
60  js::mObject cp;
61  cp["iv"] = old["Crypto"].get_obj()["IV"];
62  c["cipherparams"] = cp;
63  }
64  c["kdf"] = old["Crypto"].get_obj()["KeyHeader"].get_obj()["Kdf"];
65  {
66  js::mObject kp;
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;
71  c["kdfparams"] = kp;
72  }
73  c["sillymac"] = old["Crypto"].get_obj()["MAC"];
74  c["sillymacjson"] = _s;
75  ret["crypto"] = c;
76  version = 2;
77  }
78  if (ret.count("Crypto") && !ret.count("crypto"))
79  {
80  ret["crypto"] = ret["Crypto"];
81  ret.erase("Crypto");
82  }
83  if (version == 2)
84  {
85  ret["crypto"].get_obj()["cipher"] = "aes-128-ctr";
86  ret["crypto"].get_obj()["compat"] = "2";
87  version = 3;
88  }
89  if (version == c_keyFileVersion)
90  return ret;
91  return js::mValue();
92 }
93 
94 SecretStore::SecretStore(fs::path const& _path): m_path(_path)
95 {
96  load();
97 }
98 
99 void SecretStore::setPath(fs::path const& _path)
100 {
101  m_path = _path;
102  load();
103 }
104 
105 bytesSec SecretStore::secret(h128 const& _uuid, function<string()> const& _pass, bool _useCache) const
106 {
107  auto rit = m_cached.find(_uuid);
108  if (_useCache && rit != m_cached.end())
109  return rit->second;
110  auto it = m_keys.find(_uuid);
111  bytesSec key;
112  if (it != m_keys.end())
113  {
114  key = bytesSec(decrypt(it->second.encryptedKey, _pass()));
115  if (!key.empty())
116  {
117  m_cached[_uuid] = key;
118  // TODO: Fix constness.
119  const_cast<SecretStore*>(this)->noteAddress(_uuid, toAddress(Secret{key}));
120  }
121  }
122  return key;
123 }
124 
125 bytesSec SecretStore::secret(Address const& _address, function<string()> const& _pass) const
126 {
127  bytesSec ret;
128  if (auto k = key(_address))
129  ret = bytesSec(decrypt(k->second.encryptedKey, _pass()));
130  return ret;
131 }
132 
133 bytesSec SecretStore::secret(string const& _content, string const& _pass)
134 {
135  try
136  {
137  js::mValue u = upgraded(_content);
138  if (u.type() != js::obj_type)
139  return bytesSec();
140  return decrypt(js::write_string(u.get_obj()["crypto"], false), _pass);
141  }
142  catch (...)
143  {
144  return bytesSec();
145  }
146 }
147 
148 h128 SecretStore::importSecret(bytesSec const& _s, string const& _pass)
149 {
150  h128 r = h128::random();
151  EncryptedKey key{encrypt(_s.ref(), _pass), toUUID(r), KeyPair(Secret(_s)).address()};
152  m_cached[r] = _s;
153  m_keys[r] = move(key);
154  save();
155  return r;
156 }
157 
158 h128 SecretStore::importSecret(bytesConstRef _s, string const& _pass)
159 {
160  h128 r = h128::random();
161  EncryptedKey key{encrypt(_s, _pass), toUUID(r), KeyPair(Secret(_s)).address()};
162  m_cached[r] = bytesSec(_s);
163  m_keys[r] = move(key);
164  save();
165  return r;
166 }
167 
168 void SecretStore::kill(h128 const& _uuid)
169 {
170  m_cached.erase(_uuid);
171  if (m_keys.count(_uuid))
172  {
173  fs::remove(m_keys[_uuid].filename);
174  m_keys.erase(_uuid);
175  }
176 }
177 
179 {
180  m_cached.clear();
181 }
182 
183 void SecretStore::save(fs::path const& _keysPath)
184 {
185  fs::create_directories(_keysPath);
186  DEV_IGNORE_EXCEPTIONS(fs::permissions(_keysPath, fs::owner_all));
187  for (auto& k: m_keys)
188  {
189  string uuid = toUUID(k.first);
190  fs::path filename = (_keysPath / uuid).string() + ".json";
191  js::mObject v;
192  js::mValue crypto;
193  js::read_string(k.second.encryptedKey, crypto);
194  v["address"] = k.second.address.hex();
195  v["crypto"] = crypto;
196  v["id"] = uuid;
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);
202  }
203 }
204 
205 bool SecretStore::noteAddress(h128 const& _uuid, Address const& _address)
206 {
207  auto it = m_keys.find(_uuid);
208  if (it != m_keys.end() && it->second.address == ZeroAddress)
209  {
210  it->second.address = _address;
211  return true;
212  }
213  return false;
214 }
215 
216 void SecretStore::load(fs::path const& _keysPath)
217 {
218  try
219  {
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);
223  }
224  catch (...) {}
225 }
226 
227 h128 SecretStore::readKey(fs::path const& _file, bool _takeFileOwnership)
228 {
229  ctrace << "Reading" << _file.string();
230  return readKeyContent(contentsString(_file), _takeFileOwnership ? _file : string());
231 }
232 
233 h128 SecretStore::readKeyContent(string const& _content, fs::path const& _file)
234 {
235  try
236  {
237  js::mValue u = upgraded(_content);
238  if (u.type() == js::obj_type)
239  {
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()))
244  address = Address(o["address"].get_str());
245  else
246  cwarn << "Account address is either not defined or not in hex format" << _file.string();
247  m_keys[uuid] = EncryptedKey{js::write_string(o["crypto"], false), _file, address};
248  return uuid;
249  }
250  else
251  cwarn << "Invalid JSON in key file" << _file.string();
252  return h128();
253  }
254  catch (...)
255  {
256  return h128();
257  }
258 }
259 
260 bool SecretStore::recode(Address const& _address, string const& _newPass, function<string()> const& _pass, KDF _kdf)
261 {
262  if (auto k = key(_address))
263  {
264  bytesSec s = secret(_address, _pass);
265  if (s.empty())
266  return false;
267  else
268  {
269  k->second.encryptedKey = encrypt(s.ref(), _newPass, _kdf);
270  save();
271  return true;
272  }
273  }
274  return false;
275 }
276 
277 pair<h128 const, SecretStore::EncryptedKey> const* SecretStore::key(Address const& _address) const
278 {
279  for (auto const& k: m_keys)
280  if (k.second.address == _address)
281  return &k;
282  return nullptr;
283 }
284 
285 pair<h128 const, SecretStore::EncryptedKey>* SecretStore::key(Address const& _address)
286 {
287  for (auto& k: m_keys)
288  if (k.second.address == _address)
289  return &k;
290  return nullptr;
291 }
292 
293 bool SecretStore::recode(h128 const& _uuid, string const& _newPass, function<string()> const& _pass, KDF _kdf)
294 {
295  bytesSec s = secret(_uuid, _pass, true);
296  if (s.empty())
297  return false;
298  m_cached.erase(_uuid);
299  m_keys[_uuid].encryptedKey = encrypt(s.ref(), _newPass, _kdf);
300  save();
301  return true;
302 }
303 
304 static bytesSec deriveNewKey(string const& _pass, KDF _kdf, js::mObject& o_ret)
305 {
306  unsigned dklen = 32;
307  unsigned iterations = 1 << 18;
308  bytes salt = h256::random().asBytes();
309  if (_kdf == KDF::Scrypt)
310  {
311  unsigned p = 1;
312  unsigned r = 8;
313  o_ret["kdf"] = "scrypt";
314  {
315  js::mObject params;
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;
322  }
323  return scrypt(_pass, salt, iterations, r, p, dklen);
324  }
325  else
326  {
327  o_ret["kdf"] = "pbkdf2";
328  {
329  js::mObject params;
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;
335  }
336  return pbkdf2(_pass, salt, iterations, dklen);
337  }
338 }
339 
340 string SecretStore::encrypt(bytesConstRef _v, string const& _pass, KDF _kdf)
341 {
342  js::mObject ret;
343 
344  bytesSec derivedKey = deriveNewKey(_pass, _kdf, ret);
345  if (derivedKey.empty())
346  BOOST_THROW_EXCEPTION(crypto::CryptoException() << errinfo_comment("Key derivation failed."));
347 
348  ret["cipher"] = "aes-128-ctr";
349  SecureFixedHash<16> key(derivedKey, h128::AlignLeft);
350  h128 iv = h128::random();
351  {
352  js::mObject params;
353  params["iv"] = toHex(iv.ref());
354  ret["cipherparams"] = params;
355  }
356 
357  // cipher text
358  bytes cipherText = encryptSymNoAuth(key, iv, _v);
359  if (cipherText.empty())
360  BOOST_THROW_EXCEPTION(crypto::CryptoException() << errinfo_comment("Key encryption failed."));
361  ret["ciphertext"] = toHex(cipherText);
362 
363  // and mac.
364  h256 mac = sha3(derivedKey.ref().cropped(16, 16).toBytes() + cipherText);
365  ret["mac"] = toHex(mac.ref());
366 
367  return js::write_string(js::mValue(ret), true);
368 }
369 
370 bytesSec SecretStore::decrypt(string const& _v, string const& _pass)
371 {
372  js::mObject o;
373  {
374  js::mValue ov;
375  js::read_string(_v, ov);
376  o = ov.get_obj();
377  }
378 
379  // derive key
380  bytesSec derivedKey;
381  if (o["kdf"].get_str() == "pbkdf2")
382  {
383  auto params = o["kdfparams"].get_obj();
384  if (params["prf"].get_str() != "hmac-sha256")
385  {
386  cwarn << "Unknown PRF for PBKDF2" << params["prf"].get_str() << "not supported.";
387  return bytesSec();
388  }
389  unsigned iterations = params["c"].get_int();
390  bytes salt = fromHex(params["salt"].get_str());
391  derivedKey = pbkdf2(_pass, salt, iterations, params["dklen"].get_int());
392  }
393  else if (o["kdf"].get_str() == "scrypt")
394  {
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());
397  }
398  else
399  {
400  cwarn << "Unknown KDF" << o["kdf"].get_str() << "not supported.";
401  return bytesSec();
402  }
403 
404  if (derivedKey.size() < 32 && !(o.count("compat") && o["compat"].get_str() == "2"))
405  {
406  cwarn << "Derived key's length too short (<32 bytes)";
407  return bytesSec();
408  }
409 
410  bytes cipherText = fromHex(o["ciphertext"].get_str());
411 
412  // check MAC
413  if (o.count("mac"))
414  {
415  h256 mac(o["mac"].get_str());
416  h256 macExp;
417  if (o.count("compat") && o["compat"].get_str() == "2")
418  macExp = sha3(derivedKey.ref().cropped(derivedKey.size() - 16).toBytes() + cipherText);
419  else
420  macExp = sha3(derivedKey.ref().cropped(16, 16).toBytes() + cipherText);
421  if (mac != macExp)
422  {
423  cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
424  return bytesSec();
425  }
426  }
427  else if (o.count("sillymac"))
428  {
429  h256 mac(o["sillymac"].get_str());
430  h256 macExp = sha3(asBytes(o["sillymacjson"].get_str()) + derivedKey.ref().cropped(derivedKey.size() - 16).toBytes() + cipherText);
431  if (mac != macExp)
432  {
433  cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
434  return bytesSec();
435  }
436  }
437  else
438  cwarn << "No MAC. Proceeding anyway.";
439 
440  // decrypt
441  if (o["cipher"].get_str() == "aes-128-ctr")
442  {
443  auto params = o["cipherparams"].get_obj();
444  h128 iv(params["iv"].get_str());
445  if (o.count("compat") && o["compat"].get_str() == "2")
446  {
447  SecureFixedHash<16> key(sha3Secure(derivedKey.ref().cropped(derivedKey.size() - 16)), h128::AlignRight);
448  return decryptSymNoAuth(key, iv, &cipherText);
449  }
450  else
451  return decryptSymNoAuth(SecureFixedHash<16>(derivedKey, h128::AlignLeft), iv, &cipherText);
452  }
453  else
454  {
455  cwarn << "Unknown cipher" << o["cipher"].get_str() << "not supported.";
456  return bytesSec();
457  }
458 }
dev::encryptSymNoAuth
std::pair< bytes, h128 > encryptSymNoAuth(SecureFixedHash< 16 > const &_k, bytesConstRef _plain)
Encrypts payload with random IV/ctr using AES128-CTR.
Definition: Common.cpp:175
dev::FixedHash::AlignLeft
@ AlignLeft
Definition: FixedHash.h:62
dev::scrypt
bytesSec scrypt(std::string const &_pass, bytes const &_salt, uint64_t _n, uint32_t _r, uint32_t _p, unsigned _dkLen)
Derive key via Scrypt.
Definition: Common.cpp:313
dev::SecretStore::save
void save()
Store all keys in the managed directory.
Definition: SecretStore.h:117
dev::FixedHash::AlignRight
@ AlignRight
Definition: FixedHash.h:62
dev::vector_ref< byte const >
dev::decryptSymNoAuth
bytesSec decryptSymNoAuth(SecureFixedHash< 16 > const &_k, h128 const &_iv, bytesConstRef _cipher)
Decrypts payload with specified IV/ctr using AES128-CTR.
Definition: Common.h:124
SecretStore.h
dev::secure_vector::empty
bool empty() const
Definition: Common.h:107
dev::SecretStore::noteAddress
bool noteAddress(h128 const &_uuid, Address const &_address)
Definition: SecretStore.cpp:205
dev::secure_vector::ref
vector_ref< T > ref()
Definition: Common.h:103
dev::SecureFixedHash< 32 >
JsonSpiritHeaders.h
dev::KDF
KDF
Definition: SecretStore.h:35
dev::sha3
bool sha3(bytesConstRef _input, bytesRef o_output) noexcept
Definition: SHA3.cpp:28
dev::pbkdf2
bytesSec pbkdf2(std::string const &_pass, bytes const &_salt, unsigned _iterations, unsigned _dkLen=32)
Derive key via PBKDF2.
dev::FixedHash::ref
bytesRef ref()
Definition: FixedHash.h:132
std::swap
void swap(dev::eth::Watch &_a, dev::eth::Watch &_b)
Definition: Interface.h:282
dev::fromUUID
h128 fromUUID(std::string const &_uuid)
Definition: FixedHash.cpp:26
dev::toString
std::string toString(std::chrono::time_point< T > const &_e, std::string const &_format="%F %T")
Definition: CommonIO.h:86
dev::FixedHash
Definition: FixedHash.h:47
dev::secure_vector::size
size_t size() const
Definition: Common.h:106
dev::contentsString
string contentsString(boost::filesystem::path const &_file)
Definition: CommonIO.cpp:120
dev::ZeroAddress
Address const ZeroAddress
The zero address.
Definition: Address.cpp:22
dev::secure_vector
Definition: Common.h:78
dev::h128
FixedHash< 16 > h128
Definition: FixedHash.h:358
dev::SecretStore::readKey
h128 readKey(boost::filesystem::path const &_file, bool _takeFileOwnership)
Definition: SecretStore.cpp:227
dev::KDF::Scrypt
@ Scrypt
dev::bytes
std::vector< byte > bytes
Definition: Common.h:72
SHA3.h
dev::SecretStore::importSecret
h128 importSecret(bytesSec const &_s, std::string const &_pass)
dev::FixedHash::random
static FixedHash random()
Definition: FixedHash.h:167
dev::KeyPair::address
Address const & address() const
Retrieve the associated address of the public key.
Definition: Common.h:168
dev::toAddress
Address toAddress(Public const &_public)
Convert a public key to address.
Definition: Common.cpp:105
dev::toUUID
std::string toUUID(h128 const &_uuid)
Definition: FixedHash.cpp:38
FileSystem.h
dev::bytesSec
secure_vector< byte > bytesSec
Definition: Common.h:115
dev::SecretStore::EncryptedKey
Definition: SecretStore.h:52
dev::sha3Secure
SecureFixedHash< 32 > sha3Secure(bytesConstRef _input) noexcept
Definition: SHA3.h:50
dev::SecretStore::kill
void kill(h128 const &_uuid)
Removes the key specified by _uuid from both memory and disk.
Definition: SecretStore.cpp:168
dev::asBytes
bytes asBytes(std::string const &_b)
Converts a string to a byte array containing the string's (byte) data.
Definition: CommonData.h:106
std
Definition: FixedHash.h:393
dev::SecretStore::setPath
void setPath(boost::filesystem::path const &_path)
Set a path for finding secrets.
Definition: SecretStore.cpp:99
dev
Definition: Address.cpp:21
dev::KeyPair
Definition: Common.h:149
dev::SecretStore::clearCache
void clearCache() const
Definition: SecretStore.cpp:178
dev::Secret
SecureFixedHash< 32 > Secret
Definition: Common.h:36
cwarn
#define cwarn
Guards.h
dev::writeFile
void writeFile(boost::filesystem::path const &_file, bytesConstRef _data, bool _writeDeleteRename)
Definition: CommonIO.cpp:125
DEV_IGNORE_EXCEPTIONS
#define DEV_IGNORE_EXCEPTIONS(X)
Definition: Common.h:59
dev::FixedHash::asBytes
bytes asBytes() const
Definition: FixedHash.h:150
dev::toHex
std::string toHex(Iterator _it, Iterator _end, std::string const &_prefix)
Definition: CommonData.h:46
dev::SecretStore::readKeyContent
h128 readKeyContent(std::string const &_content, boost::filesystem::path const &_file=boost::filesystem::path())
Definition: SecretStore.cpp:233
dev::encrypt
void encrypt(Public const &_k, bytesConstRef _plain, bytes &o_cipher)
Encrypts plain text using Public key.
Definition: Common.cpp:120
dev::SecretStore
Definition: SecretStore.h:49
dev::Address
h160 Address
Definition: Address.h:30
dev::SecretStore::address
Address address(h128 const &_uuid) const
Definition: SecretStore.h:121
Log.h
dev::fromHex
bytes fromHex(std::string const &_s, WhenError _throw=WhenError::DontThrow)
Definition: CommonData.cpp:81
dev::SecretStore::recode
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.
Exceptions.h
dev::decrypt
bool decrypt(Secret const &_k, bytesConstRef _cipher, bytes &o_plaintext)
Decrypts cipher using Secret key.
Definition: Common.cpp:127
dev::SecretStore::secret
bytesSec secret(h128 const &_uuid, std::function< std::string()> const &_pass, bool _useCache=true) const
dev::errinfo_comment
boost::error_info< struct tag_comment, std::string > errinfo_comment
Definition: Assertions.h:69
ctrace
#define ctrace
dev::isHex
bool isHex(std::string const &_s) noexcept