monotone

monotone Mtn Source Tree

Root/cert.cc

1// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include "constants.hh"
7#include "cert.hh"
8#include "packet.hh"
9#include "app_state.hh"
10#include "interner.hh"
11#include "keys.hh"
12#include "netio.hh"
13#include "sanity.hh"
14#include "transforms.hh"
15#include "ui.hh"
16
17#include <boost/date_time/gregorian/gregorian.hpp>
18#include <boost/date_time/posix_time/posix_time.hpp>
19#include <boost/lexical_cast.hpp>
20#include <boost/shared_ptr.hpp>
21#include <boost/tuple/tuple.hpp>
22#include <boost/tuple/tuple_comparison.hpp>
23
24#include <string>
25#include <limits>
26#include <sstream>
27#include <vector>
28
29using namespace std;
30using namespace boost;
31
32// FIXME: the bogus-cert family of functions is ridiculous
33// and needs to be replaced, or at least factored.
34
35struct
36bogus_cert_p
37{
38 app_state & app;
39 bogus_cert_p(app_state & a) : app(a) {};
40
41 bool cert_is_bogus(cert const & c) const
42 {
43 cert_status status = check_cert(app, c);
44 if (status == cert_ok)
45 {
46 L(F("cert ok\n"));
47 return false;
48 }
49 else if (status == cert_bad)
50 {
51 string txt;
52 cert_signable_text(c, txt);
53 W(F("ignoring bad signature by '%s' on '%s'\n") % c.key() % txt);
54 return true;
55 }
56 else
57 {
58 I(status == cert_unknown);
59 string txt;
60 cert_signable_text(c, txt);
61 W(F("ignoring unknown signature by '%s' on '%s'\n") % c.key() % txt);
62 return true;
63 }
64 }
65
66 bool operator()(revision<cert> const & c) const
67 {
68 return cert_is_bogus(c.inner());
69 }
70
71 bool operator()(manifest<cert> const & c) const
72 {
73 return cert_is_bogus(c.inner());
74 }
75};
76
77
78void
79erase_bogus_certs(vector< manifest<cert> > & certs,
80 app_state & app)
81{
82 typedef vector< manifest<cert> >::iterator it;
83 it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(app));
84 certs.erase(e, certs.end());
85
86 vector< manifest<cert> > tmp_certs;
87
88 // sorry, this is a crazy data structure
89 typedef tuple< hexenc<id>, cert_name, base64<cert_value> > trust_key;
90 typedef map< trust_key, pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;
91 trust_map trust;
92
93 for (it i = certs.begin(); i != certs.end(); ++i)
94 {
95 trust_key key = trust_key(i->inner().ident, i->inner().name, i->inner().value);
96 trust_map::iterator j = trust.find(key);
97 shared_ptr< set<rsa_keypair_id> > s;
98 if (j == trust.end())
99 {
100 s.reset(new set<rsa_keypair_id>());
101 trust.insert(make_pair(key, make_pair(s, i)));
102 }
103 else
104 s = j->second.first;
105 s->insert(i->inner().key);
106 }
107
108 for (trust_map::const_iterator i = trust.begin();
109 i != trust.end(); ++i)
110 {
111 cert_value decoded_value;
112 decode_base64(get<2>(i->first), decoded_value);
113 if (app.lua.hook_get_manifest_cert_trust(*(i->second.first),
114 get<0>(i->first),
115 get<1>(i->first),
116 decoded_value))
117 {
118 L(F("trust function liked %d signers of %s cert on manifest %s\n")
119 % i->second.first->size() % get<1>(i->first) % get<0>(i->first));
120 tmp_certs.push_back(*(i->second.second));
121 }
122 else
123 {
124 W(F("trust function disliked %d signers of %s cert on manifest %s\n")
125 % i->second.first->size() % get<1>(i->first) % get<0>(i->first));
126 }
127 }
128 certs = tmp_certs;
129}
130
131void
132erase_bogus_certs(vector< revision<cert> > & certs,
133 app_state & app)
134{
135 typedef vector< revision<cert> >::iterator it;
136 it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(app));
137 certs.erase(e, certs.end());
138
139 vector< revision<cert> > tmp_certs;
140
141 // sorry, this is a crazy data structure
142 typedef tuple< hexenc<id>, cert_name, base64<cert_value> > trust_key;
143 typedef map< trust_key, pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;
144 trust_map trust;
145
146 for (it i = certs.begin(); i != certs.end(); ++i)
147 {
148 trust_key key = trust_key(i->inner().ident, i->inner().name, i->inner().value);
149 trust_map::iterator j = trust.find(key);
150 shared_ptr< set<rsa_keypair_id> > s;
151 if (j == trust.end())
152 {
153 s.reset(new set<rsa_keypair_id>());
154 trust.insert(make_pair(key, make_pair(s, i)));
155 }
156 else
157 s = j->second.first;
158 s->insert(i->inner().key);
159 }
160
161 for (trust_map::const_iterator i = trust.begin();
162 i != trust.end(); ++i)
163 {
164 cert_value decoded_value;
165 decode_base64(get<2>(i->first), decoded_value);
166 if (app.lua.hook_get_revision_cert_trust(*(i->second.first),
167 get<0>(i->first),
168 get<1>(i->first),
169 decoded_value))
170 {
171 L(F("trust function liked %d signers of %s cert on revision %s\n")
172 % i->second.first->size() % get<1>(i->first) % get<0>(i->first));
173 tmp_certs.push_back(*(i->second.second));
174 }
175 else
176 {
177 W(F("trust function disliked %d signers of %s cert on revision %s\n")
178 % i->second.first->size() % get<1>(i->first) % get<0>(i->first));
179 }
180 }
181 certs = tmp_certs;
182}
183
184
185// cert-managing routines
186
187cert::cert()
188{}
189
190cert::cert(hexenc<id> const & ident,
191 cert_name const & name,
192 base64<cert_value> const & value,
193 rsa_keypair_id const & key)
194 : ident(ident), name(name), value(value), key(key)
195{}
196
197cert::cert(hexenc<id> const & ident,
198 cert_name const & name,
199 base64<cert_value> const & value,
200 rsa_keypair_id const & key,
201 base64<rsa_sha1_signature> const & sig)
202 : ident(ident), name(name), value(value), key(key), sig(sig)
203{}
204
205bool
206cert::operator<(cert const & other) const
207{
208 return (ident < other.ident)
209 || ((ident == other.ident) && name < other.name)
210 || (((ident == other.ident) && name == other.name)
211 && value < other.value)
212 || ((((ident == other.ident) && name == other.name)
213 && value == other.value) && key < other.key)
214 || (((((ident == other.ident) && name == other.name)
215 && value == other.value) && key == other.key) && sig < other.sig);
216}
217
218bool
219cert::operator==(cert const & other) const
220{
221 return
222 (ident == other.ident)
223 && (name == other.name)
224 && (value == other.value)
225 && (key == other.key)
226 && (sig == other.sig);
227}
228
229// netio support
230
231void
232read_cert(string const & in, cert & t)
233{
234 size_t pos = 0;
235 id hash = extract_substring(in, pos,
236 constants::merkle_hash_length_in_bytes,
237 "cert hash");
238 id ident = extract_substring(in, pos,
239 constants::merkle_hash_length_in_bytes,
240 "cert ident");
241 string name, val, key, sig;
242 extract_variable_length_string(in, name, pos, "cert name");
243 extract_variable_length_string(in, val, pos, "cert val");
244 extract_variable_length_string(in, key, pos, "cert key");
245 extract_variable_length_string(in, sig, pos, "cert sig");
246 assert_end_of_buffer(in, pos, "cert");
247
248 hexenc<id> hid;
249 base64<cert_value> bval;
250 base64<rsa_sha1_signature> bsig;
251
252 encode_hexenc(ident, hid);
253 encode_base64(cert_value(val), bval);
254 encode_base64(rsa_sha1_signature(sig), bsig);
255
256 cert tmp(hid, cert_name(name), bval, rsa_keypair_id(key), bsig);
257
258 hexenc<id> hcheck;
259 id check;
260 cert_hash_code(tmp, hcheck);
261 decode_hexenc(hcheck, check);
262 if (!(check == hash))
263 {
264 hexenc<id> hhash;
265 encode_hexenc(hash, hhash);
266 throw bad_decode(F("calculated cert hash '%s' does not match '%s'")
267 % hcheck % hhash);
268 }
269 t = tmp;
270}
271
272void
273write_cert(cert const & t, string & out)
274{
275 string name, key;
276 hexenc<id> hash;
277 id ident_decoded, hash_decoded;
278 rsa_sha1_signature sig_decoded;
279 cert_value value_decoded;
280
281 cert_hash_code(t, hash);
282 decode_base64(t.value, value_decoded);
283 decode_base64(t.sig, sig_decoded);
284 decode_hexenc(t.ident, ident_decoded);
285 decode_hexenc(hash, hash_decoded);
286
287 out.append(hash_decoded());
288 out.append(ident_decoded());
289 insert_variable_length_string(t.name(), out);
290 insert_variable_length_string(value_decoded(), out);
291 insert_variable_length_string(t.key(), out);
292 insert_variable_length_string(sig_decoded(), out);
293}
294
295void
296cert_signable_text(cert const & t,
297 string & out)
298{
299 out = (F("[%s@%s:%s]") % t.name % t.ident % remove_ws(t.value())).str();
300 L(F("cert: signable text %s\n") % out);
301}
302
303void
304cert_hash_code(cert const & t, hexenc<id> & out)
305{
306 string tmp(t.ident()
307 + ":" + t.name()
308 + ":" + remove_ws(t.value())
309 + ":" + t.key()
310 + ":" + remove_ws(t.sig()));
311 data tdat(tmp);
312 calculate_ident(tdat, out);
313}
314
315bool
316priv_key_exists(app_state & app, rsa_keypair_id const & id)
317{
318
319 if (app.db.private_key_exists(id))
320 return true;
321
322 base64< arc4<rsa_priv_key> > dummy;
323
324 if (app.lua.hook_get_priv_key(id, dummy))
325 return true;
326
327 return false;
328}
329
330// Loads a private key for a given key id, from either a lua hook
331// or the database. This will bomb out if the same keyid exists
332// in both with differing contents.
333void
334load_priv_key(app_state & app,
335 rsa_keypair_id const & id,
336 base64< arc4<rsa_priv_key> > & priv)
337{
338
339 static std::map<rsa_keypair_id, base64< arc4<rsa_priv_key> > > privkeys;
340 bool persist_ok = (!privkeys.empty()) || app.lua.hook_persist_phrase_ok();
341
342
343 if (persist_ok
344 && privkeys.find(id) != privkeys.end())
345 {
346 priv = privkeys[id];
347 }
348 else
349 {
350 base64< arc4<rsa_priv_key> > dbkey, luakey;
351 bool havedb = false, havelua = false;
352
353 if (app.db.private_key_exists(id))
354 {
355 app.db.get_key(id, dbkey);
356 havedb = true;
357 }
358 havelua = app.lua.hook_get_priv_key(id, luakey);
359
360 N(havedb || havelua,
361 F("no private key '%s' found in database or get_priv_key hook") % id);
362
363 if (havelua)
364 {
365 if (havedb)
366 {
367 // We really don't want the database key and the rcfile key
368 // to differ.
369 N(remove_ws(dbkey()) == remove_ws(luakey()),
370 F("mismatch between private key '%s' in database"
371 " and get_priv_key hook") % id);
372 }
373 priv = luakey;
374 }
375 else if (havedb)
376 {
377 priv = dbkey;
378 }
379
380 if (persist_ok)
381 privkeys.insert(make_pair(id, priv));
382 }
383}
384
385void
386calculate_cert(app_state & app, cert & t)
387{
388 string signed_text;
389 base64< arc4<rsa_priv_key> > priv;
390 cert_signable_text(t, signed_text);
391
392 load_priv_key(app, t.key, priv);
393
394 make_signature(app.lua, t.key, priv, signed_text, t.sig);
395}
396
397cert_status
398check_cert(app_state & app, cert const & t)
399{
400
401 base64< rsa_pub_key > pub;
402
403 static std::map<rsa_keypair_id, base64< rsa_pub_key > > pubkeys;
404 bool persist_ok = (!pubkeys.empty()) || app.lua.hook_persist_phrase_ok();
405
406 if (persist_ok
407 && pubkeys.find(t.key) != pubkeys.end())
408 {
409 pub = pubkeys[t.key];
410 }
411 else
412 {
413 if (!app.db.public_key_exists(t.key))
414 return cert_unknown;
415 app.db.get_key(t.key, pub);
416 if (persist_ok)
417 pubkeys.insert(make_pair(t.key, pub));
418 }
419
420 string signed_text;
421 cert_signable_text(t, signed_text);
422 if (check_signature(app.lua, t.key, pub, signed_text, t.sig))
423 return cert_ok;
424 else
425 return cert_bad;
426}
427
428
429// "special certs"
430
431string const branch_cert_name("branch");
432
433bool
434guess_default_key(rsa_keypair_id & key,
435 app_state & app)
436{
437
438 if (app.signing_key() != "")
439 {
440 key = app.signing_key;
441 return true;
442 }
443
444 if (app.branch_name() != "")
445 {
446 cert_value branch(app.branch_name());
447 if (app.lua.hook_get_branch_key(branch, key))
448 return true;
449 }
450
451 vector<rsa_keypair_id> all_privkeys;
452 app.db.get_private_keys(all_privkeys);
453 if (all_privkeys.size() != 1)
454 return false;
455 else
456 {
457 key = all_privkeys[0];
458 return true;
459 }
460}
461
462void
463guess_branch(revision_id const & ident,
464 app_state & app,
465 cert_value & branchname)
466{
467 if (app.branch_name() != "")
468 {
469 branchname = app.branch_name();
470 }
471 else
472 {
473 N(!ident.inner()().empty(),
474 F("no branch found for empty revision, "
475 "please provide a branch name"));
476
477 vector< revision<cert> > certs;
478 cert_name branch(branch_cert_name);
479 app.db.get_revision_certs(ident, branch, certs);
480 erase_bogus_certs(certs, app);
481
482 N(certs.size() != 0,
483 F("no branch certs found for revision %s, "
484 "please provide a branch name") % ident);
485
486 N(certs.size() == 1,
487 F("multiple branch certs found for revision %s, "
488 "please provide a branch name") % ident);
489
490 decode_base64(certs[0].inner().value, branchname);
491 app.set_branch(branchname());
492 }
493}
494
495void
496make_simple_cert(hexenc<id> const & id,
497 cert_name const & nm,
498 cert_value const & cv,
499 app_state & app,
500 cert & c)
501{
502 rsa_keypair_id key;
503 N(guess_default_key(key,app),
504 F("no unique private key for cert construction"));
505 base64<cert_value> encoded_val;
506 encode_base64(cv, encoded_val);
507 cert t(id, nm, encoded_val, key);
508 calculate_cert(app, t);
509 c = t;
510}
511
512static void
513put_simple_revision_cert(revision_id const & id,
514 cert_name const & nm,
515 cert_value const & val,
516 app_state & app,
517 packet_consumer & pc)
518{
519 cert t;
520 make_simple_cert(id.inner(), nm, val, app, t);
521 revision<cert> cc(t);
522 pc.consume_revision_cert(cc);
523}
524
525void
526cert_revision_in_branch(revision_id const & rev,
527 cert_value const & branchname,
528 app_state & app,
529 packet_consumer & pc)
530{
531 put_simple_revision_cert (rev, branch_cert_name,
532 branchname, app, pc);
533}
534
535void
536get_branch_heads(cert_value const & branchname,
537 app_state & app,
538 set<revision_id> & heads)
539{
540 vector< revision<cert> > certs;
541 base64<cert_value> branch_encoded;
542
543 encode_base64(branchname, branch_encoded);
544 app.db.get_revision_certs(cert_name(branch_cert_name),
545 branch_encoded, certs);
546
547 erase_bogus_certs(certs, app);
548
549 heads.clear();
550
551 for (vector< revision<cert> >::const_iterator i = certs.begin();
552 i != certs.end(); ++i)
553 heads.insert(revision_id(i->inner().ident));
554
555 erase_ancestors(heads, app);
556}
557
558
559// "standard certs"
560
561string const date_cert_name = "date";
562string const author_cert_name = "author";
563string const tag_cert_name = "tag";
564string const changelog_cert_name = "changelog";
565string const comment_cert_name = "comment";
566string const testresult_cert_name = "testresult";
567
568
569void
570cert_revision_date_time(revision_id const & m,
571 boost::posix_time::ptime t,
572 app_state & app,
573 packet_consumer & pc)
574{
575 string val = boost::posix_time::to_iso_extended_string(t);
576 put_simple_revision_cert(m, date_cert_name, val, app, pc);
577}
578
579void
580cert_revision_date_time(revision_id const & m,
581 time_t t,
582 app_state & app,
583 packet_consumer & pc)
584{
585 // make sure you do all your CVS conversions by 2038!
586 boost::posix_time::ptime tmp(boost::gregorian::date(1970,1,1),
587 boost::posix_time::seconds(static_cast<long>(t)));
588 cert_revision_date_time(m, tmp, app, pc);
589}
590
591void
592cert_revision_date_now(revision_id const & m,
593 app_state & app,
594 packet_consumer & pc)
595{
596 cert_revision_date_time(m, boost::posix_time::second_clock::universal_time(), app, pc);
597}
598
599void
600cert_revision_author(revision_id const & m,
601 string const & author,
602 app_state & app,
603 packet_consumer & pc)
604{
605 put_simple_revision_cert(m, author_cert_name,
606 author, app, pc);
607}
608
609void
610cert_revision_author_default(revision_id const & m,
611 app_state & app,
612 packet_consumer & pc)
613{
614 string author;
615 if (!app.lua.hook_get_author(app.branch_name(), author))
616 {
617 rsa_keypair_id key;
618 N(guess_default_key(key, app),
619 F("no default author name for branch '%s'")
620 % app.branch_name);
621 author = key();
622 }
623 cert_revision_author(m, author, app, pc);
624}
625
626void
627cert_revision_tag(revision_id const & m,
628 string const & tagname,
629 app_state & app,
630 packet_consumer & pc)
631{
632 put_simple_revision_cert(m, tag_cert_name,
633 tagname, app, pc);
634}
635
636
637void
638cert_revision_changelog(revision_id const & m,
639 string const & changelog,
640 app_state & app,
641 packet_consumer & pc)
642{
643 put_simple_revision_cert(m, changelog_cert_name,
644 changelog, app, pc);
645}
646
647void
648cert_revision_comment(revision_id const & m,
649 string const & comment,
650 app_state & app,
651 packet_consumer & pc)
652{
653 put_simple_revision_cert(m, comment_cert_name,
654 comment, app, pc);
655}
656
657void
658cert_revision_testresult(revision_id const & r,
659 string const & results,
660 app_state & app,
661 packet_consumer & pc)
662{
663 bool passed = false;
664 if (lowercase(results) == "true" ||
665 lowercase(results) == "yes" ||
666 lowercase(results) == "pass" ||
667 results == "1")
668 passed = true;
669 else if (lowercase(results) == "false" ||
670 lowercase(results) == "no" ||
671 lowercase(results) == "fail" ||
672 results == "0")
673 passed = false;
674 else
675 throw informative_failure("could not interpret test results, tried '0/1' 'yes/no', 'true/false', 'pass/fail'");
676
677 put_simple_revision_cert(r, testresult_cert_name, lexical_cast<string>(passed), app, pc);
678}
679
680
681#ifdef BUILD_UNIT_TESTS
682#include "unit_tests.hh"
683
684#endif // BUILD_UNIT_TESTS

Archive Download this file

Branches

Tags

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