monotone

monotone Mtn Source Tree

Root/keys.cc

1#include <iostream>
2#include <string>
3#include <map>
4
5#include <unistd.h>
6#include <string.h>
7
8#include <boost/shared_ptr.hpp>
9
10#include "cryptopp/arc4.h"
11#include "cryptopp/base64.h"
12#include "cryptopp/hex.h"
13#include "cryptopp/cryptlib.h"
14#include "cryptopp/osrng.h"
15#include "cryptopp/sha.h"
16#include "cryptopp/rsa.h"
17
18#include "constants.hh"
19#include "keys.hh"
20#include "lua.hh"
21#include "netio.hh"
22#include "platform.hh"
23#include "transforms.hh"
24#include "sanity.hh"
25#include "ui.hh"
26
27// copyright (C) 2002, 2003, 2004 graydon hoare <graydon@pobox.com>
28// all rights reserved.
29// licensed to the public under the terms of the GNU GPL (>= 2)
30// see the file COPYING for details
31
32// there will probably forever be bugs in this file. it's very
33// hard to get right, portably and securely. sorry about that.
34
35using namespace CryptoPP;
36using namespace std;
37
38using boost::shared_ptr;
39
40static void
41do_arc4(SecByteBlock & phrase,
42 SecByteBlock & payload)
43{
44 L(F("running arc4 process on %d bytes of data\n") % payload.size());
45 ARC4 a4(phrase.data(), phrase.size());
46 a4.ProcessString(payload.data(), payload.size());
47}
48
49// 'force_from_user' means that we don't use the passphrase cache, and we
50// don't use the get_passphrase hook.
51static void
52get_passphrase(lua_hooks & lua,
53 rsa_keypair_id const & keyid,
54 SecByteBlock & phrase,
55 bool confirm_phrase = false,
56 bool force_from_user = false,
57 string prompt_beginning = "enter passphrase")
58{
59 string lua_phrase;
60
61 // we permit the user to relax security here, by caching a passphrase (if
62 // they permit it) through the life of a program run. this helps when
63 // you're making a half-dozen certs during a commit or merge or
64 // something.
65 bool persist_phrase = lua.hook_persist_phrase_ok();
66 static std::map<rsa_keypair_id, string> phrases;
67
68 if (!force_from_user && phrases.find(keyid) != phrases.end())
69 {
70 string phr = phrases[keyid];
71 phrase.Assign(reinterpret_cast<byte const *>(phr.data()), phr.size());
72 return;
73 }
74
75 if (!force_from_user && lua.hook_get_passphrase(keyid, lua_phrase))
76 {
77 // user is being a slob and hooking lua to return his passphrase
78 phrase.Assign(reinterpret_cast<const byte *>(lua_phrase.data()),
79 lua_phrase.size());
80 N(lua_phrase != "",
81 F("got empty passphrase from get_passphrase() hook"));
82 }
83 else
84 {
85 char pass1[constants::maxpasswd];
86 char pass2[constants::maxpasswd];
87 for (int i = 0; i < 3; ++i)
88 {
89 memset(pass1, 0, constants::maxpasswd);
90 memset(pass2, 0, constants::maxpasswd);
91 ui.ensure_clean_line();
92 read_password(prompt_beginning + " for key ID [" + keyid() + "]: ",
93 pass1, constants::maxpasswd);
94 cout << endl;
95 N(pass1[0],
96 F("empty passphrase not allowed"));
97 if (confirm_phrase)
98 {
99 ui.ensure_clean_line();
100 read_password(string("confirm passphrase for key ID [") + keyid() + "]: ",
101 pass2, constants::maxpasswd);
102 cout << endl;
103 if (strlen(pass1) == 0 || strlen(pass2) == 0)
104 {
105 P(F("empty passphrases not allowed, try again\n"));
106 N(i < 2, F("too many failed passphrases\n"));
107 }
108 else if (strcmp(pass1, pass2) == 0)
109 break;
110 else
111 {
112 P(F("passphrases do not match, try again\n"));
113 N(i < 2, F("too many failed passphrases\n"));
114 }
115 }
116 else
117 break;
118 }
119
120 try
121 {
122 phrase.Assign(reinterpret_cast<byte const *>(pass1), strlen(pass1));
123
124 // permit security relaxation. maybe.
125 if (persist_phrase)
126 {
127 phrases.insert(make_pair(keyid,string(pass1)));
128 }
129 }
130 catch (...)
131 {
132 memset(pass1, 0, constants::maxpasswd);
133 memset(pass2, 0, constants::maxpasswd);
134 throw;
135 }
136 memset(pass1, 0, constants::maxpasswd);
137 memset(pass2, 0, constants::maxpasswd);
138 }
139}
140
141template <typename T>
142static void
143write_der(T & val, SecByteBlock & sec)
144{
145 // FIXME: this helper is *wrong*. I don't see how to DER-encode into a
146 // SecByteBlock, so we may well wind up leaving raw key bytes in malloc
147 // regions if we're not lucky. but we want to. maybe muck with
148 // AllocatorWithCleanup<T>? who knows.. please fix!
149 string der_encoded;
150 try
151 {
152 StringSink der_sink(der_encoded);
153 val.DEREncode(der_sink);
154 der_sink.MessageEnd();
155 sec.Assign(reinterpret_cast<byte const *>(der_encoded.data()),
156 der_encoded.size());
157 L(F("wrote %d bytes of DER-encoded data\n") % der_encoded.size());
158 }
159 catch (...)
160 {
161 for (size_t i = 0; i < der_encoded.size(); ++i)
162 der_encoded[i] = '\0';
163 throw;
164 }
165 for (size_t i = 0; i < der_encoded.size(); ++i)
166 der_encoded[i] = '\0';
167}
168
169
170void
171generate_key_pair(lua_hooks & lua, // to hook for phrase
172 rsa_keypair_id const & id, // to prompting user for phrase
173 base64<rsa_pub_key> & pub_out,
174 base64< arc4<rsa_priv_key> > & priv_out,
175 string const unit_test_passphrase)
176{
177 // we will panic here if the user doesn't like urandom and we can't give
178 // them a real entropy-driven random.
179 bool request_blocking_rng = false;
180 if (!lua.hook_non_blocking_rng_ok())
181 {
182#ifndef BLOCKING_RNG_AVAILABLE
183 throw oops("no blocking RNG available and non-blocking RNG rejected");
184#else
185 request_blocking_rng = true;
186#endif
187 }
188
189 AutoSeededRandomPool rng(request_blocking_rng);
190 SecByteBlock phrase, pubkey, privkey;
191 rsa_pub_key raw_pub_key;
192 arc4<rsa_priv_key> raw_priv_key;
193
194 // generate private key (and encrypt it)
195 RSAES_OAEP_SHA_Decryptor priv(rng, constants::keylen);
196 write_der(priv, privkey);
197
198 if (unit_test_passphrase.empty())
199 get_passphrase(lua, id, phrase, true, true);
200 else
201 phrase.Assign(reinterpret_cast<byte const *>(unit_test_passphrase.c_str()),
202 unit_test_passphrase.size());
203 do_arc4(phrase, privkey);
204 raw_priv_key = string(reinterpret_cast<char const *>(privkey.data()),
205 privkey.size());
206
207 // generate public key
208 RSAES_OAEP_SHA_Encryptor pub(priv);
209 write_der(pub, pubkey);
210 raw_pub_key = string(reinterpret_cast<char const *>(pubkey.data()),
211 pubkey.size());
212
213 // if all that worked, we can return our results to caller
214 encode_base64(raw_priv_key, priv_out);
215 encode_base64(raw_pub_key, pub_out);
216 L(F("generated %d-byte public key\n") % pub_out().size());
217 L(F("generated %d-byte (encrypted) private key\n") % priv_out().size());
218}
219
220void
221change_key_passphrase(lua_hooks & lua,
222 rsa_keypair_id const & id,
223 base64< arc4<rsa_priv_key> > & encoded_key)
224{
225 SecByteBlock phrase;
226 get_passphrase(lua, id, phrase, false, true, "enter old passphrase");
227
228 arc4<rsa_priv_key> decoded_key;
229 SecByteBlock key_block;
230 decode_base64(encoded_key, decoded_key);
231 key_block.Assign(reinterpret_cast<byte const *>(decoded_key().data()),
232 decoded_key().size());
233 do_arc4(phrase, key_block);
234
235 try
236 {
237 L(F("building signer from %d-byte decrypted private key\n") % key_block.size());
238 StringSource keysource(key_block.data(), key_block.size(), true);
239 shared_ptr<RSASSA_PKCS1v15_SHA_Signer> signer;
240 signer = shared_ptr<RSASSA_PKCS1v15_SHA_Signer>
241 (new RSASSA_PKCS1v15_SHA_Signer(keysource));
242 }
243 catch (...)
244 {
245 throw informative_failure("failed to decrypt private RSA key, "
246 "probably incorrect passphrase");
247 }
248
249 get_passphrase(lua, id, phrase, true, true, "enter new passphrase");
250 do_arc4(phrase, key_block);
251 decoded_key = string(reinterpret_cast<char const *>(key_block.data()),
252 key_block.size());
253 encode_base64(decoded_key, encoded_key);
254}
255
256void
257make_signature(lua_hooks & lua, // to hook for phrase
258 rsa_keypair_id const & id, // to prompting user for phrase
259 base64< arc4<rsa_priv_key> > const & priv,
260 string const & tosign,
261 base64<rsa_sha1_signature> & signature)
262{
263 arc4<rsa_priv_key> decoded_key;
264 SecByteBlock decrypted_key;
265 SecByteBlock phrase;
266 string sig_string;
267
268 // we will panic here if the user doesn't like urandom and we can't give
269 // them a real entropy-driven random.
270 bool request_blocking_rng = false;
271 if (!lua.hook_non_blocking_rng_ok())
272 {
273#ifndef BLOCKING_RNG_AVAILABLE
274 throw oops("no blocking RNG available and non-blocking RNG rejected");
275#else
276 request_blocking_rng = true;
277#endif
278 }
279 AutoSeededRandomPool rng(request_blocking_rng);
280
281 // we permit the user to relax security here, by caching a decrypted key
282 // (if they permit it) through the life of a program run. this helps when
283 // you're making a half-dozen certs during a commit or merge or
284 // something.
285
286 static std::map<rsa_keypair_id, shared_ptr<RSASSA_PKCS1v15_SHA_Signer> > signers;
287 bool persist_phrase = (!signers.empty()) || lua.hook_persist_phrase_ok();
288 bool force = false;
289
290 shared_ptr<RSASSA_PKCS1v15_SHA_Signer> signer;
291 if (persist_phrase && signers.find(id) != signers.end())
292 signer = signers[id];
293
294 else
295 {
296 for (int i = 0; i < 3; ++i)
297 {
298 L(F("base64-decoding %d-byte private key\n") % priv().size());
299 decode_base64(priv, decoded_key);
300 decrypted_key.Assign(reinterpret_cast<byte const *>(decoded_key().data()),
301 decoded_key().size());
302 get_passphrase(lua, id, phrase, false, force);
303
304 try
305 {
306 do_arc4(phrase, decrypted_key);
307 L(F("building signer from %d-byte decrypted private key\n") % decrypted_key.size());
308 StringSource keysource(decrypted_key.data(), decrypted_key.size(), true);
309 signer = shared_ptr<RSASSA_PKCS1v15_SHA_Signer>
310 (new RSASSA_PKCS1v15_SHA_Signer(keysource));
311 }
312 catch (...)
313 {
314 if (i >= 2)
315 throw informative_failure("failed to decrypt private RSA key, "
316 "probably incorrect passphrase");
317 // don't use the cache bad one next time
318 force = true;
319 continue;
320 }
321
322 if (persist_phrase)
323 signers.insert(make_pair(id,signer));
324 break;
325 }
326 }
327
328 StringSource tmp(tosign, true,
329 new SignerFilter
330 (rng, *signer,
331 new StringSink(sig_string)));
332
333 L(F("produced %d-byte signature\n") % sig_string.size());
334 encode_base64(rsa_sha1_signature(sig_string), signature);
335}
336
337bool
338check_signature(lua_hooks & lua,
339 rsa_keypair_id const & id,
340 base64<rsa_pub_key> const & pub_encoded,
341 string const & alleged_text,
342 base64<rsa_sha1_signature> const & signature)
343{
344 // examine pubkey
345
346 static std::map<rsa_keypair_id, shared_ptr<RSASSA_PKCS1v15_SHA_Verifier> > verifiers;
347 bool persist_phrase = (!verifiers.empty()) || lua.hook_persist_phrase_ok();
348
349 shared_ptr<RSASSA_PKCS1v15_SHA_Verifier> verifier;
350 if (persist_phrase
351 && verifiers.find(id) != verifiers.end())
352 verifier = verifiers[id];
353
354 else
355 {
356 rsa_pub_key pub;
357 decode_base64(pub_encoded, pub);
358 SecByteBlock pub_block;
359 pub_block.Assign(reinterpret_cast<byte const *>(pub().data()), pub().size());
360 StringSource keysource(pub_block.data(), pub_block.size(), true);
361 L(F("building verifier for %d-byte pub key\n") % pub_block.size());
362 verifier = shared_ptr<RSASSA_PKCS1v15_SHA_Verifier>
363 (new RSASSA_PKCS1v15_SHA_Verifier(keysource));
364
365 if (persist_phrase)
366 verifiers.insert(make_pair(id, verifier));
367 }
368
369 // examine signature
370 rsa_sha1_signature sig_decoded;
371 decode_base64(signature, sig_decoded);
372 if (sig_decoded().size() != verifier->SignatureLength())
373 return false;
374
375 // check the text+sig against the key
376 L(F("checking %d-byte (%d decoded) signature\n") %
377 signature().size() % sig_decoded().size());
378 VerifierFilter * vf = NULL;
379
380 // crypto++ likes to use pointers in ways which boost and std:: smart
381 // pointers aren't really good with, unfortunately.
382 try
383 {
384 vf = new VerifierFilter(*verifier);
385 vf->Put(reinterpret_cast<byte const *>(sig_decoded().data()), sig_decoded().size());
386 }
387 catch (...)
388 {
389 if (vf)
390 delete vf;
391 throw;
392 }
393
394 I(vf);
395 StringSource tmp(alleged_text, true, vf);
396 return vf->GetLastResult();
397}
398
399void
400read_pubkey(string const & in,
401 rsa_keypair_id & id,
402 base64<rsa_pub_key> & pub)
403{
404 string tmp_id, tmp_key;
405 size_t pos = 0;
406 extract_variable_length_string(in, tmp_id, pos, "pubkey id");
407 extract_variable_length_string(in, tmp_key, pos, "pubkey value");
408 id = tmp_id;
409 encode_base64(rsa_pub_key(tmp_key), pub);
410}
411
412void
413write_pubkey(rsa_keypair_id const & id,
414 base64<rsa_pub_key> const & pub,
415 string & out)
416{
417 rsa_pub_key pub_tmp;
418 decode_base64(pub, pub_tmp);
419 insert_variable_length_string(id(), out);
420 insert_variable_length_string(pub_tmp(), out);
421}
422
423
424void
425key_hash_code(rsa_keypair_id const & id,
426 base64<rsa_pub_key> const & pub,
427 hexenc<id> & out)
428{
429 data tdat(id() + ":" + remove_ws(pub()));
430 calculate_ident(tdat, out);
431}
432
433void
434key_hash_code(rsa_keypair_id const & id,
435 base64< arc4<rsa_priv_key> > const & priv,
436 hexenc<id> & out)
437{
438 data tdat(id() + ":" + remove_ws(priv()));
439 calculate_ident(tdat, out);
440}
441
442void
443require_password(rsa_keypair_id const & key,
444 app_state & app)
445{
446 N(priv_key_exists(app, key),
447 F("no private key '%s' found in database or get_priv_key hook") % key);
448 N(app.db.public_key_exists(key),
449 F("no public key '%s' found in database") % key);
450 base64<rsa_pub_key> pub;
451 app.db.get_key(key, pub);
452 base64< arc4<rsa_priv_key> > priv;
453 load_priv_key(app, key, priv);
454 if (app.lua.hook_persist_phrase_ok())
455 {
456 string plaintext("hi maude");
457 base64<rsa_sha1_signature> sig;
458 make_signature(app.lua, key, priv, plaintext, sig);
459 N(check_signature(app.lua, key, pub, plaintext, sig),
460 F("passphrase for '%s' is incorrect") % key);
461 }
462}
463
464#ifdef BUILD_UNIT_TESTS
465#include "unit_tests.hh"
466
467static void
468signature_round_trip_test()
469{
470 lua_hooks lua;
471 lua.add_std_hooks();
472 lua.add_test_hooks();
473
474 BOOST_CHECKPOINT("generating key pairs");
475 rsa_keypair_id key("bob123@test.com");
476 base64<rsa_pub_key> pubkey;
477 base64< arc4<rsa_priv_key> > privkey;
478 generate_key_pair(lua, key, pubkey, privkey, "bob123@test.com");
479
480 BOOST_CHECKPOINT("signing plaintext");
481 string plaintext("test string to sign");
482 base64<rsa_sha1_signature> sig;
483 make_signature(lua, key, privkey, plaintext, sig);
484
485 BOOST_CHECKPOINT("checking signature");
486 BOOST_CHECK(check_signature(lua, key, pubkey, plaintext, sig));
487
488 string broken_plaintext = plaintext + " ...with a lie";
489 BOOST_CHECKPOINT("checking non-signature");
490 BOOST_CHECK(!check_signature(lua, key, pubkey, broken_plaintext, sig));
491}
492
493static void
494osrng_test()
495{
496 AutoSeededRandomPool rng_random(true), rng_urandom(false);
497
498 for (int round = 0; round < 20; ++round)
499 {
500 MaurerRandomnessTest t_blank, t_urandom, t_random;
501 int i = 0;
502
503 while (t_blank.BytesNeeded() != 0)
504 {
505 t_blank.Put(static_cast<byte>(0));
506 i++;
507 }
508 L(F("%d bytes blank input -> tests as %f randomness\n")
509 % i % t_blank.GetTestValue());
510
511
512 i = 0;
513 while (t_urandom.BytesNeeded() != 0)
514 {
515 t_urandom.Put(rng_urandom.GenerateByte());
516 i++;
517 }
518 L(F("%d bytes urandom-seeded input -> tests as %f randomness\n")
519 % i % t_urandom.GetTestValue());
520
521
522 i = 0;
523 while (t_random.BytesNeeded() != 0)
524 {
525 t_random.Put(rng_random.GenerateByte());
526 i++;
527 }
528
529 L(F("%d bytes random-seeded input -> tests as %f randomness\n")
530 % i % t_random.GetTestValue());
531
532 BOOST_CHECK(t_blank.GetTestValue() == 0.0);
533 BOOST_CHECK(t_urandom.GetTestValue() > 0.95);
534 BOOST_CHECK(t_random.GetTestValue() > 0.95);
535 }
536}
537
538void
539add_key_tests(test_suite * suite)
540{
541 I(suite);
542 suite->add(BOOST_TEST_CASE(&osrng_test));
543 suite->add(BOOST_TEST_CASE(&signature_round_trip_test));
544}
545
546#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status