monotone

monotone Mtn Source Tree

Root/keys.cc

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

Archive Download this file

Branches

Tags

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