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 <fstream>␊ |
14 | ␊ |
15 | #include "cert.hh"␊ |
16 | #include "charset.hh"␊ |
17 | #include "cmd.hh"␊ |
18 | #include "app_state.hh"␊ |
19 | #include "keys.hh"␊ |
20 | #include "transforms.hh"␊ |
21 | #include "ssh_agent.hh"␊ |
22 | #include "botan/pipe.h"␊ |
23 | ␊ |
24 | using std::cout;␊ |
25 | using std::ostream_iterator;␊ |
26 | using std::ostringstream;␊ |
27 | using std::set;␊ |
28 | using std::string;␊ |
29 | using std::ofstream;␊ |
30 | using Botan::Pipe;␊ |
31 | using Botan::RSA_PrivateKey;␊ |
32 | ␊ |
33 | CMD(genkey, "genkey", "", CMD_REF(key_and_cert), N_("KEYID"),␊ |
34 | N_("Generates an RSA key-pair"),␊ |
35 | "",␊ |
36 | options::opts::none)␊ |
37 | {␊ |
38 | if (args.size() != 1)␊ |
39 | throw usage(execid);␊ |
40 | ␊ |
41 | rsa_keypair_id ident;␊ |
42 | internalize_rsa_keypair_id(idx(args, 0), ident);␊ |
43 | bool exists = app.keys.key_pair_exists(ident);␊ |
44 | if (app.db.database_specified())␊ |
45 | {␊ |
46 | transaction_guard guard(app.db);␊ |
47 | exists = exists || app.db.public_key_exists(ident);␊ |
48 | guard.commit();␊ |
49 | }␊ |
50 | ␊ |
51 | N(!exists, F("key '%s' already exists") % ident);␊ |
52 | ␊ |
53 | keypair kp;␊ |
54 | P(F("generating key-pair '%s'") % ident);␊ |
55 | generate_key_pair(app.lua, ident, kp);␊ |
56 | P(F("storing key-pair '%s' in %s/") ␊ |
57 | % ident % app.keys.get_key_dir());␊ |
58 | app.keys.put_key_pair(ident, kp);␊ |
59 | }␊ |
60 | ␊ |
61 | CMD(dropkey, "dropkey", "", CMD_REF(key_and_cert), N_("KEYID"),␊ |
62 | N_("Drops a public and/or private key"),␊ |
63 | "",␊ |
64 | options::opts::none)␊ |
65 | {␊ |
66 | bool key_deleted = false;␊ |
67 | ␊ |
68 | if (args.size() != 1)␊ |
69 | throw usage(execid);␊ |
70 | ␊ |
71 | rsa_keypair_id ident(idx(args, 0)());␊ |
72 | bool checked_db = false;␊ |
73 | if (app.db.database_specified())␊ |
74 | {␊ |
75 | transaction_guard guard(app.db);␊ |
76 | if (app.db.public_key_exists(ident))␊ |
77 | {␊ |
78 | P(F("dropping public key '%s' from database") % ident);␊ |
79 | app.db.delete_public_key(ident);␊ |
80 | key_deleted = true;␊ |
81 | }␊ |
82 | guard.commit();␊ |
83 | checked_db = true;␊ |
84 | }␊ |
85 | ␊ |
86 | if (app.keys.key_pair_exists(ident))␊ |
87 | {␊ |
88 | P(F("dropping key pair '%s' from keystore") % ident);␊ |
89 | app.keys.delete_key(ident);␊ |
90 | key_deleted = true;␊ |
91 | }␊ |
92 | ␊ |
93 | i18n_format fmt;␊ |
94 | if (checked_db)␊ |
95 | fmt = F("public or private key '%s' does not exist "␊ |
96 | "in keystore or database");␊ |
97 | else␊ |
98 | fmt = F("public or private key '%s' does not exist "␊ |
99 | "in keystore, and no database was specified");␊ |
100 | N(key_deleted, fmt % idx(args, 0)());␊ |
101 | }␊ |
102 | ␊ |
103 | CMD(passphrase, "passphrase", "", CMD_REF(key_and_cert), N_("KEYID"),␊ |
104 | N_("Changes the passphrase of a private RSA key"),␊ |
105 | "",␊ |
106 | options::opts::none)␊ |
107 | {␊ |
108 | if (args.size() != 1)␊ |
109 | throw usage(execid);␊ |
110 | ␊ |
111 | rsa_keypair_id ident;␊ |
112 | internalize_rsa_keypair_id(idx(args, 0), ident);␊ |
113 | ␊ |
114 | N(app.keys.key_pair_exists(ident),␊ |
115 | F("key '%s' does not exist in the keystore") % ident);␊ |
116 | ␊ |
117 | keypair key;␊ |
118 | app.keys.get_key_pair(ident, key);␊ |
119 | change_key_passphrase(app.lua, ident, key.priv);␊ |
120 | app.keys.delete_key(ident);␊ |
121 | app.keys.put_key_pair(ident, key);␊ |
122 | P(F("passphrase changed"));␊ |
123 | }␊ |
124 | ␊ |
125 | CMD(ssh_agent_export, "ssh_agent_export", "", CMD_REF(key_and_cert),␊ |
126 | N_("[FILENAME]"),␊ |
127 | N_("Exports a private key for use with ssh-agent"),␊ |
128 | "",␊ |
129 | options::opts::none)␊ |
130 | {␊ |
131 | if (args.size() > 1)␊ |
132 | throw usage(execid);␊ |
133 | ␊ |
134 | rsa_keypair_id id;␊ |
135 | keypair key;␊ |
136 | get_user_key(id, app);␊ |
137 | N(priv_key_exists(app, id), F("the key you specified cannot be found"));␊ |
138 | app.keys.get_key_pair(id, key);␊ |
139 | shared_ptr<RSA_PrivateKey> priv = get_private_key(app.lua, id, key.priv);␊ |
140 | utf8 new_phrase;␊ |
141 | get_passphrase(app.lua, id, new_phrase, true, true);␊ |
142 | Pipe p;␊ |
143 | p.start_msg();␊ |
144 | if (new_phrase().length())␊ |
145 | {␊ |
146 | Botan::PKCS8::encrypt_key(*priv,␊ |
147 | p,␊ |
148 | new_phrase(),␊ |
149 | "PBE-PKCS5v20(SHA-1,TripleDES/CBC)");␊ |
150 | }␊ |
151 | else␊ |
152 | {␊ |
153 | Botan::PKCS8::encode(*priv, p);␊ |
154 | }␊ |
155 | string decoded_key = p.read_all_as_string();␊ |
156 | if (args.size() == 0)␊ |
157 | cout << decoded_key;␊ |
158 | else␊ |
159 | {␊ |
160 | string external_path = system_path(idx(args, 0)).as_external();␊ |
161 | ofstream fout(external_path.c_str(), ofstream::out);␊ |
162 | fout << decoded_key;␊ |
163 | }␊ |
164 | }␊ |
165 | ␊ |
166 | CMD(ssh_agent_add, "ssh_agent_add", "", CMD_REF(key_and_cert), "",␊ |
167 | N_("Adds a private key to ssh-agent"),␊ |
168 | "",␊ |
169 | options::opts::none)␊ |
170 | {␊ |
171 | if (args.size() > 1)␊ |
172 | throw usage(execid);␊ |
173 | ␊ |
174 | rsa_keypair_id id;␊ |
175 | keypair key;␊ |
176 | get_user_key(id, app);␊ |
177 | N(priv_key_exists(app, id), F("the key you specified cannot be found"));␊ |
178 | app.keys.get_key_pair(id, key);␊ |
179 | shared_ptr<RSA_PrivateKey> priv = get_private_key(app.lua, id, key.priv);␊ |
180 | app.agent.add_identity(*priv, id());␊ |
181 | }␊ |
182 | ␊ |
183 | CMD(cert, "cert", "", CMD_REF(key_and_cert),␊ |
184 | N_("REVISION CERTNAME [CERTVAL]"),␊ |
185 | N_("Creates a certificate for a revision"),␊ |
186 | "",␊ |
187 | options::opts::none)␊ |
188 | {␊ |
189 | if ((args.size() != 3) && (args.size() != 2))␊ |
190 | throw usage(execid);␊ |
191 | ␊ |
192 | transaction_guard guard(app.db);␊ |
193 | ␊ |
194 | revision_id rid;␊ |
195 | complete(app, idx(args, 0)(), rid);␊ |
196 | ␊ |
197 | cert_name cname;␊ |
198 | internalize_cert_name(idx(args, 1), cname);␊ |
199 | ␊ |
200 | rsa_keypair_id key;␊ |
201 | get_user_key(key, app);␊ |
202 | ␊ |
203 | cert_value val;␊ |
204 | if (args.size() == 3)␊ |
205 | val = cert_value(idx(args, 2)());␊ |
206 | else␊ |
207 | {␊ |
208 | data dat;␊ |
209 | read_data_stdin(dat);␊ |
210 | val = cert_value(dat());␊ |
211 | }␊ |
212 | ␊ |
213 | app.get_project().put_cert(rid, cname, val);␊ |
214 | guard.commit();␊ |
215 | }␊ |
216 | ␊ |
217 | CMD(trusted, "trusted", "", CMD_REF(key_and_cert), ␊ |
218 | N_("REVISION NAME VALUE SIGNER1 [SIGNER2 [...]]"),␊ |
219 | N_("Tests whether a hypothetical certificate would be trusted"),␊ |
220 | N_("The current settings are used to run the test."),␊ |
221 | options::opts::none)␊ |
222 | {␊ |
223 | if (args.size() < 4)␊ |
224 | throw usage(execid);␊ |
225 | ␊ |
226 | revision_id rid;␊ |
227 | complete(app, idx(args, 0)(), rid, false);␊ |
228 | hexenc<id> ident(rid.inner());␊ |
229 | ␊ |
230 | cert_name cname;␊ |
231 | internalize_cert_name(idx(args, 1), cname);␊ |
232 | ␊ |
233 | cert_value value(idx(args, 2)());␊ |
234 | ␊ |
235 | set<rsa_keypair_id> signers;␊ |
236 | for (unsigned int i = 3; i != args.size(); ++i)␊ |
237 | {␊ |
238 | rsa_keypair_id keyid;␊ |
239 | internalize_rsa_keypair_id(idx(args, i), keyid);␊ |
240 | signers.insert(keyid);␊ |
241 | }␊ |
242 | ␊ |
243 | ␊ |
244 | bool trusted = app.lua.hook_get_revision_cert_trust(signers, ident,␊ |
245 | cname, value);␊ |
246 | ␊ |
247 | ␊ |
248 | ostringstream all_signers;␊ |
249 | copy(signers.begin(), signers.end(),␊ |
250 | ostream_iterator<rsa_keypair_id>(all_signers, " "));␊ |
251 | ␊ |
252 | cout << (F("if a cert on: %s\n"␊ |
253 | "with key: %s\n"␊ |
254 | "and value: %s\n"␊ |
255 | "was signed by: %s\n"␊ |
256 | "it would be: %s")␊ |
257 | % ident␊ |
258 | % cname␊ |
259 | % value␊ |
260 | % all_signers.str()␊ |
261 | % (trusted ? _("trusted") : _("UNtrusted")))␊ |
262 | << '\n'; // final newline is kept out of the translation␊ |
263 | }␊ |
264 | ␊ |
265 | CMD(tag, "tag", "", CMD_REF(review), N_("REVISION TAGNAME"),␊ |
266 | N_("Puts a symbolic tag certificate on a revision"),␊ |
267 | "",␊ |
268 | options::opts::none)␊ |
269 | {␊ |
270 | if (args.size() != 2)␊ |
271 | throw usage(execid);␊ |
272 | ␊ |
273 | revision_id r;␊ |
274 | complete(app, idx(args, 0)(), r);␊ |
275 | cert_revision_tag(r, idx(args, 1)(), app);␊ |
276 | }␊ |
277 | ␊ |
278 | ␊ |
279 | CMD(testresult, "testresult", "", CMD_REF(review),␊ |
280 | N_("ID (pass|fail|true|false|yes|no|1|0)"),␊ |
281 | N_("Notes the results of running a test on a revision"),␊ |
282 | "",␊ |
283 | options::opts::none)␊ |
284 | {␊ |
285 | if (args.size() != 2)␊ |
286 | throw usage(execid);␊ |
287 | ␊ |
288 | revision_id r;␊ |
289 | complete(app, idx(args, 0)(), r);␊ |
290 | cert_revision_testresult(r, idx(args, 1)(), app);␊ |
291 | }␊ |
292 | ␊ |
293 | ␊ |
294 | CMD(approve, "approve", "", CMD_REF(review), N_("REVISION"),␊ |
295 | N_("Approves a particular revision"),␊ |
296 | "",␊ |
297 | options::opts::branch)␊ |
298 | {␊ |
299 | if (args.size() != 1)␊ |
300 | throw usage(execid);␊ |
301 | ␊ |
302 | revision_id r;␊ |
303 | complete(app, idx(args, 0)(), r);␊ |
304 | guess_branch(r, app);␊ |
305 | N(app.opts.branchname() != "", F("need --branch argument for approval"));␊ |
306 | app.get_project().put_revision_in_branch(r, app.opts.branchname);␊ |
307 | }␊ |
308 | ␊ |
309 | CMD(comment, "comment", "", CMD_REF(review), N_("REVISION [COMMENT]"),␊ |
310 | N_("Comments on a particular revision"),␊ |
311 | "",␊ |
312 | options::opts::none)␊ |
313 | {␊ |
314 | if (args.size() != 1 && args.size() != 2)␊ |
315 | throw usage(execid);␊ |
316 | ␊ |
317 | utf8 comment;␊ |
318 | if (args.size() == 2)␊ |
319 | comment = idx(args, 1);␊ |
320 | else␊ |
321 | {␊ |
322 | external comment_external;␊ |
323 | N(app.lua.hook_edit_comment(external(""), external(""), comment_external),␊ |
324 | F("edit comment failed"));␊ |
325 | system_to_utf8(comment_external, comment);␊ |
326 | }␊ |
327 | ␊ |
328 | N(comment().find_first_not_of("\n\r\t ") != string::npos,␊ |
329 | F("empty comment"));␊ |
330 | ␊ |
331 | revision_id r;␊ |
332 | complete(app, idx(args, 0)(), r);␊ |
333 | cert_revision_comment(r, comment, app);␊ |
334 | }␊ |
335 | ␊ |
336 | // Local Variables:␊ |
337 | // mode: C++␊ |
338 | // fill-column: 76␊ |
339 | // c-file-style: "gnu"␊ |
340 | // indent-tabs-mode: nil␊ |
341 | // End:␊ |
342 | // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:␊ |