monotone

monotone Mtn Source Tree

Root/src/cmd_key_cert.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 "base.hh"
11#include <iostream>
12#include <sstream>
13#include <iterator>
14
15#include "basic_io.hh"
16#include "charset.hh"
17#include "cmd.hh"
18#include "app_state.hh"
19#include "database.hh"
20#include "file_io.hh"
21#include "project.hh"
22#include "keys.hh"
23#include "key_store.hh"
24#include "maybe_workspace_updater.hh"
25#include "transforms.hh"
26#include "vocab_cast.hh"
27
28using std::cout;
29using std::ostream_iterator;
30using std::ostringstream;
31using std::set;
32using std::string;
33
34namespace
35{
36 namespace syms
37 {
38 symbol const name("name");
39 symbol const hash("hash");
40 symbol const public_location("public_location");
41 symbol const private_location("private_location");
42 }
43};
44
45CMD(genkey, "genkey", "", CMD_REF(key_and_cert), N_("KEY_NAME"),
46 N_("Generates an RSA key-pair"),
47 "",
48 options::opts::force_duplicate_key)
49{
50 database db(app, database::maybe_unspecified);
51 key_store keys(app);
52
53 if (args.size() != 1)
54 throw usage(execid);
55
56 key_name name = typecast_vocab<key_name>(idx(args, 0));
57
58 if (!app.opts.force_duplicate_key)
59 {
60 E(!keys.key_pair_exists(name), origin::user,
61 F("you already have a key named '%s'") % name);
62 if (db.database_specified())
63 {
64 E(!db.public_key_exists(name), origin::user,
65 F("there is another key named '%s'") % name);
66 }
67 }
68
69 keys.create_key_pair(db, name);
70}
71
72CMD_AUTOMATE(generate_key, N_("KEY_NAME PASSPHRASE"),
73 N_("Generates an RSA key-pair"),
74 "",
75 options::opts::force_duplicate_key)
76{
77 // not unified with CMD(genkey), because the call to create_key_pair is
78 // significantly different.
79
80 E(args.size() == 2, origin::user,
81 F("wrong argument count"));
82
83 database db(app, database::maybe_unspecified);
84 key_store keys(app);
85
86 key_name name = typecast_vocab<key_name>(idx(args, 0));
87
88 if (!app.opts.force_duplicate_key)
89 {
90 E(!keys.key_pair_exists(name), origin::user,
91 F("you already have a key named '%s'") % name);
92 if (db.database_specified())
93 {
94 E(!db.public_key_exists(name), origin::user,
95 F("there is another key named '%s'") % name);
96 }
97 }
98
99 utf8 passphrase = idx(args, 1);
100
101 key_id hash;
102 keys.create_key_pair(db, name, key_store::create_quiet, &passphrase, &hash);
103
104 basic_io::printer prt;
105 basic_io::stanza stz;
106 vector<string> publocs, privlocs;
107 if (db.database_specified())
108 publocs.push_back("database");
109 publocs.push_back("keystore");
110 privlocs.push_back("keystore");
111
112 stz.push_str_pair(syms::name, name());
113 stz.push_binary_pair(syms::hash, hash.inner());
114 stz.push_str_multi(syms::public_location, publocs);
115 stz.push_str_multi(syms::private_location, privlocs);
116 prt.print_stanza(stz);
117
118 output.write(prt.buf.data(), prt.buf.size());
119
120}
121
122static void
123dropkey_common(app_state & app,
124 args_vector args,
125 bool drop_private)
126{
127 database db(app, database::maybe_unspecified);
128 key_store keys(app);
129 bool key_deleted = false;
130 bool checked_db = false;
131
132 key_identity_info identity;
133 project_t project(db);
134 project.get_key_identity(keys, app.lua,
135 typecast_vocab<external_key_name>(idx(args, 0)),
136 identity);
137
138 if (db.database_specified())
139 {
140 transaction_guard guard(db);
141 if (db.public_key_exists(identity.id))
142 {
143 P(F("dropping public key %s from database") % identity.id);
144 db.delete_public_key(identity.id);
145 key_deleted = true;
146 }
147 guard.commit();
148 checked_db = true;
149 }
150
151 if (drop_private && keys.key_pair_exists(identity.id))
152 {
153 P(F("dropping key pair %s from keystore") % identity.id);
154 keys.delete_key(identity.id);
155 key_deleted = true;
156 }
157
158 i18n_format fmt;
159 if (checked_db)
160 fmt = F("public or private key '%s' does not exist "
161 "in keystore or database");
162 else
163 fmt = F("public or private key '%s' does not exist "
164 "in keystore, and no database was specified");
165 E(key_deleted, origin::user, fmt % idx(args, 0)());
166}
167
168CMD(dropkey, "dropkey", "", CMD_REF(key_and_cert), N_("KEY_NAME_OR_HASH"),
169 N_("Drops a public and/or private key"),
170 "",
171 options::opts::none)
172{
173 if (args.size() != 1)
174 throw usage(execid);
175
176 dropkey_common(app, args,
177 true); // drop_private
178}
179
180CMD_AUTOMATE(drop_public_key, N_("KEY_NAME_OR_HASH"),
181 N_("Drops a public key"),
182 "",
183 options::opts::none)
184{
185 E(args.size() == 1, origin::user,
186 F("wrong argument count"));
187
188 dropkey_common(app, args,
189 false); // drop_private
190}
191
192CMD(passphrase, "passphrase", "", CMD_REF(key_and_cert), N_("KEY_NAME_OR_HASH"),
193 N_("Changes the passphrase of a private RSA key"),
194 "",
195 options::opts::none)
196{
197 if (args.size() != 1)
198 throw usage(execid);
199
200 key_store keys(app);
201 database db(app, database::maybe_unspecified);
202 project_t project(db);
203 key_identity_info identity;
204
205 project.get_key_identity(keys, app.lua,
206 typecast_vocab<external_key_name>(idx(args, 0)),
207 identity);
208
209 keys.change_key_passphrase(identity.id);
210 P(F("passphrase changed"));
211}
212
213CMD(ssh_agent_export, "ssh_agent_export", "", CMD_REF(key_and_cert),
214 N_("[FILENAME]"),
215 N_("Exports a private key for use with ssh-agent"),
216 "",
217 options::opts::none)
218{
219 database db(app, database::maybe_unspecified);
220 key_store keys(app);
221 project_t project(db);
222
223 if (args.size() > 1)
224 throw usage(execid);
225
226 key_id id;
227 get_user_key(app.opts, app.lua, db, keys, project, id);
228
229 if (args.empty())
230 keys.export_key_for_agent(id, cout);
231 else
232 {
233 ostringstream fout;
234 keys.export_key_for_agent(id, fout);
235 data keydat(fout.str(), origin::system);
236
237 system_path fname(idx(args, 0));
238 write_data_userprivate(fname, keydat, fname.dirname());
239 }
240}
241
242CMD(ssh_agent_add, "ssh_agent_add", "", CMD_REF(key_and_cert), "",
243 N_("Adds a private key to ssh-agent"),
244 "",
245 options::opts::none)
246{
247 database db(app, database::maybe_unspecified);
248 key_store keys(app);
249 project_t project(db);
250
251 if (args.size() > 1)
252 throw usage(execid);
253
254 key_id id;
255 get_user_key(app.opts, app.lua, db, keys, project, id);
256 keys.add_key_to_agent(id);
257}
258
259CMD(cert, "cert", "", CMD_REF(key_and_cert),
260 N_("SELECTOR CERTNAME [CERTVAL]"),
261 N_("Creates a certificate for a revision or set of revisions"),
262 N_("Creates a certificate with the given name and value on each revision "
263 "that matches the given selector"),
264 options::opts::none)
265{
266 database db(app);
267 key_store keys(app);
268 project_t project(db);
269
270 if ((args.size() != 3) && (args.size() != 2))
271 throw usage(execid);
272
273 transaction_guard guard(db);
274
275 set<revision_id> revisions;
276 complete(app.opts, app.lua, project, idx(args, 0)(), revisions);
277
278 cert_name cname = typecast_vocab<cert_name>(idx(args, 1));
279
280 cache_user_key(app.opts, project, keys, app.lua);
281
282 cert_value val;
283 if (args.size() == 3)
284 val = typecast_vocab<cert_value>(idx(args, 2));
285 else
286 {
287 data dat;
288 read_data_stdin(dat);
289 val = typecast_vocab<cert_value>(dat);
290 }
291 for (set<revision_id>::const_iterator r = revisions.begin();
292 r != revisions.end(); ++r)
293 {
294 project.put_cert(keys, *r, cname, val);
295 }
296 guard.commit();
297}
298
299CMD(trusted, "trusted", "", CMD_REF(key_and_cert),
300 N_("REVISION NAME VALUE SIGNER1 [SIGNER2 [...]]"),
301 N_("Tests whether a hypothetical certificate would be trusted"),
302 N_("The current settings are used to run the test."),
303 options::opts::none)
304{
305 key_store keys(app); // so the user can name keys that aren't in the db
306 database db(app);
307 project_t project(db);
308
309 if (args.size() < 4)
310 throw usage(execid);
311
312 set<revision_id> rids;
313 complete(app.opts, app.lua, project, idx(args, 0)(), rids);
314
315 revision_id ident;
316 if (!rids.empty())
317 ident = *rids.begin();
318
319 cert_name cname = typecast_vocab<cert_name>(idx(args, 1));
320 cert_value value = typecast_vocab<cert_value>(idx(args, 2));
321
322 set<key_identity_info> signers;
323 for (unsigned int i = 3; i != args.size(); ++i)
324 {
325 key_identity_info identity;
326 project.get_key_identity(keys, app.lua,
327 typecast_vocab<external_key_name>(idx(args, i)),
328 identity);
329 signers.insert(identity);
330 }
331
332
333 bool trusted = app.lua.hook_get_revision_cert_trust(signers,
334 ident.inner(),
335 cname, value);
336
337
338 ostringstream all_signers;
339 copy(signers.begin(), signers.end(),
340 ostream_iterator<key_identity_info>(all_signers, " "));
341
342 cout << (F("if a cert on: %s\n"
343 "with key: %s\n"
344 "and value: %s\n"
345 "was signed by: %s\n"
346 "it would be: %s")
347 % ident
348 % cname
349 % value
350 % all_signers.str()
351 % (trusted ? _("trusted") : _("UNtrusted")))
352 << '\n'; // final newline is kept out of the translation
353}
354
355CMD(tag, "tag", "", CMD_REF(review), N_("REVISION TAGNAME"),
356 N_("Puts a symbolic tag certificate on a revision"),
357 "",
358 options::opts::none)
359{
360 database db(app);
361 key_store keys(app);
362 project_t project(db);
363
364 if (args.size() != 2)
365 throw usage(execid);
366
367 revision_id r;
368 complete(app.opts, app.lua, project, idx(args, 0)(), r);
369
370 cache_user_key(app.opts, project, keys, app.lua);
371 project.put_tag(keys, r, idx(args, 1)());
372}
373
374
375CMD(testresult, "testresult", "", CMD_REF(review),
376 N_("REV (pass|fail|true|false|yes|no|1|0)"),
377 N_("Notes the results of running a test on a revision"),
378 "",
379 options::opts::none)
380{
381 database db(app);
382 key_store keys(app);
383 project_t project(db);
384
385 if (args.size() != 2)
386 throw usage(execid);
387
388 revision_id r;
389 complete(app.opts, app.lua, project, idx(args, 0)(), r);
390
391 cache_user_key(app.opts, project, keys, app.lua);
392 project.put_revision_testresult(keys, r, idx(args, 1)());
393}
394
395
396CMD(approve, "approve", "", CMD_REF(review), N_("REVISION"),
397 N_("Approves a particular revision"),
398 "",
399 options::opts::branch | options::opts::auto_update)
400{
401 database db(app);
402 key_store keys(app);
403 project_t project(db);
404
405 if (args.size() != 1)
406 throw usage(execid);
407
408 maybe_workspace_updater updater(app, project);
409
410 revision_id r;
411 complete(app.opts, app.lua, project, idx(args, 0)(), r);
412 guess_branch(app.opts, project, r);
413 E(!app.opts.branch().empty(), origin::user,
414 F("need '--branch' argument for approval"));
415
416 cache_user_key(app.opts, project, keys, app.lua);
417 project.put_revision_in_branch(keys, r, app.opts.branch);
418
419 updater.maybe_do_update();
420}
421
422CMD(suspend, "suspend", "", CMD_REF(review), N_("REVISION"),
423 N_("Suspends a particular revision"),
424 "",
425 options::opts::branch | options::opts::auto_update)
426{
427 database db(app);
428 key_store keys(app);
429 project_t project(db);
430
431 if (args.size() != 1)
432 throw usage(execid);
433
434 maybe_workspace_updater updater(app, project);
435
436 revision_id r;
437 complete(app.opts, app.lua, project, idx(args, 0)(), r);
438 guess_branch(app.opts, project, r);
439 E(!app.opts.branch().empty(), origin::user,
440 F("need '--branch' argument to suspend"));
441
442 cache_user_key(app.opts, project, keys, app.lua);
443 project.suspend_revision_in_branch(keys, r, app.opts.branch);
444
445 updater.maybe_do_update();
446}
447
448CMD(comment, "comment", "", CMD_REF(review), N_("REVISION [COMMENT]"),
449 N_("Comments on a particular revision"),
450 "",
451 options::opts::none)
452{
453 database db(app);
454 key_store keys(app);
455 project_t project(db);
456
457 if (args.size() != 1 && args.size() != 2)
458 throw usage(execid);
459
460 utf8 comment;
461 if (args.size() == 2)
462 comment = idx(args, 1);
463 else
464 {
465 external comment_external;
466 E(app.lua.hook_edit_comment(external(""), comment_external),
467 origin::user,
468 F("edit comment failed"));
469 system_to_utf8(comment_external, comment);
470 }
471
472 E(comment().find_first_not_of("\n\r\t ") != string::npos,
473 origin::user,
474 F("empty comment"));
475
476 revision_id r;
477 complete(app.opts, app.lua, project, idx(args, 0)(), r);
478
479 cache_user_key(app.opts, project, keys, app.lua);
480 project.put_revision_comment(keys, r, comment);
481}
482
483// Local Variables:
484// mode: C++
485// fill-column: 76
486// c-file-style: "gnu"
487// indent-tabs-mode: nil
488// End:
489// 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