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 "base.hh"␊ |
11 | #include <limits>␊ |
12 | #include <sstream>␊ |
13 | #include "vector.hh"␊ |
14 | ␊ |
15 | #include <boost/shared_ptr.hpp>␊ |
16 | #include <boost/tuple/tuple.hpp>␊ |
17 | #include <boost/tuple/tuple_comparison.hpp>␊ |
18 | ␊ |
19 | #include "lexical_cast.hh"␊ |
20 | #include "cert.hh"␊ |
21 | #include "constants.hh"␊ |
22 | #include "database.hh"␊ |
23 | #include "interner.hh"␊ |
24 | #include "keys.hh"␊ |
25 | #include "key_store.hh"␊ |
26 | #include "netio.hh"␊ |
27 | #include "options.hh"␊ |
28 | #include "project.hh"␊ |
29 | #include "revision.hh"␊ |
30 | #include "sanity.hh"␊ |
31 | #include "simplestring_xform.hh"␊ |
32 | #include "transforms.hh"␊ |
33 | #include "ui.hh"␊ |
34 | ␊ |
35 | using std::make_pair;␊ |
36 | using std::map;␊ |
37 | using std::pair;␊ |
38 | using std::set;␊ |
39 | using std::string;␊ |
40 | using std::vector;␊ |
41 | using std::remove_if;␊ |
42 | ␊ |
43 | using boost::shared_ptr;␊ |
44 | using boost::get;␊ |
45 | using boost::tuple;␊ |
46 | using boost::lexical_cast;␊ |
47 | ␊ |
48 | // The alternaive is to #include "cert.hh" in vocab.*, which is even␊ |
49 | // uglier.␊ |
50 | ␊ |
51 | #include "vocab_macros.hh"␊ |
52 | cc_DECORATE(revision)␊ |
53 | cc_DECORATE(manifest)␊ |
54 | template <typename T>␊ |
55 | static inline void␊ |
56 | verify(T & val)␊ |
57 | {}␊ |
58 | template class revision<cert>;␊ |
59 | template class manifest<cert>;␊ |
60 | ␊ |
61 | // FIXME: the bogus-cert family of functions is ridiculous␊ |
62 | // and needs to be replaced, or at least factored.␊ |
63 | ␊ |
64 | struct␊ |
65 | bogus_cert_p␊ |
66 | {␊ |
67 | database & db;␊ |
68 | bogus_cert_p(database & db) : db(db) {};␊ |
69 | ␊ |
70 | bool cert_is_bogus(cert const & c) const␊ |
71 | {␊ |
72 | cert_status status = check_cert(db, c);␊ |
73 | if (status == cert_ok)␊ |
74 | {␊ |
75 | L(FL("cert ok"));␊ |
76 | return false;␊ |
77 | }␊ |
78 | else if (status == cert_bad)␊ |
79 | {␊ |
80 | string txt;␊ |
81 | cert_signable_text(c, txt);␊ |
82 | W(F("ignoring bad signature by '%s' on '%s'") % c.key() % txt);␊ |
83 | return true;␊ |
84 | }␊ |
85 | else␊ |
86 | {␊ |
87 | I(status == cert_unknown);␊ |
88 | string txt;␊ |
89 | cert_signable_text(c, txt);␊ |
90 | W(F("ignoring unknown signature by '%s' on '%s'") % c.key() % txt);␊ |
91 | return true;␊ |
92 | }␊ |
93 | }␊ |
94 | ␊ |
95 | bool operator()(revision<cert> const & c) const␊ |
96 | {␊ |
97 | return cert_is_bogus(c.inner());␊ |
98 | }␊ |
99 | ␊ |
100 | bool operator()(manifest<cert> const & c) const␊ |
101 | {␊ |
102 | return cert_is_bogus(c.inner());␊ |
103 | }␊ |
104 | };␊ |
105 | ␊ |
106 | void␊ |
107 | erase_bogus_certs(database & db,␊ |
108 | vector< manifest<cert> > & certs)␊ |
109 | {␊ |
110 | typedef vector< manifest<cert> >::iterator it;␊ |
111 | it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(db));␊ |
112 | certs.erase(e, certs.end());␊ |
113 | ␊ |
114 | vector< manifest<cert> > tmp_certs;␊ |
115 | ␊ |
116 | // Sorry, this is a crazy data structure␊ |
117 | typedef tuple< manifest_id, cert_name, cert_value > trust_key;␊ |
118 | typedef map< trust_key, ␊ |
119 | pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;␊ |
120 | trust_map trust;␊ |
121 | ␊ |
122 | for (it i = certs.begin(); i != certs.end(); ++i)␊ |
123 | {␊ |
124 | trust_key key = trust_key(manifest_id(i->inner().ident.inner()),␊ |
125 | i->inner().name,␊ |
126 | i->inner().value);␊ |
127 | trust_map::iterator j = trust.find(key);␊ |
128 | shared_ptr< set<rsa_keypair_id> > s;␊ |
129 | if (j == trust.end())␊ |
130 | {␊ |
131 | s.reset(new set<rsa_keypair_id>());␊ |
132 | trust.insert(make_pair(key, make_pair(s, i)));␊ |
133 | }␊ |
134 | else␊ |
135 | s = j->second.first;␊ |
136 | s->insert(i->inner().key);␊ |
137 | }␊ |
138 | ␊ |
139 | for (trust_map::const_iterator i = trust.begin();␊ |
140 | i != trust.end(); ++i)␊ |
141 | {␊ |
142 | if (db.hook_get_manifest_cert_trust(*(i->second.first),␊ |
143 | get<0>(i->first),␊ |
144 | get<1>(i->first),␊ |
145 | get<2>(i->first)))␊ |
146 | {␊ |
147 | if (global_sanity.debug_p())␊ |
148 | L(FL("trust function liked %d signers of %s cert on manifest %s")␊ |
149 | % i->second.first->size()␊ |
150 | % get<1>(i->first)␊ |
151 | % get<0>(i->first));␊ |
152 | tmp_certs.push_back(*(i->second.second));␊ |
153 | }␊ |
154 | else␊ |
155 | {␊ |
156 | W(F("trust function disliked %d signers of %s cert on manifest %s")␊ |
157 | % i->second.first->size()␊ |
158 | % get<1>(i->first)␊ |
159 | % get<0>(i->first));␊ |
160 | }␊ |
161 | }␊ |
162 | certs = tmp_certs;␊ |
163 | }␊ |
164 | ␊ |
165 | void␊ |
166 | erase_bogus_certs(database & db,␊ |
167 | vector< revision<cert> > & certs)␊ |
168 | {␊ |
169 | typedef vector< revision<cert> >::iterator it;␊ |
170 | it e = remove_if(certs.begin(), certs.end(), bogus_cert_p(db));␊ |
171 | certs.erase(e, certs.end());␊ |
172 | ␊ |
173 | vector< revision<cert> > tmp_certs;␊ |
174 | ␊ |
175 | // sorry, this is a crazy data structure␊ |
176 | typedef tuple< revision_id, cert_name, cert_value > trust_key;␊ |
177 | typedef map< trust_key, ␊ |
178 | pair< shared_ptr< set<rsa_keypair_id> >, it > > trust_map;␊ |
179 | trust_map trust;␊ |
180 | ␊ |
181 | for (it i = certs.begin(); i != certs.end(); ++i)␊ |
182 | {␊ |
183 | trust_key key = trust_key(i->inner().ident, ␊ |
184 | i->inner().name, ␊ |
185 | i->inner().value);␊ |
186 | trust_map::iterator j = trust.find(key);␊ |
187 | shared_ptr< set<rsa_keypair_id> > s;␊ |
188 | if (j == trust.end())␊ |
189 | {␊ |
190 | s.reset(new set<rsa_keypair_id>());␊ |
191 | trust.insert(make_pair(key, make_pair(s, i)));␊ |
192 | }␊ |
193 | else␊ |
194 | s = j->second.first;␊ |
195 | s->insert(i->inner().key);␊ |
196 | }␊ |
197 | ␊ |
198 | for (trust_map::const_iterator i = trust.begin();␊ |
199 | i != trust.end(); ++i)␊ |
200 | {␊ |
201 | if (db.hook_get_revision_cert_trust(*(i->second.first),␊ |
202 | get<0>(i->first),␊ |
203 | get<1>(i->first),␊ |
204 | get<2>(i->first)))␊ |
205 | {␊ |
206 | if (global_sanity.debug_p())␊ |
207 | L(FL("trust function liked %d signers of %s cert on revision %s")␊ |
208 | % i->second.first->size()␊ |
209 | % get<1>(i->first)␊ |
210 | % get<0>(i->first));␊ |
211 | tmp_certs.push_back(*(i->second.second));␊ |
212 | }␊ |
213 | else␊ |
214 | {␊ |
215 | W(F("trust function disliked %d signers of %s cert on revision %s")␊ |
216 | % i->second.first->size()␊ |
217 | % get<1>(i->first)␊ |
218 | % get<0>(i->first));␊ |
219 | }␊ |
220 | }␊ |
221 | certs = tmp_certs;␊ |
222 | }␊ |
223 | ␊ |
224 | ␊ |
225 | // cert-managing routines␊ |
226 | ␊ |
227 | cert::cert()␊ |
228 | {}␊ |
229 | ␊ |
230 | cert::cert(std::string const & s)␊ |
231 | {␊ |
232 | read_cert(s, *this);␊ |
233 | }␊ |
234 | ␊ |
235 | cert::cert(revision_id const & ident,␊ |
236 | cert_name const & name,␊ |
237 | cert_value const & value,␊ |
238 | rsa_keypair_id const & key)␊ |
239 | : ident(ident), name(name), value(value), key(key)␊ |
240 | {}␊ |
241 | ␊ |
242 | cert::cert(revision_id const & ident,␊ |
243 | cert_name const & name,␊ |
244 | cert_value const & value,␊ |
245 | rsa_keypair_id const & key,␊ |
246 | rsa_sha1_signature const & sig)␊ |
247 | : ident(ident), name(name), value(value), key(key), sig(sig)␊ |
248 | {}␊ |
249 | ␊ |
250 | bool␊ |
251 | cert::operator<(cert const & other) const␊ |
252 | {␊ |
253 | return (ident < other.ident)␊ |
254 | || ((ident == other.ident) && name < other.name)␊ |
255 | || (((ident == other.ident) && name == other.name)␊ |
256 | && value < other.value)␊ |
257 | || ((((ident == other.ident) && name == other.name)␊ |
258 | && value == other.value) && key < other.key)␊ |
259 | || (((((ident == other.ident) && name == other.name)␊ |
260 | && value == other.value) && key == other.key) && sig < other.sig);␊ |
261 | }␊ |
262 | ␊ |
263 | bool␊ |
264 | cert::operator==(cert const & other) const␊ |
265 | {␊ |
266 | return␊ |
267 | (ident == other.ident)␊ |
268 | && (name == other.name)␊ |
269 | && (value == other.value)␊ |
270 | && (key == other.key)␊ |
271 | && (sig == other.sig);␊ |
272 | }␊ |
273 | ␊ |
274 | // netio support␊ |
275 | ␊ |
276 | void␊ |
277 | read_cert(string const & in, cert & t)␊ |
278 | {␊ |
279 | size_t pos = 0;␊ |
280 | id hash = id(extract_substring(in, pos,␊ |
281 | constants::merkle_hash_length_in_bytes,␊ |
282 | "cert hash"));␊ |
283 | revision_id ident = revision_id(extract_substring(in, pos,␊ |
284 | constants::merkle_hash_length_in_bytes,␊ |
285 | "cert ident"));␊ |
286 | string name, val, key, sig;␊ |
287 | extract_variable_length_string(in, name, pos, "cert name");␊ |
288 | extract_variable_length_string(in, val, pos, "cert val");␊ |
289 | extract_variable_length_string(in, key, pos, "cert key");␊ |
290 | extract_variable_length_string(in, sig, pos, "cert sig");␊ |
291 | assert_end_of_buffer(in, pos, "cert");␊ |
292 | ␊ |
293 | cert tmp(ident, cert_name(name), cert_value(val), rsa_keypair_id(key),␊ |
294 | rsa_sha1_signature(sig));␊ |
295 | ␊ |
296 | id check;␊ |
297 | cert_hash_code(tmp, check);␊ |
298 | if (!(check == hash))␊ |
299 | throw bad_decode(F("calculated cert hash '%s' does not match '%s'")␊ |
300 | % check % hash);␊ |
301 | t = tmp;␊ |
302 | }␊ |
303 | ␊ |
304 | void␊ |
305 | write_cert(cert const & t, string & out)␊ |
306 | {␊ |
307 | string name, key;␊ |
308 | id hash;␊ |
309 | ␊ |
310 | cert_hash_code(t, hash);␊ |
311 | ␊ |
312 | out.append(hash());␊ |
313 | out.append(t.ident.inner()());␊ |
314 | insert_variable_length_string(t.name(), out);␊ |
315 | insert_variable_length_string(t.value(), out);␊ |
316 | insert_variable_length_string(t.key(), out);␊ |
317 | insert_variable_length_string(t.sig(), out);␊ |
318 | }␊ |
319 | ␊ |
320 | void␊ |
321 | cert_signable_text(cert const & t, string & out)␊ |
322 | {␊ |
323 | base64<cert_value> val_encoded(encode_base64(t.value));␊ |
324 | string ident_encoded(encode_hexenc(t.ident.inner()()));␊ |
325 | ␊ |
326 | out.clear();␊ |
327 | out.reserve(4 + t.name().size() + ident_encoded.size()␊ |
328 | + val_encoded().size());␊ |
329 | ␊ |
330 | out += '[';␊ |
331 | out.append(t.name());␊ |
332 | out += '@';␊ |
333 | out.append(ident_encoded);␊ |
334 | out += ':';␊ |
335 | append_without_ws(out, val_encoded());␊ |
336 | out += ']';␊ |
337 | ␊ |
338 | L(FL("cert: signable text %s") % out);␊ |
339 | }␊ |
340 | ␊ |
341 | void␊ |
342 | cert_hash_code(cert const & t, id & out)␊ |
343 | {␊ |
344 | base64<rsa_sha1_signature> sig_encoded(encode_base64(t.sig));␊ |
345 | base64<cert_value> val_encoded(encode_base64(t.value));␊ |
346 | string ident_encoded(encode_hexenc(t.ident.inner()()));␊ |
347 | string tmp;␊ |
348 | tmp.reserve(4 + ident_encoded.size()␊ |
349 | + t.name().size() + val_encoded().size()␊ |
350 | + t.key().size() + sig_encoded().size());␊ |
351 | tmp.append(ident_encoded);␊ |
352 | tmp += ':';␊ |
353 | tmp.append(t.name());␊ |
354 | tmp += ':';␊ |
355 | append_without_ws(tmp, val_encoded());␊ |
356 | tmp += ':';␊ |
357 | tmp.append(t.key());␊ |
358 | tmp += ':';␊ |
359 | append_without_ws(tmp, sig_encoded());␊ |
360 | ␊ |
361 | data tdat(tmp);␊ |
362 | calculate_ident(tdat, out);␊ |
363 | }␊ |
364 | ␊ |
365 | cert_status␊ |
366 | check_cert(database & db, cert const & t)␊ |
367 | {␊ |
368 | string signed_text;␊ |
369 | cert_signable_text(t, signed_text);␊ |
370 | return db.check_signature(t.key, signed_text, t.sig);␊ |
371 | }␊ |
372 | ␊ |
373 | bool␊ |
374 | put_simple_revision_cert(database & db,␊ |
375 | key_store & keys,␊ |
376 | revision_id const & id,␊ |
377 | cert_name const & nm,␊ |
378 | cert_value const & val)␊ |
379 | {␊ |
380 | I(!keys.signing_key().empty());␊ |
381 | ␊ |
382 | cert t(id, nm, val, keys.signing_key);␊ |
383 | string signed_text;␊ |
384 | cert_signable_text(t, signed_text);␊ |
385 | load_key_pair(keys, t.key);␊ |
386 | keys.make_signature(db, t.key, signed_text, t.sig);␊ |
387 | ␊ |
388 | revision<cert> cc(t);␊ |
389 | return db.put_revision_cert(cc);␊ |
390 | }␊ |
391 | ␊ |
392 | // "special certs"␊ |
393 | ␊ |
394 | // Guess which branch is appropriate for a commit below IDENT.␊ |
395 | // OPTS may override. Branch name is returned in BRANCHNAME.␊ |
396 | // Does not modify branch state in OPTS.␊ |
397 | void␊ |
398 | guess_branch(options & opts, project_t & project,␊ |
399 | revision_id const & ident, branch_name & branchname)␊ |
400 | {␊ |
401 | if (opts.branch_given && !opts.branchname().empty())␊ |
402 | branchname = opts.branchname;␊ |
403 | else␊ |
404 | {␊ |
405 | N(!ident.inner()().empty(),␊ |
406 | F("no branch found for empty revision, "␊ |
407 | "please provide a branch name"));␊ |
408 | ␊ |
409 | set<branch_name> branches;␊ |
410 | project.get_revision_branches(ident, branches);␊ |
411 | ␊ |
412 | N(branches.size() != 0,␊ |
413 | F("no branch certs found for revision %s, "␊ |
414 | "please provide a branch name") % ident);␊ |
415 | ␊ |
416 | N(branches.size() == 1,␊ |
417 | F("multiple branch certs found for revision %s, "␊ |
418 | "please provide a branch name") % ident);␊ |
419 | ␊ |
420 | set<branch_name>::iterator i = branches.begin();␊ |
421 | I(i != branches.end());␊ |
422 | branchname = *i;␊ |
423 | }␊ |
424 | }␊ |
425 | ␊ |
426 | // As above, but set the branch name in the options␊ |
427 | // if it wasn't already set.␊ |
428 | void␊ |
429 | guess_branch(options & opts, project_t & project, revision_id const & ident)␊ |
430 | {␊ |
431 | branch_name branchname;␊ |
432 | guess_branch(opts, project, ident, branchname);␊ |
433 | opts.branchname = branchname;␊ |
434 | }␊ |
435 | ␊ |
436 | void␊ |
437 | cert_revision_in_branch(database & db,␊ |
438 | key_store & keys,␊ |
439 | revision_id const & rev,␊ |
440 | branch_name const & branch)␊ |
441 | {␊ |
442 | put_simple_revision_cert(db, keys, rev, branch_cert_name,␊ |
443 | cert_value(branch()));␊ |
444 | }␊ |
445 | ␊ |
446 | void␊ |
447 | cert_revision_suspended_in_branch(database & db,␊ |
448 | key_store & keys,␊ |
449 | revision_id const & rev,␊ |
450 | branch_name const & branch)␊ |
451 | {␊ |
452 | put_simple_revision_cert(db, keys, rev, suspend_cert_name,␊ |
453 | cert_value(branch()));␊ |
454 | }␊ |
455 | ␊ |
456 | ␊ |
457 | // "standard certs"␊ |
458 | ␊ |
459 | void␊ |
460 | cert_revision_date_time(database & db,␊ |
461 | key_store & keys,␊ |
462 | revision_id const & rev,␊ |
463 | date_t const & t)␊ |
464 | {␊ |
465 | cert_value val = cert_value(t.as_iso_8601_extended());␊ |
466 | put_simple_revision_cert(db, keys, rev, date_cert_name, val);␊ |
467 | }␊ |
468 | ␊ |
469 | void␊ |
470 | cert_revision_author(database & db,␊ |
471 | key_store & keys,␊ |
472 | revision_id const & rev,␊ |
473 | string const & author)␊ |
474 | {␊ |
475 | put_simple_revision_cert(db, keys, rev, author_cert_name,␊ |
476 | cert_value(author));␊ |
477 | }␊ |
478 | ␊ |
479 | void␊ |
480 | cert_revision_tag(database & db,␊ |
481 | key_store & keys,␊ |
482 | revision_id const & rev,␊ |
483 | string const & tagname)␊ |
484 | {␊ |
485 | put_simple_revision_cert(db, keys, rev, tag_cert_name,␊ |
486 | cert_value(tagname));␊ |
487 | }␊ |
488 | ␊ |
489 | void␊ |
490 | cert_revision_changelog(database & db,␊ |
491 | key_store & keys,␊ |
492 | revision_id const & rev,␊ |
493 | utf8 const & log)␊ |
494 | {␊ |
495 | put_simple_revision_cert(db, keys, rev, changelog_cert_name,␊ |
496 | cert_value(log()));␊ |
497 | }␊ |
498 | ␊ |
499 | void␊ |
500 | cert_revision_comment(database & db,␊ |
501 | key_store & keys,␊ |
502 | revision_id const & rev,␊ |
503 | utf8 const & comment)␊ |
504 | {␊ |
505 | put_simple_revision_cert(db, keys, rev, comment_cert_name,␊ |
506 | cert_value(comment()));␊ |
507 | }␊ |
508 | ␊ |
509 | void␊ |
510 | cert_revision_testresult(database & db,␊ |
511 | key_store & keys,␊ |
512 | revision_id const & rev,␊ |
513 | string const & results)␊ |
514 | {␊ |
515 | bool passed = false;␊ |
516 | if (lowercase(results) == "true" ||␊ |
517 | lowercase(results) == "yes" ||␊ |
518 | lowercase(results) == "pass" ||␊ |
519 | results == "1")␊ |
520 | passed = true;␊ |
521 | else if (lowercase(results) == "false" ||␊ |
522 | lowercase(results) == "no" ||␊ |
523 | lowercase(results) == "fail" ||␊ |
524 | results == "0")␊ |
525 | passed = false;␊ |
526 | else␊ |
527 | throw informative_failure("could not interpret test results, "␊ |
528 | "tried '0/1' 'yes/no', 'true/false', "␊ |
529 | "'pass/fail'");␊ |
530 | ␊ |
531 | put_simple_revision_cert(db, keys, rev, testresult_cert_name,␊ |
532 | cert_value(lexical_cast<string>(passed)));␊ |
533 | }␊ |
534 | ␊ |
535 | // Local Variables:␊ |
536 | // mode: C++␊ |
537 | // fill-column: 76␊ |
538 | // c-file-style: "gnu"␊ |
539 | // indent-tabs-mode: nil␊ |
540 | // End:␊ |
541 | // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:␊ |