monotone

monotone Mtn Source Tree

Root/keys.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <iostream>
11#include <string>
12#include <map>
13
14#include <unistd.h>
15#include <string.h>
16
17#include <boost/shared_ptr.hpp>
18
19#include "botan/botan.h"
20#include "botan/rsa.h"
21#include "botan/keypair.h"
22
23#include "constants.hh"
24#include "keys.hh"
25#include "lua_hooks.hh"
26#include "netio.hh"
27#include "platform.hh"
28#include "safe_map.hh"
29#include "transforms.hh"
30#include "simplestring_xform.hh"
31#include "sanity.hh"
32#include "ui.hh"
33#include "cert.hh"
34#include "app_state.hh"
35
36using std::cout;
37using std::endl;
38using std::make_pair;
39using std::map;
40using std::string;
41
42using boost::shared_ptr;
43using boost::shared_dynamic_cast;
44
45using Botan::byte;
46using Botan::get_cipher;
47using Botan::PKCS8_PrivateKey;
48using Botan::PK_Decryptor;
49using Botan::PK_Encryptor;
50using Botan::PK_Signer;
51using Botan::PK_Verifier;
52using Botan::Pipe;
53using Botan::RSA_PrivateKey;
54using Botan::RSA_PublicKey;
55using Botan::SecureVector;
56using Botan::X509_PublicKey;
57
58// there will probably forever be bugs in this file. it's very
59// hard to get right, portably and securely. sorry about that.
60
61static void
62do_arc4(SecureVector<Botan::byte> & sym_key,
63 SecureVector<Botan::byte> & payload)
64{
65 L(FL("running arc4 process on %d bytes of data") % payload.size());
66 Pipe enc(get_cipher("ARC4", sym_key, Botan::ENCRYPTION));
67 enc.process_msg(payload);
68 payload = enc.read_all();
69}
70
71// 'force_from_user' means that we don't use the passphrase cache, and we
72// don't use the get_passphrase hook.
73static void
74get_passphrase(lua_hooks & lua,
75 rsa_keypair_id const & keyid,
76 string & phrase,
77 bool confirm_phrase = false,
78 bool force_from_user = false,
79 string prompt_beginning = "enter passphrase")
80{
81
82 // we permit the user to relax security here, by caching a passphrase (if
83 // they permit it) through the life of a program run. this helps when
84 // you're making a half-dozen certs during a commit or merge or
85 // something.
86 bool persist_phrase = lua.hook_persist_phrase_ok();
87 static map<rsa_keypair_id, string> phrases;
88
89 if (!force_from_user && phrases.find(keyid) != phrases.end())
90 {
91 phrase = phrases[keyid];
92 return;
93 }
94
95 if (!force_from_user && lua.hook_get_passphrase(keyid, phrase))
96 {
97 // user is being a slob and hooking lua to return his passphrase
98 N(phrase != "",
99 F("got empty passphrase from get_passphrase() hook"));
100 }
101 else
102 {
103 char pass1[constants::maxpasswd];
104 char pass2[constants::maxpasswd];
105 for (int i = 0; i < 3; ++i)
106 {
107 memset(pass1, 0, constants::maxpasswd);
108 memset(pass2, 0, constants::maxpasswd);
109 ui.ensure_clean_line();
110 read_password(prompt_beginning + " for key ID [" + keyid() + "]: ",
111 pass1, constants::maxpasswd);
112 cout << endl;
113 if (strlen(pass1) == 0)
114 {
115 P(F("empty passphrase not allowed"));
116 continue;
117 }
118
119 if (confirm_phrase)
120 {
121 ui.ensure_clean_line();
122 read_password((F("confirm passphrase for key ID [%s]: ") % keyid()).str(),
123 pass2, constants::maxpasswd);
124 cout << endl;
125 if (strlen(pass1) == 0 || strlen(pass2) == 0)
126 {
127 P(F("empty passphrases not allowed, try again"));
128 N(i < 2, F("too many failed passphrases"));
129 }
130 else if (strcmp(pass1, pass2) == 0)
131 break;
132 else
133 {
134 P(F("passphrases do not match, try again"));
135 N(i < 2, F("too many failed passphrases"));
136 }
137 }
138 else
139 break;
140 }
141 N(strlen(pass1) != 0, F("no passphrase given"));
142
143 try
144 {
145 phrase = pass1;
146
147 // permit security relaxation. maybe.
148 if (persist_phrase)
149 {
150 phrases.erase(keyid);
151 safe_insert(phrases, make_pair(keyid, string(pass1)));
152 }
153 }
154 catch (...)
155 {
156 memset(pass1, 0, constants::maxpasswd);
157 memset(pass2, 0, constants::maxpasswd);
158 throw;
159 }
160 memset(pass1, 0, constants::maxpasswd);
161 memset(pass2, 0, constants::maxpasswd);
162 }
163}
164
165
166void
167generate_key_pair(lua_hooks & lua, // to hook for phrase
168 rsa_keypair_id const & id, // to prompting user for phrase
169 keypair & kp_out,
170 string const unit_test_passphrase)
171{
172
173 string phrase;
174 SecureVector<Botan::byte> pubkey, privkey;
175 rsa_pub_key raw_pub_key;
176 rsa_priv_key raw_priv_key;
177
178 // generate private key (and encrypt it)
179 RSA_PrivateKey priv(constants::keylen);
180
181 if (unit_test_passphrase.empty())
182 get_passphrase(lua, id, phrase, true, true);
183 else
184 phrase = unit_test_passphrase;
185
186 Pipe p;
187 p.start_msg();
188 Botan::PKCS8::encrypt_key(priv, p, phrase,
189 "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER);
190 raw_priv_key = rsa_priv_key(p.read_all_as_string());
191
192 // generate public key
193 Pipe p2;
194 p2.start_msg();
195 Botan::X509::encode(priv, p2, Botan::RAW_BER);
196 raw_pub_key = rsa_pub_key(p2.read_all_as_string());
197
198 // if all that worked, we can return our results to caller
199 encode_base64(raw_priv_key, kp_out.priv);
200 encode_base64(raw_pub_key, kp_out.pub);
201 L(FL("generated %d-byte public key\n"
202 "generated %d-byte (encrypted) private key\n")
203 % kp_out.pub().size()
204 % kp_out.priv().size());
205}
206
207// ask for passphrase then decrypt a private key.
208shared_ptr<RSA_PrivateKey>
209get_private_key(lua_hooks & lua,
210 rsa_keypair_id const & id,
211 base64< rsa_priv_key > const & priv,
212 bool force_from_user = false)
213{
214 rsa_priv_key decoded_key;
215 string phrase;
216 bool force = force_from_user;
217
218 L(FL("base64-decoding %d-byte private key") % priv().size());
219 decode_base64(priv, decoded_key);
220 for (int i = 0; i < 3; ++i)
221 {
222 get_passphrase(lua, id, phrase, false, force);
223 L(FL("have %d-byte encrypted private key") % decoded_key().size());
224
225 shared_ptr<PKCS8_PrivateKey> pkcs8_key;
226 try
227 {
228 Pipe p;
229 p.process_msg(decoded_key());
230 pkcs8_key = shared_ptr<PKCS8_PrivateKey>(Botan::PKCS8::load_key(p, phrase));
231 }
232 catch (...)
233 {
234 if (i >= 2)
235 throw informative_failure("failed to decrypt private RSA key, "
236 "probably incorrect passphrase");
237 // don't use the cache bad one next time
238 force = true;
239 continue;
240 }
241
242 shared_ptr<RSA_PrivateKey> priv_key;
243 priv_key = shared_dynamic_cast<RSA_PrivateKey>(pkcs8_key);
244 if (!priv_key)
245 throw informative_failure("Failed to get RSA signing key");
246
247 return priv_key;
248 }
249 I(false);
250}
251
252// converts an oldstyle arc4 encrypted key into a newstyle pkcs#8 encoded
253// key. the public key is also included
254void
255migrate_private_key(app_state & app,
256 rsa_keypair_id const & id,
257 base64< arc4<rsa_priv_key> > const & old_priv,
258 keypair & new_kp)
259{
260 arc4<rsa_priv_key> decoded_key;
261 SecureVector<Botan::byte> decrypted_key;
262 string phrase;
263
264 bool force = false;
265
266 // need to decrypt the old key
267 shared_ptr<RSA_PrivateKey> priv_key;
268 L(FL("base64-decoding %d-byte old private key") % old_priv().size());
269 decode_base64(old_priv, decoded_key);
270 for (int i = 0; i < 3; ++i)
271 {
272 decrypted_key.set(reinterpret_cast<Botan::byte const *>(decoded_key().data()),
273 decoded_key().size());
274 get_passphrase(app.lua, id, phrase, false, force);
275 SecureVector<Botan::byte> sym_key;
276 sym_key.set(reinterpret_cast<Botan::byte const *>(phrase.data()), phrase.size());
277 do_arc4(sym_key, decrypted_key);
278
279 L(FL("building signer from %d-byte decrypted private key") % decrypted_key.size());
280
281 shared_ptr<PKCS8_PrivateKey> pkcs8_key;
282 try
283 {
284 Pipe p;
285 p.process_msg(decrypted_key);
286 pkcs8_key = shared_ptr<PKCS8_PrivateKey>(Botan::PKCS8::load_key(p, "", false));
287 }
288 catch (...)
289 {
290 if (i >= 2)
291 throw informative_failure("failed to decrypt old private RSA key, "
292 "probably incorrect passphrase");
293 // don't use the cache bad one next time
294 force = true;
295 continue;
296 }
297
298 priv_key = shared_dynamic_cast<RSA_PrivateKey>(pkcs8_key);
299 if (!priv_key)
300 throw informative_failure("Failed to get old RSA key");
301 }
302
303 I(priv_key);
304
305 // now we can write out the new key
306 Pipe p;
307 p.start_msg();
308 Botan::PKCS8::encrypt_key(*priv_key, p, phrase,
309 "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER);
310 rsa_priv_key raw_priv = rsa_priv_key(p.read_all_as_string());
311 encode_base64(raw_priv, new_kp.priv);
312
313 // also the public portion
314 Pipe p2;
315 p2.start_msg();
316 Botan::X509::encode(*priv_key, p2, Botan::RAW_BER);
317 rsa_pub_key raw_pub = rsa_pub_key(p2.read_all_as_string());
318 encode_base64(raw_pub, new_kp.pub);
319}
320
321void
322change_key_passphrase(lua_hooks & lua,
323 rsa_keypair_id const & id,
324 base64< rsa_priv_key > & encoded_key)
325{
326 shared_ptr<RSA_PrivateKey> priv = get_private_key(lua, id, encoded_key, true);
327
328 string new_phrase;
329 get_passphrase(lua, id, new_phrase, true, true, "enter new passphrase");
330
331 Pipe p;
332 p.start_msg();
333 Botan::PKCS8::encrypt_key(*priv, p, new_phrase,
334 "PBE-PKCS5v20(SHA-1,TripleDES/CBC)", Botan::RAW_BER);
335 rsa_priv_key decoded_key = rsa_priv_key(p.read_all_as_string());
336
337 encode_base64(decoded_key, encoded_key);
338}
339
340void
341make_signature(app_state & app, // to hook for phrase
342 rsa_keypair_id const & id, // to prompting user for phrase
343 base64< rsa_priv_key > const & priv,
344 string const & tosign,
345 base64<rsa_sha1_signature> & signature)
346{
347 SecureVector<Botan::byte> sig;
348 string sig_string;
349
350 // we permit the user to relax security here, by caching a decrypted key
351 // (if they permit it) through the life of a program run. this helps when
352 // you're making a half-dozen certs during a commit or merge or
353 // something.
354
355 bool persist_phrase = (!app.signers.empty()) || app.lua.hook_persist_phrase_ok();
356
357 shared_ptr<PK_Signer> signer;
358 shared_ptr<RSA_PrivateKey> priv_key;
359 if (persist_phrase && app.signers.find(id) != app.signers.end())
360 signer = app.signers[id].first;
361
362 else
363 {
364 priv_key = get_private_key(app.lua, id, priv);
365 signer = shared_ptr<PK_Signer>(get_pk_signer(*priv_key, "EMSA3(SHA-1)"));
366
367 /* XXX This is ugly. We need to keep the key around as long
368 * as the signer is around, but the shared_ptr for the key will go
369 * away after we leave this scope. Hence we store a pair of
370 * <verifier,key> so they both exist. */
371 if (persist_phrase)
372 app.signers.insert(make_pair(id,make_pair(signer,priv_key)));
373 }
374
375 sig = signer->sign_message(reinterpret_cast<Botan::byte const *>(tosign.data()), tosign.size());
376 sig_string = string(reinterpret_cast<char const*>(sig.begin()), sig.size());
377
378 L(FL("produced %d-byte signature") % sig_string.size());
379 encode_base64(rsa_sha1_signature(sig_string), signature);
380}
381
382bool
383check_signature(app_state &app,
384 rsa_keypair_id const & id,
385 base64<rsa_pub_key> const & pub_encoded,
386 string const & alleged_text,
387 base64<rsa_sha1_signature> const & signature)
388{
389 // examine pubkey
390
391 bool persist_phrase = (!app.verifiers.empty()) || app.lua.hook_persist_phrase_ok();
392
393 shared_ptr<PK_Verifier> verifier;
394 shared_ptr<RSA_PublicKey> pub_key;
395 if (persist_phrase
396 && app.verifiers.find(id) != app.verifiers.end())
397 verifier = app.verifiers[id].first;
398
399 else
400 {
401 rsa_pub_key pub;
402 decode_base64(pub_encoded, pub);
403 SecureVector<Botan::byte> pub_block;
404 pub_block.set(reinterpret_cast<Botan::byte const *>(pub().data()), pub().size());
405
406 L(FL("building verifier for %d-byte pub key") % pub_block.size());
407 shared_ptr<X509_PublicKey> x509_key =
408 shared_ptr<X509_PublicKey>(Botan::X509::load_key(pub_block));
409 pub_key = shared_dynamic_cast<RSA_PublicKey>(x509_key);
410 if (!pub_key)
411 throw informative_failure("Failed to get RSA verifying key");
412
413 verifier = shared_ptr<PK_Verifier>(get_pk_verifier(*pub_key, "EMSA3(SHA-1)"));
414
415 /* XXX This is ugly. We need to keep the key around
416 * as long as the verifier is around, but the shared_ptr will go
417 * away after we leave this scope. Hence we store a pair of
418 * <verifier,key> so they both exist. */
419 if (persist_phrase)
420 app.verifiers.insert(make_pair(id, make_pair(verifier, pub_key)));
421 }
422
423 // examine signature
424 rsa_sha1_signature sig_decoded;
425 decode_base64(signature, sig_decoded);
426
427 // check the text+sig against the key
428 L(FL("checking %d-byte (%d decoded) signature") %
429 signature().size() % sig_decoded().size());
430
431 bool valid_sig = verifier->verify_message(
432 reinterpret_cast<Botan::byte const*>(alleged_text.data()), alleged_text.size(),
433 reinterpret_cast<Botan::byte const*>(sig_decoded().data()), sig_decoded().size());
434
435 return valid_sig;
436}
437
438void encrypt_rsa(lua_hooks & lua,
439 rsa_keypair_id const & id,
440 base64<rsa_pub_key> & pub_encoded,
441 string const & plaintext,
442 rsa_oaep_sha_data & ciphertext)
443{
444 rsa_pub_key pub;
445 decode_base64(pub_encoded, pub);
446 SecureVector<Botan::byte> pub_block;
447 pub_block.set(reinterpret_cast<Botan::byte const *>(pub().data()), pub().size());
448
449 shared_ptr<X509_PublicKey> x509_key = shared_ptr<X509_PublicKey>(Botan::X509::load_key(pub_block));
450 shared_ptr<RSA_PublicKey> pub_key = shared_dynamic_cast<RSA_PublicKey>(x509_key);
451 if (!pub_key)
452 throw informative_failure("Failed to get RSA encrypting key");
453
454 shared_ptr<PK_Encryptor> encryptor;
455 encryptor = shared_ptr<PK_Encryptor>(get_pk_encryptor(*pub_key, "EME1(SHA-1)"));
456
457 SecureVector<Botan::byte> ct;
458 ct = encryptor->encrypt(
459 reinterpret_cast<Botan::byte const *>(plaintext.data()), plaintext.size());
460 ciphertext = rsa_oaep_sha_data(string(reinterpret_cast<char const *>(ct.begin()), ct.size()));
461}
462
463void decrypt_rsa(lua_hooks & lua,
464 rsa_keypair_id const & id,
465 base64< rsa_priv_key > const & priv,
466 rsa_oaep_sha_data const & ciphertext,
467 string & plaintext)
468{
469 shared_ptr<RSA_PrivateKey> priv_key = get_private_key(lua, id, priv);
470
471 shared_ptr<PK_Decryptor> decryptor;
472 decryptor = shared_ptr<PK_Decryptor>(get_pk_decryptor(*priv_key, "EME1(SHA-1)"));
473
474 SecureVector<Botan::byte> plain;
475 plain = decryptor->decrypt(
476 reinterpret_cast<Botan::byte const *>(ciphertext().data()), ciphertext().size());
477 plaintext = string(reinterpret_cast<char const*>(plain.begin()), plain.size());
478}
479
480void
481read_pubkey(string const & in,
482 rsa_keypair_id & id,
483 base64<rsa_pub_key> & pub)
484{
485 string tmp_id, tmp_key;
486 size_t pos = 0;
487 extract_variable_length_string(in, tmp_id, pos, "pubkey id");
488 extract_variable_length_string(in, tmp_key, pos, "pubkey value");
489 id = tmp_id;
490 encode_base64(rsa_pub_key(tmp_key), pub);
491}
492
493void
494write_pubkey(rsa_keypair_id const & id,
495 base64<rsa_pub_key> const & pub,
496 string & out)
497{
498 rsa_pub_key pub_tmp;
499 decode_base64(pub, pub_tmp);
500 insert_variable_length_string(id(), out);
501 insert_variable_length_string(pub_tmp(), out);
502}
503
504
505void
506key_hash_code(rsa_keypair_id const & ident,
507 base64<rsa_pub_key> const & pub,
508 hexenc<id> & out)
509{
510 data tdat(ident() + ":" + remove_ws(pub()));
511 calculate_ident(tdat, out);
512}
513
514void
515key_hash_code(rsa_keypair_id const & ident,
516 base64< rsa_priv_key > const & priv,
517 hexenc<id> & out)
518{
519 data tdat(ident() + ":" + remove_ws(priv()));
520 calculate_ident(tdat, out);
521}
522
523// helper to compare if two keys have the same hash
524// (ie are the same key)
525bool
526keys_match(rsa_keypair_id const & id1,
527 base64<rsa_pub_key> const & key1,
528 rsa_keypair_id const & id2,
529 base64<rsa_pub_key> const & key2)
530{
531 hexenc<id> hash1, hash2;
532 key_hash_code(id1, key1, hash1);
533 key_hash_code(id2, key2, hash2);
534 return hash1 == hash2;
535}
536
537bool
538keys_match(rsa_keypair_id const & id1,
539 base64< rsa_priv_key > const & key1,
540 rsa_keypair_id const & id2,
541 base64< rsa_priv_key > const & key2)
542{
543 hexenc<id> hash1, hash2;
544 key_hash_code(id1, key1, hash1);
545 key_hash_code(id2, key2, hash2);
546 return hash1 == hash2;
547}
548
549void
550require_password(rsa_keypair_id const & key,
551 app_state & app)
552{
553 N(priv_key_exists(app, key),
554 F("no key pair '%s' found in key store '%s'")
555 % key % app.keys.get_key_dir());
556 keypair kp;
557 load_key_pair(app, key, kp);
558 if (app.lua.hook_persist_phrase_ok())
559 {
560 string plaintext("hi maude");
561 base64<rsa_sha1_signature> sig;
562 make_signature(app, key, kp.priv, plaintext, sig);
563 N(check_signature(app, key, kp.pub, plaintext, sig),
564 F("passphrase for '%s' is incorrect") % key);
565 }
566}
567
568#ifdef BUILD_UNIT_TESTS
569#include "unit_tests.hh"
570
571static void
572arc4_test()
573{
574
575 string pt("new fascist tidiness regime in place");
576 string phr("still spring water");
577
578 SecureVector<Botan::byte> phrase(reinterpret_cast<Botan::byte const*>(phr.data()),
579 phr.size());
580
581 SecureVector<Botan::byte> orig(reinterpret_cast<Botan::byte const*>(pt.data()),
582 pt.size());
583
584 SecureVector<Botan::byte> data(orig);
585
586 BOOST_CHECKPOINT("encrypting data");
587 do_arc4(phrase, data);
588
589 BOOST_CHECK(data != orig);
590
591 BOOST_CHECKPOINT("decrypting data");
592 do_arc4(phrase, data);
593
594 BOOST_CHECK(data == orig);
595
596}
597
598static void
599signature_round_trip_test()
600{
601 app_state app;
602 app.lua.add_std_hooks();
603 app.lua.add_test_hooks();
604
605 BOOST_CHECKPOINT("generating key pairs");
606 rsa_keypair_id key("bob123@test.com");
607 keypair kp;
608 generate_key_pair(app.lua, key, kp, "bob123@test.com");
609
610 BOOST_CHECKPOINT("signing plaintext");
611 string plaintext("test string to sign");
612 base64<rsa_sha1_signature> sig;
613 make_signature(app, key, kp.priv, plaintext, sig);
614
615 BOOST_CHECKPOINT("checking signature");
616 BOOST_CHECK(check_signature(app, key, kp.pub, plaintext, sig));
617
618 string broken_plaintext = plaintext + " ...with a lie";
619 BOOST_CHECKPOINT("checking non-signature");
620 BOOST_CHECK(!check_signature(app, key, kp.pub, broken_plaintext, sig));
621}
622
623void
624add_key_tests(test_suite * suite)
625{
626 I(suite);
627 suite->add(BOOST_TEST_CASE(&arc4_test));
628 suite->add(BOOST_TEST_CASE(&signature_round_trip_test));
629}
630
631#endif // BUILD_UNIT_TESTS
632
633// Local Variables:
634// mode: C++
635// fill-column: 76
636// c-file-style: "gnu"
637// indent-tabs-mode: nil
638// End:
639// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

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