Fabcoin Core  0.16.2
P2P Digital Currency
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(string const& _path): m_path(_path)
95 {
96  load();
97 }
98 
99 void SecretStore::setPath(string 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  m_cached[_uuid] = key;
117  }
118  return key;
119 }
120 
121 bytesSec SecretStore::secret(Address const& _address, function<string()> const& _pass) const
122 {
123  bytesSec ret;
124  if (auto k = key(_address))
125  ret = bytesSec(decrypt(k->second.encryptedKey, _pass()));
126  return ret;
127 }
128 
129 bytesSec SecretStore::secret(string const& _content, string const& _pass)
130 {
131  try
132  {
133  js::mValue u = upgraded(_content);
134  if (u.type() != js::obj_type)
135  return bytesSec();
136  return decrypt(js::write_string(u.get_obj()["crypto"], false), _pass);
137  }
138  catch (...)
139  {
140  return bytesSec();
141  }
142 }
143 
144 h128 SecretStore::importSecret(bytesSec const& _s, string const& _pass)
145 {
146  h128 r = h128::random();
147  EncryptedKey key{encrypt(_s.ref(), _pass), toUUID(r), KeyPair(Secret(_s)).address()};
148  m_cached[r] = _s;
149  m_keys[r] = move(key);
150  save();
151  return r;
152 }
153 
154 h128 SecretStore::importSecret(bytesConstRef _s, string const& _pass)
155 {
156  h128 r = h128::random();
157  EncryptedKey key{encrypt(_s, _pass), toUUID(r), KeyPair(Secret(_s)).address()};
158  m_cached[r] = bytesSec(_s);
159  m_keys[r] = move(key);
160  save();
161  return r;
162 }
163 
164 void SecretStore::kill(h128 const& _uuid)
165 {
166  m_cached.erase(_uuid);
167  if (m_keys.count(_uuid))
168  {
169  fs::remove(m_keys[_uuid].filename);
170  m_keys.erase(_uuid);
171  }
172 }
173 
175 {
176  m_cached.clear();
177 }
178 
179 void SecretStore::save(string const& _keysPath)
180 {
181  fs::path p(_keysPath);
182  fs::create_directories(p);
183  DEV_IGNORE_EXCEPTIONS(fs::permissions(p, fs::owner_all));
184  for (auto& k: m_keys)
185  {
186  string uuid = toUUID(k.first);
187  string filename = (p / uuid).string() + ".json";
188  js::mObject v;
189  js::mValue crypto;
190  js::read_string(k.second.encryptedKey, crypto);
191  v["address"] = k.second.address.hex();
192  v["crypto"] = crypto;
193  v["id"] = uuid;
194  v["version"] = c_keyFileVersion;
195  writeFile(filename, js::write_string(js::mValue(v), true));
196  swap(k.second.filename, filename);
197  if (!filename.empty() && !fs::equivalent(filename, k.second.filename))
198  fs::remove(filename);
199  }
200 }
201 
202 bool SecretStore::noteAddress(h128 const& _uuid, Address const& _address)
203 {
204  if (m_keys.find(_uuid) != m_keys.end() && m_keys[_uuid].address == ZeroAddress)
205  {
206  m_keys[_uuid].address = _address;
207  return true;
208  }
209  return false;
210 }
211 
212 void SecretStore::load(string const& _keysPath)
213 {
214  fs::path p(_keysPath);
215  try
216  {
217  for (fs::directory_iterator it(p); it != fs::directory_iterator(); ++it)
218  if (fs::is_regular_file(it->path()))
219  readKey(it->path().string(), true);
220  }
221  catch (...) {}
222 }
223 
224 h128 SecretStore::readKey(string const& _file, bool _takeFileOwnership)
225 {
226  ctrace << "Reading" << _file;
227  return readKeyContent(contentsString(_file), _takeFileOwnership ? _file : string());
228 }
229 
230 h128 SecretStore::readKeyContent(string const& _content, string const& _file)
231 {
232  try
233  {
234  js::mValue u = upgraded(_content);
235  if (u.type() == js::obj_type)
236  {
237  js::mObject& o = u.get_obj();
238  auto uuid = fromUUID(o["id"].get_str());
240  if (o.find("address") != o.end() && isHex(o["address"].get_str()))
241  address = Address(o["address"].get_str());
242  else
243  cwarn << "Account address is either not defined or not in hex format" << _file;
244  m_keys[uuid] = EncryptedKey{js::write_string(o["crypto"], false), _file, address};
245  return uuid;
246  }
247  else
248  cwarn << "Invalid JSON in key file" << _file;
249  return h128();
250  }
251  catch (...)
252  {
253  return h128();
254  }
255 }
256 
257 bool SecretStore::recode(Address const& _address, string const& _newPass, function<string()> const& _pass, KDF _kdf)
258 {
259  if (auto k = key(_address))
260  {
261  bytesSec s = secret(_address, _pass);
262  if (s.empty())
263  return false;
264  else
265  {
266  k->second.encryptedKey = encrypt(s.ref(), _newPass, _kdf);
267  save();
268  return true;
269  }
270  }
271  return false;
272 }
273 
274 pair<h128 const, SecretStore::EncryptedKey> const* SecretStore::key(Address const& _address) const
275 {
276  for (auto const& k: m_keys)
277  if (k.second.address == _address)
278  return &k;
279  return nullptr;
280 }
281 
282 pair<h128 const, SecretStore::EncryptedKey>* SecretStore::key(Address const& _address)
283 {
284  for (auto& k: m_keys)
285  if (k.second.address == _address)
286  return &k;
287  return nullptr;
288 }
289 
290 bool SecretStore::recode(h128 const& _uuid, string const& _newPass, function<string()> const& _pass, KDF _kdf)
291 {
292  bytesSec s = secret(_uuid, _pass, true);
293  if (s.empty())
294  return false;
295  m_cached.erase(_uuid);
296  m_keys[_uuid].encryptedKey = encrypt(s.ref(), _newPass, _kdf);
297  save();
298  return true;
299 }
300 
301 static bytesSec deriveNewKey(string const& _pass, KDF _kdf, js::mObject& o_ret)
302 {
303  unsigned dklen = 32;
304  unsigned iterations = 1 << 18;
305  bytes salt = h256::random().asBytes();
306  if (_kdf == KDF::Scrypt)
307  {
308  unsigned p = 1;
309  unsigned r = 8;
310  o_ret["kdf"] = "scrypt";
311  {
312  js::mObject params;
313  params["n"] = int64_t(iterations);
314  params["r"] = int(r);
315  params["p"] = int(p);
316  params["dklen"] = int(dklen);
317  params["salt"] = toHex(salt);
318  o_ret["kdfparams"] = params;
319  }
320  return scrypt(_pass, salt, iterations, r, p, dklen);
321  }
322  else
323  {
324  o_ret["kdf"] = "pbkdf2";
325  {
326  js::mObject params;
327  params["prf"] = "hmac-sha256";
328  params["c"] = int(iterations);
329  params["salt"] = toHex(salt);
330  params["dklen"] = int(dklen);
331  o_ret["kdfparams"] = params;
332  }
333  return pbkdf2(_pass, salt, iterations, dklen);
334  }
335 }
336 
337 string SecretStore::encrypt(bytesConstRef _v, string const& _pass, KDF _kdf)
338 {
339  js::mObject ret;
340 
341  bytesSec derivedKey = deriveNewKey(_pass, _kdf, ret);
342  if (derivedKey.empty())
343  BOOST_THROW_EXCEPTION(crypto::CryptoException() << errinfo_comment("Key derivation failed."));
344 
345  ret["cipher"] = "aes-128-ctr";
346  SecureFixedHash<16> key(derivedKey, h128::AlignLeft);
347  h128 iv = h128::random();
348  {
349  js::mObject params;
350  params["iv"] = toHex(iv.ref());
351  ret["cipherparams"] = params;
352  }
353 
354  // cipher text
355  bytes cipherText = encryptSymNoAuth(key, iv, _v);
356  if (cipherText.empty())
357  BOOST_THROW_EXCEPTION(crypto::CryptoException() << errinfo_comment("Key encryption failed."));
358  ret["ciphertext"] = toHex(cipherText);
359 
360  // and mac.
361  h256 mac = sha3(derivedKey.ref().cropped(16, 16).toBytes() + cipherText);
362  ret["mac"] = toHex(mac.ref());
363 
364  return js::write_string(js::mValue(ret), true);
365 }
366 
367 bytesSec SecretStore::decrypt(string const& _v, string const& _pass)
368 {
369  js::mObject o;
370  {
371  js::mValue ov;
372  js::read_string(_v, ov);
373  o = ov.get_obj();
374  }
375 
376  // derive key
377  bytesSec derivedKey;
378  if (o["kdf"].get_str() == "pbkdf2")
379  {
380  auto params = o["kdfparams"].get_obj();
381  if (params["prf"].get_str() != "hmac-sha256")
382  {
383  cwarn << "Unknown PRF for PBKDF2" << params["prf"].get_str() << "not supported.";
384  return bytesSec();
385  }
386  unsigned iterations = params["c"].get_int();
387  bytes salt = fromHex(params["salt"].get_str());
388  derivedKey = pbkdf2(_pass, salt, iterations, params["dklen"].get_int());
389  }
390  else if (o["kdf"].get_str() == "scrypt")
391  {
392  auto p = o["kdfparams"].get_obj();
393  derivedKey = scrypt(_pass, fromHex(p["salt"].get_str()), p["n"].get_int(), p["r"].get_int(), p["p"].get_int(), p["dklen"].get_int());
394  }
395  else
396  {
397  cwarn << "Unknown KDF" << o["kdf"].get_str() << "not supported.";
398  return bytesSec();
399  }
400 
401  if (derivedKey.size() < 32 && !(o.count("compat") && o["compat"].get_str() == "2"))
402  {
403  cwarn << "Derived key's length too short (<32 bytes)";
404  return bytesSec();
405  }
406 
407  bytes cipherText = fromHex(o["ciphertext"].get_str());
408 
409  // check MAC
410  if (o.count("mac"))
411  {
412  h256 mac(o["mac"].get_str());
413  h256 macExp;
414  if (o.count("compat") && o["compat"].get_str() == "2")
415  macExp = sha3(derivedKey.ref().cropped(derivedKey.size() - 16).toBytes() + cipherText);
416  else
417  macExp = sha3(derivedKey.ref().cropped(16, 16).toBytes() + cipherText);
418  if (mac != macExp)
419  {
420  cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
421  return bytesSec();
422  }
423  }
424  else if (o.count("sillymac"))
425  {
426  h256 mac(o["sillymac"].get_str());
427  h256 macExp = sha3(asBytes(o["sillymacjson"].get_str()) + derivedKey.ref().cropped(derivedKey.size() - 16).toBytes() + cipherText);
428  if (mac != macExp)
429  {
430  cwarn << "Invalid key - MAC mismatch; expected" << toString(macExp) << ", got" << toString(mac);
431  return bytesSec();
432  }
433  }
434  else
435  cwarn << "No MAC. Proceeding anyway.";
436 
437  // decrypt
438  if (o["cipher"].get_str() == "aes-128-ctr")
439  {
440  auto params = o["cipherparams"].get_obj();
441  h128 iv(params["iv"].get_str());
442  if (o.count("compat") && o["compat"].get_str() == "2")
443  {
444  SecureFixedHash<16> key(sha3Secure(derivedKey.ref().cropped(derivedKey.size() - 16)), h128::AlignRight);
445  return decryptSymNoAuth(key, iv, &cipherText);
446  }
447  else
448  return decryptSymNoAuth(SecureFixedHash<16>(derivedKey, h128::AlignLeft), iv, &cipherText);
449  }
450  else
451  {
452  cwarn << "Unknown cipher" << o["cipher"].get_str() << "not supported.";
453  return bytesSec();
454  }
455 }
const Object & get_obj() const
h128 fromUUID(std::string const &_uuid)
Definition: FixedHash.cpp:31
Adapted from code found on http://stackoverflow.com/questions/180947/base64-decode-snippet-in-c Origi...
Definition: Arith256.cpp:15
std::string toHex(T const &_data, int _w=2, HexPrefix _prefix=HexPrefix::DontAdd)
Definition: CommonData.h:54
#define ctrace
Definition: Log.h:305
h128 readKeyContent(std::string const &_content, std::string const &_file=std::string())
Import the key contained in the json-encoded _content, but do not store it in the managed directory...
vector_ref< T > ref()
Definition: Common.h:106
void swap(dev::eth::Watch &_a, dev::eth::Watch &_b)
Definition: Interface.h:284
void save()
Store all keys in the managed directory.
Definition: SecretStore.h:115
void writeFile(std::string const &_file, bytesConstRef _data, bool _writeDeleteRename=false)
Write the given binary data into the given file, replacing the file if it pre-exists.
Definition: CommonIO.cpp:107
bytesSec decryptSymNoAuth(SecureFixedHash< 16 > const &_k, h128 const &_iv, bytesConstRef _cipher)
Decrypts payload with specified IV/ctr using AES128-CTR.
Definition: Common.h:129
bytesSec pbkdf2(std::string const &_pass, bytes const &_salt, unsigned _iterations, unsigned _dkLen=32)
Derive key via PBKDF2.
Simple class that represents a "key pair".
Definition: Common.h:150
size_t count
Definition: ExecStats.cpp:37
secure_vector< byte > bytesSec
Definition: Common.h:118
h160 Address
An Ethereum address: 20 bytes.
Definition: Common.h:62
SecureFixedHash< 32 > sha3Secure(bytesConstRef _input)
Definition: SHA3.h:41
std::pair< bytes, h128 > encryptSymNoAuth(SecureFixedHash< 16 > const &_k, bytesConstRef _plain)
Encrypts payload with random IV/ctr using AES128-CTR.
Definition: Common.cpp:157
SecureFixedHash< 32 > Secret
Definition: Common.h:35
#define c(i)
std::hash for asio::adress
Definition: Common.h:323
std::string contentsString(std::string const &_file)
Retrieve and returns the contents of the given file as a std::string.
std::string toString(string32 const &_s)
Make normal string from fixed-length string.
Definition: CommonData.cpp:141
void version()
Definition: main.cpp:53
mConfig::Value_type mValue
Value_type type() const
bytes fromHex(std::string const &_s, WhenError _throw=WhenError::DontThrow)
Definition: CommonData.cpp:99
bool read_string(const String_type &s, Value_type &value)
std::pair< h128 const, EncryptedKey > const * key(Address const &_address) const
bytesRef ref()
Definition: FixedHash.h:133
bytesSec secret(h128 const &_uuid, std::function< std::string()> const &_pass, bool _useCache=true) const
bool isHex(std::string const &_s) noexcept
Address ZeroAddress
The zero address.
Definition: Common.cpp:65
static bytesSec decrypt(std::string const &_v, std::string const &_pass)
Decrypts _v with a key derived from _pass or the empty byte array on error.
std::vector< byte > bytes
Definition: Common.h:75
Fixed-size raw-byte array container type, with an API optimised for storing hashes.
Definition: FixedHash.h:47
bytes asBytes(std::string const &_b)
Converts a string to a byte array containing the string&#39;s (byte) data.
Definition: CommonData.h:92
#define cwarn
Definition: Log.h:304
h128 importSecret(bytesSec const &_s, std::string const &_pass)
Imports the decrypted key given by _s and stores it, encrypted with (a key derived from) the password...
static std::string encrypt(bytesConstRef _v, std::string const &_pass, KDF _kdf=KDF::Scrypt)
Encrypts _v with a key derived from _pass or the empty string on error.
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.
mConfig::Object_type mObject
Address const & address() const
Retrieve the associated address of the public key.
Definition: Common.h:173
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:280
static FixedHash random()
Definition: FixedHash.h:162
bytes asBytes() const
Definition: FixedHash.h:145
Value_type::String_type write_string(const Value_type &value, bool pretty)
h128 readKey(std::string const &_file, bool _takeFileOwnership)
Import the key from the file _file, but do not copy it to the managed directory yet.
FixedHash< 16 > h128
Definition: FixedHash.h:342
void encrypt(Public const &_k, bytesConstRef _plain, bytes &o_cipher)
Encrypts plain text using Public key.
Definition: Common.cpp:102
bool decrypt(Secret const &_k, bytesConstRef _cipher, bytes &o_plaintext)
Decrypts cipher using Secret key.
Definition: Common.cpp:109
boost::error_info< struct tag_comment, std::string > errinfo_comment
Definition: Assertions.h:78
bool sha3(bytesConstRef _input, bytesRef o_output)
Calculate SHA3-256 hash of the given input and load it into the given output.
Definition: SHA3.cpp:214
void kill(h128 const &_uuid)
Removes the key specified by _uuid from both memory and disk.
void setPath(std::string const &_path)
Set a path for finding secrets.
Definition: SecretStore.cpp:99
size_t size() const
Definition: Common.h:109
struct evm_uint160be address(struct evm_env *env)
Definition: capi.c:13
std::string get_str(std::string::const_iterator begin, std::string::const_iterator end)
void clearCache() const
Clears all cached decrypted keys.
#define DEV_IGNORE_EXCEPTIONS(X)
Definition: Common.h:63
bool noteAddress(h128 const &_uuid, Address const &_address)
std::string toUUID(h128 const &_uuid)
Definition: FixedHash.cpp:43
bool empty() const
Definition: Common.h:110