monotone

monotone Mtn Source Tree

Root/cert.cc

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

Archive Download this file

Branches

Tags

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