@@ -65,40 +65,54 @@ namespace fs = boost::filesystem;
6565#if 1
6666
6767inline h128 fromUUID (std::string const & _uuid) { return h128 (boost::replace_all_copy (_uuid, " -" , " " )); }
68+ inline std::string toUUID (h128 const & _uuid) { std::string ret = toHex (_uuid.ref ()); for (unsigned i: {20 , 16 , 12 , 8 }) ret.insert (ret.begin () + i, ' -' ); return ret; }
6869
69- class KeyManager : public Worker
70+ class KeyStore
7071{
7172public:
72- KeyManager () { readKeys (); }
73- ~KeyManager () {}
73+ KeyStore () { readKeys (); }
74+ ~KeyStore () {}
7475
75- Secret secret (h128 const & _uuid, function<std::string()> const & _pass)
76+ bytes key (h128 const & _uuid, function<std::string()> const & _pass)
7677 {
77- auto rit = m_ready .find (_uuid);
78- if (rit != m_ready .end ())
78+ auto rit = m_cached .find (_uuid);
79+ if (rit != m_cached .end ())
7980 return rit->second ;
8081 auto it = m_keys.find (_uuid);
8182 if (it == m_keys.end ())
82- return Secret ();
83- Secret ret ( decrypt (it->second , _pass () ));
84- if (ret )
85- m_ready [_uuid] = ret ;
86- return ret ;
83+ return bytes ();
84+ bytes key = decrypt (it->second , _pass ());
85+ if (!key. empty () )
86+ m_cached [_uuid] = key ;
87+ return key ;
8788 }
8889
89- h128 create ( std::string const & _pass)
90+ h128 import (bytes const & _s, std::string const & _pass)
9091 {
91- auto s = Secret ::random ();
92- h128 r ( sha3 (s)) ;
93- m_ready [r] = s ;
94- m_keys[r] = encrypt (s. asBytes (), _pass );
92+ h128 r = h128 ::random ();
93+ m_cached[r] = _s ;
94+ m_keys [r] = encrypt (_s, _pass) ;
95+ writeKeys ( );
9596 return r;
9697 }
9798
99+ // Clear any cached keys.
100+ void clearCache () const { m_cached.clear (); }
101+
98102private:
99103 void writeKeys (std::string const & _keysPath = getDataDir(" web3" ) + "/keys")
100104 {
101- (void )_keysPath;
105+ fs::path p (_keysPath);
106+ boost::filesystem::create_directories (p);
107+ for (auto const & k: m_keys)
108+ {
109+ std::string uuid = toUUID (k.first );
110+ js::mObject v;
111+ v[" crypto" ] = k.second ;
112+ v[" id" ] = uuid;
113+ v[" version" ] = 2 ;
114+ writeFile ((p / uuid).string () + " .json" , js::write_string (js::mValue (v), true ));
115+ }
102116 }
103117
104118 void readKeys (std::string const & _keysPath = getDataDir(" web3" ) + "/keys")
@@ -119,16 +133,49 @@ class KeyManager: public Worker
119133 else
120134 cwarn << " Cannot read key version" << version;
121135 }
122- else
123- cwarn << " Invalid JSON in key file" << it->path ().string ();
136+ // else
137+ // cwarn << "Invalid JSON in key file" << it->path().string();
124138 }
125139 }
126140
127141 static js::mValue encrypt (bytes const & _v, std::string const & _pass)
128142 {
129- (void )_v;
130- (void )_pass;
131- return js::mValue ();
143+ js::mObject ret;
144+
145+ // KDF info
146+ unsigned dklen = 16 ;
147+ unsigned iterations = 262144 ;
148+ bytes salt = h256::random ().asBytes ();
149+ ret[" kdf" ] = " pbkdf2" ;
150+ {
151+ js::mObject params;
152+ params[" prf" ] = " hmac-sha256" ;
153+ params[" c" ] = (int )iterations;
154+ params[" salt" ] = toHex (salt);
155+ params[" dklen" ] = (int )dklen;
156+ ret[" kdfparams" ] = params;
157+ }
158+ bytes derivedKey = pbkdf2 (_pass, salt, iterations, dklen);
159+
160+ // cipher info
161+ ret[" cipher" ] = " aes-128-cbc" ;
162+ h128 key (sha3 (h128 (derivedKey, h128::AlignRight)), h128::AlignRight);
163+ h128 iv = h128::random ();
164+ {
165+ js::mObject params;
166+ params[" iv" ] = toHex (iv.ref ());
167+ ret[" cipherparams" ] = params;
168+ }
169+
170+ // cipher text
171+ bytes cipherText = encryptSymNoAuth (key, iv, &_v);
172+ ret[" ciphertext" ] = toHex (cipherText);
173+
174+ // and mac.
175+ h256 mac = sha3 (bytesConstRef (&derivedKey).cropped (derivedKey.size () - 16 ).toBytes () + cipherText);
176+ ret[" mac" ] = toHex (mac.ref ());
177+
178+ return ret;
132179 }
133180
134181 static bytes decrypt (js::mValue const & _v, std::string const & _pass)
@@ -167,32 +214,154 @@ class KeyManager: public Worker
167214 }
168215
169216 // decrypt
170- bytes ret;
171217 if (o[" cipher" ].get_str () == " aes-128-cbc" )
172218 {
173219 auto params = o[" cipherparams" ].get_obj ();
174220 h128 key (sha3 (h128 (derivedKey, h128::AlignRight)), h128::AlignRight);
175221 h128 iv (params[" iv" ].get_str ());
176- decryptSymNoAuth (key, iv, &cipherText, ret );
222+ return decryptSymNoAuth (key, iv, &cipherText);
177223 }
178224 else
179225 {
180226 cwarn << " Unknown cipher" << o[" cipher" ].get_str () << " not supported." ;
181227 return bytes ();
182228 }
183-
184- return ret;
185229 }
186230
187- mutable std::map<h128, Secret> m_ready ;
231+ mutable std::map<h128, bytes> m_cached ;
188232 std::map<h128, js::mValue > m_keys;
189233};
190234
235+ class UnknownPassword : public Exception {};
236+
237+ struct KeyInfo
238+ {
239+ h256 passHash;
240+ std::string name;
241+ };
242+
243+ static const auto DontKnowThrow = [](){ BOOST_THROW_EXCEPTION (UnknownPassword ()); return std::string (); };
244+
245+ // This one is specifically for Ethereum, but we can make it generic in due course.
246+ // TODO: hidden-partition style key-store.
247+ class KeyManager
248+ {
249+ public:
250+ KeyManager () { m_cachedPasswords[sha3 (m_password)] = m_password; }
251+ ~KeyManager () {}
252+
253+ void load (std::string const & _pass, std::string const & _keysFile = getDataDir(" ethereum" ) + "/keys.info")
254+ {
255+ try {
256+ bytes salt = contents (_keysFile + " .salt" );
257+ bytes encKeys = contents (_keysFile);
258+ m_key = h128 (pbkdf2 (_pass, salt, 262144 , 16 ));
259+ bytes bs = decryptSymNoAuth (m_key, h128 (), &encKeys);
260+ RLP s (bs);
261+ unsigned version = (unsigned )s[0 ];
262+ if (version == 1 )
263+ {
264+ for (auto const & i: s[1 ])
265+ m_keyInfo[m_addrLookup[(Address)i[0 ]] = (h128)i[1 ]] = KeyInfo{(h256)i[2 ], (std::string)i[3 ]};
266+ for (auto const & i: s[2 ])
267+ m_passwordInfo[(h256)i[0 ]] = (std::string)i[1 ];
268+ m_password = (string)s[3 ];
269+ }
270+ }
271+ catch (...) {}
272+ m_cachedPasswords[sha3 (m_password)] = m_password;
273+ }
274+
275+ // Only use if previously loaded ok.
276+ // @returns false if wasn't previously loaded ok.
277+ bool save (std::string const & _keysFile = getDataDir(" ethereum" ) + "/keys.info") { if (!m_key) return false ; save (m_key, _keysFile); return true ; }
278+
279+ void save (std::string const & _pass, std::string const & _keysFile = getDataDir(" ethereum" ) + "/keys.info")
280+ {
281+ bytes salt = h256::random ().asBytes ();
282+ writeFile (_keysFile + " .salt" , salt);
283+ auto key = h128 (pbkdf2 (_pass, salt, 262144 , 16 ));
284+ save (key, _keysFile);
285+ }
286+
287+ void save (h128 const & _key, std::string const & _keysFile = getDataDir(" ethereum" ) + "/keys.info")
288+ {
289+ RLPStream s (4 );
290+ s << 1 ;
291+ s.appendList (m_addrLookup.size ());
292+ for (auto const & i: m_addrLookup)
293+ s.appendList (4 ) << i.first << i.second << m_keyInfo[i.second ].passHash << m_keyInfo[i.second ].name ;
294+ s.appendList (m_passwordInfo.size ());
295+ for (auto const & i: m_passwordInfo)
296+ s.appendList (2 ) << i.first << i.second ;
297+ s.append (m_password);
298+
299+ writeFile (_keysFile, encryptSymNoAuth (_key, h128 (), &s.out ()));
300+ m_key = _key;
301+ }
302+
303+ Secret secret (Address const & _address, function<std::string()> const & _pass = DontKnowThrow)
304+ {
305+ auto it = m_addrLookup.find (_address);
306+ if (it == m_addrLookup.end ())
307+ return Secret ();
308+ return secret (it->second , _pass);
309+ }
310+
311+ Secret secret (h128 const & _uuid, function<std::string()> const & _pass = DontKnowThrow)
312+ {
313+ return Secret (m_store.key (_uuid, [&](){
314+ auto it = m_cachedPasswords.find (m_keyInfo[_uuid].passHash );
315+ if (it == m_cachedPasswords.end ())
316+ {
317+ std::string p = _pass ();
318+ m_cachedPasswords[sha3 (p)] = p;
319+ return p;
320+ }
321+ else
322+ return it->second ;
323+ }));
324+ }
325+
326+ h128 import (Secret const & _s, std::string const & _pass, string const & _info = std::string(), string const & _passInfo = std::string())
327+ {
328+ Address addr = KeyPair (_s).address ();
329+ auto passHash = sha3 (_pass);
330+ m_cachedPasswords[passHash] = _pass;
331+ m_passwordInfo[passHash] = _passInfo;
332+ auto uuid = m_store.import (_s.asBytes (), _pass);
333+ m_keyInfo[uuid] = KeyInfo{passHash, _info};
334+ m_addrLookup[addr] = uuid;
335+ return uuid;
336+ }
337+
338+ h128 import (Secret const & _s, std::string const & _info = std::string())
339+ {
340+ // cache password, remember the key, remember the address
341+ return import (_s, m_password, _info, std::string ());
342+ }
343+
344+ private:
345+ // Ethereum keys.
346+ std::map<Address, h128> m_addrLookup;
347+ std::map<h128, KeyInfo> m_keyInfo;
348+ std::map<h256, std::string> m_passwordInfo;
349+
350+ // Passwords that we're storing.
351+ std::map<h256, std::string> m_cachedPasswords;
352+
353+ // The default password for keys in the keystore - protected by the master password.
354+ std::string m_password = asString(h256::random().asBytes());
355+
356+ KeyStore m_store;
357+ h128 m_key;
358+ };
359+
191360int main ()
192361{
193- cdebug << toHex (pbkdf2 (" password" , asBytes (" salt" ), 1 , 20 ));
194362 KeyManager keyman;
195- cdebug << " Secret key for 0498f19a-59db-4d54-ac95-33901b4f1870 is " << keyman.secret (fromUUID (" 0498f19a-59db-4d54-ac95-33901b4f1870" ), [](){ return " foo" ; });
363+ auto id = fromUUID (" 441193ae-a767-f1c3-48ba-dd6610db5ed0" );
364+ cdebug << " Secret key for " << toUUID (id) << " is" << keyman.secret (id, [](){ return " bar" ; });
196365}
197366
198367#elif 0
0 commit comments