Branch data Line data Source code
1 : : /* $OpenBSD: ssh-add.c,v 1.109 2014/02/02 03:44:31 djm Exp $ */
2 : : /*
3 : : * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 : : * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
5 : : * All rights reserved
6 : : * Adds an identity to the authentication server, or removes an identity.
7 : : *
8 : : * As far as I am concerned, the code I have written for this software
9 : : * can be used freely for any purpose. Any derived versions of this
10 : : * software must be clearly marked as such, and if the derived work is
11 : : * incompatible with the protocol description in the RFC file, it must be
12 : : * called by a name other than "ssh" or "Secure Shell".
13 : : *
14 : : * SSH2 implementation,
15 : : * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
16 : : *
17 : : * Redistribution and use in source and binary forms, with or without
18 : : * modification, are permitted provided that the following conditions
19 : : * are met:
20 : : * 1. Redistributions of source code must retain the above copyright
21 : : * notice, this list of conditions and the following disclaimer.
22 : : * 2. Redistributions in binary form must reproduce the above copyright
23 : : * notice, this list of conditions and the following disclaimer in the
24 : : * documentation and/or other materials provided with the distribution.
25 : : *
26 : : * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27 : : * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 : : * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29 : : * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30 : : * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 : : * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 : : * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 : : * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 : : * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
35 : : * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 : : */
37 : :
38 : : #include "includes.h"
39 : :
40 : : #include <sys/types.h>
41 : : #include <sys/stat.h>
42 : : #include <sys/param.h>
43 : :
44 : : #include <openssl/evp.h>
45 : : #include "openbsd-compat/openssl-compat.h"
46 : :
47 : : #include <fcntl.h>
48 : : #include <pwd.h>
49 : : #include <stdarg.h>
50 : : #include <stdio.h>
51 : : #include <stdlib.h>
52 : : #include <string.h>
53 : : #include <unistd.h>
54 : :
55 : : #include "xmalloc.h"
56 : : #include "ssh.h"
57 : : #include "rsa.h"
58 : : #include "log.h"
59 : : #include "key.h"
60 : : #include "buffer.h"
61 : : #include "authfd.h"
62 : : #include "authfile.h"
63 : : #include "pathnames.h"
64 : : #include "misc.h"
65 : :
66 : : /* argv0 */
67 : : extern char *__progname;
68 : :
69 : : /* Default files to add */
70 : : static char *default_files[] = {
71 : : _PATH_SSH_CLIENT_ID_RSA,
72 : : _PATH_SSH_CLIENT_ID_DSA,
73 : : #ifdef OPENSSL_HAS_ECC
74 : : _PATH_SSH_CLIENT_ID_ECDSA,
75 : : #endif
76 : : _PATH_SSH_CLIENT_ID_ED25519,
77 : : _PATH_SSH_CLIENT_IDENTITY,
78 : : NULL
79 : : };
80 : :
81 : : /* Default lifetime (0 == forever) */
82 : : static int lifetime = 0;
83 : :
84 : : /* User has to confirm key use */
85 : : static int confirm = 0;
86 : :
87 : : /* we keep a cache of one passphrases */
88 : : static char *pass = NULL;
89 : : static void
90 : 5 : clear_pass(void)
91 : : {
92 [ - + ]: 5 : if (pass) {
93 : 0 : explicit_bzero(pass, strlen(pass));
94 : 0 : free(pass);
95 : 0 : pass = NULL;
96 : : }
97 : 5 : }
98 : :
99 : : static int
100 : 0 : delete_file(AuthenticationConnection *ac, const char *filename, int key_only)
101 : : {
102 : 0 : Key *public = NULL, *cert = NULL;
103 : 0 : char *certpath = NULL, *comment = NULL;
104 : 0 : int ret = -1;
105 : :
106 : 0 : public = key_load_public(filename, &comment);
107 [ # # ]: 0 : if (public == NULL) {
108 : : printf("Bad key file %s\n", filename);
109 : 0 : return -1;
110 : : }
111 [ # # ]: 0 : if (ssh_remove_identity(ac, public)) {
112 : 0 : fprintf(stderr, "Identity removed: %s (%s)\n", filename, comment);
113 : 0 : ret = 0;
114 : : } else
115 : 0 : fprintf(stderr, "Could not remove identity: %s\n", filename);
116 : :
117 [ # # ]: 0 : if (key_only)
118 : : goto out;
119 : :
120 : : /* Now try to delete the corresponding certificate too */
121 : 0 : free(comment);
122 : 0 : comment = NULL;
123 : 0 : xasprintf(&certpath, "%s-cert.pub", filename);
124 [ # # ]: 0 : if ((cert = key_load_public(certpath, &comment)) == NULL)
125 : : goto out;
126 [ # # ]: 0 : if (!key_equal_public(cert, public))
127 : 0 : fatal("Certificate %s does not match private key %s",
128 : : certpath, filename);
129 : :
130 [ # # ]: 0 : if (ssh_remove_identity(ac, cert)) {
131 : 0 : fprintf(stderr, "Identity removed: %s (%s)\n", certpath,
132 : : comment);
133 : 0 : ret = 0;
134 : : } else
135 : 0 : fprintf(stderr, "Could not remove identity: %s\n", certpath);
136 : :
137 : : out:
138 [ # # ]: 0 : if (cert != NULL)
139 : 0 : key_free(cert);
140 [ # # ]: 0 : if (public != NULL)
141 : 0 : key_free(public);
142 : 0 : free(certpath);
143 : 0 : free(comment);
144 : :
145 : 0 : return ret;
146 : : }
147 : :
148 : : /* Send a request to remove all identities. */
149 : : static int
150 : 1 : delete_all(AuthenticationConnection *ac)
151 : : {
152 : 1 : int ret = -1;
153 : :
154 [ + - ]: 1 : if (ssh_remove_all_identities(ac, 1))
155 : 1 : ret = 0;
156 : : /* ignore error-code for ssh2 */
157 : 1 : ssh_remove_all_identities(ac, 2);
158 : :
159 [ + - ]: 1 : if (ret == 0)
160 : 1 : fprintf(stderr, "All identities removed.\n");
161 : : else
162 : 0 : fprintf(stderr, "Failed to remove all identities.\n");
163 : :
164 : 1 : return ret;
165 : : }
166 : :
167 : : static int
168 : 5 : add_file(AuthenticationConnection *ac, const char *filename, int key_only)
169 : : {
170 : : Key *private, *cert;
171 : 5 : char *comment = NULL;
172 : 5 : char msg[1024], *certpath = NULL;
173 : 5 : int fd, perms_ok, ret = -1;
174 : : Buffer keyblob;
175 : :
176 [ - + ][ + - ]: 5 : if (strcmp(filename, "-") == 0) {
177 : : fd = STDIN_FILENO;
178 : : filename = "(stdin)";
179 [ - + ]: 5 : } else if ((fd = open(filename, O_RDONLY)) < 0) {
180 : 0 : perror(filename);
181 : 0 : return -1;
182 : : }
183 : :
184 : : /*
185 : : * Since we'll try to load a keyfile multiple times, permission errors
186 : : * will occur multiple times, so check perms first and bail if wrong.
187 : : */
188 [ + - ]: 5 : if (fd != STDIN_FILENO) {
189 : 5 : perms_ok = key_perm_ok(fd, filename);
190 [ - + ]: 5 : if (!perms_ok) {
191 : 0 : close(fd);
192 : 0 : return -1;
193 : : }
194 : : }
195 : 5 : buffer_init(&keyblob);
196 [ - + ]: 5 : if (!key_load_file(fd, filename, &keyblob)) {
197 : 0 : buffer_free(&keyblob);
198 : 0 : close(fd);
199 : 0 : return -1;
200 : : }
201 : 5 : close(fd);
202 : :
203 : : /* At first, try empty passphrase */
204 : 5 : private = key_parse_private(&keyblob, filename, "", &comment);
205 [ - + ]: 5 : if (comment == NULL)
206 : 0 : comment = xstrdup(filename);
207 : : /* try last */
208 [ - + ][ # # ]: 5 : if (private == NULL && pass != NULL)
209 : 0 : private = key_parse_private(&keyblob, filename, pass, NULL);
210 [ - + ]: 5 : if (private == NULL) {
211 : : /* clear passphrase since it did not work */
212 : 0 : clear_pass();
213 : 0 : snprintf(msg, sizeof msg, "Enter passphrase for %.200s: ",
214 : : comment);
215 : : for (;;) {
216 : 0 : pass = read_passphrase(msg, RP_ALLOW_STDIN);
217 [ # # ]: 0 : if (strcmp(pass, "") == 0) {
218 : 0 : clear_pass();
219 : 0 : free(comment);
220 : 0 : buffer_free(&keyblob);
221 : 0 : return -1;
222 : : }
223 : 0 : private = key_parse_private(&keyblob, filename, pass,
224 : : &comment);
225 [ # # ]: 0 : if (private != NULL)
226 : : break;
227 : 0 : clear_pass();
228 : 0 : snprintf(msg, sizeof msg,
229 : : "Bad passphrase, try again for %.200s: ", comment);
230 : : }
231 : : }
232 : 5 : buffer_free(&keyblob);
233 : :
234 [ + - ]: 5 : if (ssh_add_identity_constrained(ac, private, comment, lifetime,
235 : : confirm)) {
236 : 5 : fprintf(stderr, "Identity added: %s (%s)\n", filename, comment);
237 : 5 : ret = 0;
238 [ + + ]: 5 : if (lifetime != 0)
239 : 2 : fprintf(stderr,
240 : : "Lifetime set to %d seconds\n", lifetime);
241 [ - + ]: 5 : if (confirm != 0)
242 : 0 : fprintf(stderr,
243 : : "The user must confirm each use of the key\n");
244 : : } else {
245 : 0 : fprintf(stderr, "Could not add identity: %s\n", filename);
246 : : }
247 : :
248 : : /* Skip trying to load the cert if requested */
249 [ + - ]: 5 : if (key_only)
250 : : goto out;
251 : :
252 : : /* Now try to add the certificate flavour too */
253 : 5 : xasprintf(&certpath, "%s-cert.pub", filename);
254 [ - + ]: 5 : if ((cert = key_load_public(certpath, NULL)) == NULL)
255 : : goto out;
256 : :
257 [ # # ]: 0 : if (!key_equal_public(cert, private)) {
258 : 0 : error("Certificate %s does not match private key %s",
259 : : certpath, filename);
260 : 0 : key_free(cert);
261 : 0 : goto out;
262 : : }
263 : :
264 : : /* Graft with private bits */
265 [ # # ]: 0 : if (key_to_certified(private, key_cert_is_legacy(cert)) != 0) {
266 : 0 : error("%s: key_to_certified failed", __func__);
267 : 0 : key_free(cert);
268 : 0 : goto out;
269 : : }
270 : 0 : key_cert_copy(cert, private);
271 : 0 : key_free(cert);
272 : :
273 [ # # ]: 0 : if (!ssh_add_identity_constrained(ac, private, comment,
274 : : lifetime, confirm)) {
275 : 0 : error("Certificate %s (%s) add failed", certpath,
276 : 0 : private->cert->key_id);
277 : : }
278 : 0 : fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
279 : 0 : private->cert->key_id);
280 [ # # ]: 0 : if (lifetime != 0)
281 : 0 : fprintf(stderr, "Lifetime set to %d seconds\n", lifetime);
282 [ # # ]: 0 : if (confirm != 0)
283 : 0 : fprintf(stderr, "The user must confirm each use of the key\n");
284 : : out:
285 [ + - ]: 5 : if (certpath != NULL)
286 : 5 : free(certpath);
287 : 5 : free(comment);
288 : 5 : key_free(private);
289 : :
290 : 5 : return ret;
291 : : }
292 : :
293 : : static int
294 : 0 : update_card(AuthenticationConnection *ac, int add, const char *id)
295 : : {
296 : 0 : char *pin = NULL;
297 : 0 : int ret = -1;
298 : :
299 [ # # ]: 0 : if (add) {
300 [ # # ]: 0 : if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
301 : : RP_ALLOW_STDIN)) == NULL)
302 : : return -1;
303 : : }
304 : :
305 [ # # ][ # # ]: 0 : if (ssh_update_card(ac, add, id, pin == NULL ? "" : pin,
306 : : lifetime, confirm)) {
307 [ # # ]: 0 : fprintf(stderr, "Card %s: %s\n",
308 : : add ? "added" : "removed", id);
309 : 0 : ret = 0;
310 : : } else {
311 [ # # ]: 0 : fprintf(stderr, "Could not %s card: %s\n",
312 : : add ? "add" : "remove", id);
313 : 0 : ret = -1;
314 : : }
315 : 0 : free(pin);
316 : 0 : return ret;
317 : : }
318 : :
319 : : static int
320 : 7 : list_identities(AuthenticationConnection *ac, int do_fp)
321 : : {
322 : : Key *key;
323 : : char *comment, *fp;
324 : 7 : int had_identities = 0;
325 : : int version;
326 : :
327 [ + + ]: 21 : for (version = 1; version <= 2; version++) {
328 [ + + ]: 28 : for (key = ssh_get_first_identity(ac, &comment, version);
329 : : key != NULL;
330 : 14 : key = ssh_get_next_identity(ac, &comment, version)) {
331 : 14 : had_identities = 1;
332 [ + + ]: 14 : if (do_fp) {
333 : 11 : fp = key_fingerprint(key, SSH_FP_MD5,
334 : : SSH_FP_HEX);
335 : 11 : printf("%d %s %s (%s)\n",
336 : : key_size(key), fp, comment, key_type(key));
337 : 11 : free(fp);
338 : : } else {
339 [ - + ]: 3 : if (!key_write(key, stdout))
340 : 0 : fprintf(stderr, "key_write failed");
341 : 3 : fprintf(stdout, " %s\n", comment);
342 : : }
343 : 14 : key_free(key);
344 : 14 : free(comment);
345 : : }
346 : : }
347 [ + + ]: 7 : if (!had_identities) {
348 : : printf("The agent has no identities.\n");
349 : 2 : return -1;
350 : : }
351 : : return 0;
352 : : }
353 : :
354 : : static int
355 : 0 : lock_agent(AuthenticationConnection *ac, int lock)
356 : : {
357 : : char prompt[100], *p1, *p2;
358 : 0 : int passok = 1, ret = -1;
359 : :
360 : 0 : strlcpy(prompt, "Enter lock password: ", sizeof(prompt));
361 : 0 : p1 = read_passphrase(prompt, RP_ALLOW_STDIN);
362 [ # # ]: 0 : if (lock) {
363 : 0 : strlcpy(prompt, "Again: ", sizeof prompt);
364 : 0 : p2 = read_passphrase(prompt, RP_ALLOW_STDIN);
365 [ # # ]: 0 : if (strcmp(p1, p2) != 0) {
366 : 0 : fprintf(stderr, "Passwords do not match.\n");
367 : 0 : passok = 0;
368 : : }
369 : 0 : explicit_bzero(p2, strlen(p2));
370 : 0 : free(p2);
371 : : }
372 [ # # ][ # # ]: 0 : if (passok && ssh_lock_agent(ac, lock, p1)) {
373 [ # # ]: 0 : fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un");
374 : 0 : ret = 0;
375 : : } else
376 [ # # ]: 0 : fprintf(stderr, "Failed to %slock agent.\n", lock ? "" : "un");
377 : 0 : explicit_bzero(p1, strlen(p1));
378 : 0 : free(p1);
379 : 0 : return (ret);
380 : : }
381 : :
382 : : static int
383 : 5 : do_file(AuthenticationConnection *ac, int deleting, int key_only, char *file)
384 : : {
385 [ - + ]: 5 : if (deleting) {
386 [ # # ]: 0 : if (delete_file(ac, file, key_only) == -1)
387 : : return -1;
388 : : } else {
389 [ + - ]: 5 : if (add_file(ac, file, key_only) == -1)
390 : : return -1;
391 : : }
392 : : return 0;
393 : : }
394 : :
395 : : static void
396 : 0 : usage(void)
397 : : {
398 : 0 : fprintf(stderr, "usage: %s [options] [file ...]\n", __progname);
399 : 0 : fprintf(stderr, "Options:\n");
400 : 0 : fprintf(stderr, " -l List fingerprints of all identities.\n");
401 : 0 : fprintf(stderr, " -L List public key parameters of all identities.\n");
402 : 0 : fprintf(stderr, " -k Load only keys and not certificates.\n");
403 : 0 : fprintf(stderr, " -c Require confirmation to sign using identities\n");
404 : 0 : fprintf(stderr, " -t life Set lifetime (in seconds) when adding identities.\n");
405 : 0 : fprintf(stderr, " -d Delete identity.\n");
406 : 0 : fprintf(stderr, " -D Delete all identities.\n");
407 : 0 : fprintf(stderr, " -x Lock agent.\n");
408 : 0 : fprintf(stderr, " -X Unlock agent.\n");
409 : 0 : fprintf(stderr, " -s pkcs11 Add keys from PKCS#11 provider.\n");
410 : 0 : fprintf(stderr, " -e pkcs11 Remove keys provided by PKCS#11 provider.\n");
411 : 0 : }
412 : :
413 : : int
414 : 14 : main(int argc, char **argv)
415 : : {
416 : : extern char *optarg;
417 : : extern int optind;
418 : 14 : AuthenticationConnection *ac = NULL;
419 : 14 : char *pkcs11provider = NULL;
420 : 14 : int i, ch, deleting = 0, ret = 0, key_only = 0;
421 : :
422 : : /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
423 : 14 : sanitise_stdfd();
424 : :
425 : 14 : __progname = ssh_get_progname(argv[0]);
426 : 14 : seed_rng();
427 : :
428 : 14 : OpenSSL_add_all_algorithms();
429 : :
430 : : /* At first, get a connection to the authentication agent. */
431 : 14 : ac = ssh_get_authentication_connection();
432 [ + + ]: 14 : if (ac == NULL) {
433 : 1 : fprintf(stderr,
434 : : "Could not open a connection to your authentication agent.\n");
435 : 1 : exit(2);
436 : : }
437 [ + + ]: 15 : while ((ch = getopt(argc, argv, "klLcdDxXe:s:t:")) != -1) {
438 [ + - - - : 10 : switch (ch) {
+ - - + -
- ]
439 : : case 'k':
440 : : key_only = 1;
441 : : break;
442 : : case 'l':
443 : : case 'L':
444 [ + + ]: 7 : if (list_identities(ac, ch == 'l' ? 1 : 0) == -1)
445 : 2 : ret = 1;
446 : : goto done;
447 : : case 'x':
448 : : case 'X':
449 [ # # ]: 0 : if (lock_agent(ac, ch == 'x' ? 1 : 0) == -1)
450 : 0 : ret = 1;
451 : : goto done;
452 : : case 'c':
453 : 0 : confirm = 1;
454 : 0 : break;
455 : : case 'd':
456 : 0 : deleting = 1;
457 : 0 : break;
458 : : case 'D':
459 [ - + ]: 1 : if (delete_all(ac) == -1)
460 : 0 : ret = 1;
461 : : goto done;
462 : : case 's':
463 : 0 : pkcs11provider = optarg;
464 : 0 : break;
465 : : case 'e':
466 : 0 : deleting = 1;
467 : 0 : pkcs11provider = optarg;
468 : 0 : break;
469 : : case 't':
470 [ - + ]: 2 : if ((lifetime = convtime(optarg)) == -1) {
471 : 0 : fprintf(stderr, "Invalid lifetime\n");
472 : 0 : ret = 1;
473 : 0 : goto done;
474 : : }
475 : : break;
476 : : default:
477 : 0 : usage();
478 : 0 : ret = 1;
479 : 2 : goto done;
480 : : }
481 : : }
482 : 5 : argc -= optind;
483 : 5 : argv += optind;
484 [ - + ]: 5 : if (pkcs11provider != NULL) {
485 [ # # ]: 0 : if (update_card(ac, !deleting, pkcs11provider) == -1)
486 : 0 : ret = 1;
487 : : goto done;
488 : : }
489 [ + - ]: 5 : if (argc == 0) {
490 : : char buf[MAXPATHLEN];
491 : : struct passwd *pw;
492 : : struct stat st;
493 : 0 : int count = 0;
494 : :
495 [ # # ]: 0 : if ((pw = getpwuid(getuid())) == NULL) {
496 : 0 : fprintf(stderr, "No user found with uid %u\n",
497 : : (u_int)getuid());
498 : 0 : ret = 1;
499 : 0 : goto done;
500 : : }
501 : :
502 [ # # ]: 0 : for (i = 0; default_files[i]; i++) {
503 : 0 : snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir,
504 : : default_files[i]);
505 [ # # ]: 0 : if (stat(buf, &st) < 0)
506 : 0 : continue;
507 [ # # ]: 0 : if (do_file(ac, deleting, key_only, buf) == -1)
508 : : ret = 1;
509 : : else
510 : 0 : count++;
511 : : }
512 [ # # ]: 0 : if (count == 0)
513 : 0 : ret = 1;
514 : : } else {
515 [ + + ]: 10 : for (i = 0; i < argc; i++) {
516 [ - + ]: 5 : if (do_file(ac, deleting, key_only, argv[i]) == -1)
517 : 0 : ret = 1;
518 : : }
519 : : }
520 : 5 : clear_pass();
521 : :
522 : : done:
523 : 13 : ssh_close_authentication_connection(ac);
524 : 13 : return ret;
525 : : }
|