1 /*        $NetBSD: ssh-add.c,v 1.32 2024/09/24 21:32:19 christos Exp $          */
2 /* $OpenBSD: ssh-add.c,v 1.173 2024/09/06 02:30:44 djm Exp $ */
3 
4 /*
5  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
7  *                    All rights reserved
8  * Adds an identity to the authentication server, or removes an identity.
9  *
10  * As far as I am concerned, the code I have written for this software
11  * can be used freely for any purpose.  Any derived versions of this
12  * software must be clearly marked as such, and if the derived work is
13  * incompatible with the protocol description in the RFC file, it must be
14  * called by a name other than "ssh" or "Secure Shell".
15  *
16  * SSH2 implementation,
17  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
18  *
19  * Redistribution and use in source and binary forms, with or without
20  * modification, are permitted provided that the following conditions
21  * are met:
22  * 1. Redistributions of source code must retain the above copyright
23  *    notice, this list of conditions and the following disclaimer.
24  * 2. Redistributions in binary form must reproduce the above copyright
25  *    notice, this list of conditions and the following disclaimer in the
26  *    documentation and/or other materials provided with the distribution.
27  *
28  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
29  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
31  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
32  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
37  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 #include "includes.h"
41 __RCSID("$NetBSD: ssh-add.c,v 1.32 2024/09/24 21:32:19 christos Exp $");
42 #include <sys/types.h>
43 #include <sys/stat.h>
44 
45 #ifdef WITH_OPENSSL
46 #include <openssl/evp.h>
47 #endif
48 
49 #include <errno.h>
50 #include <fcntl.h>
51 #include <pwd.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stdarg.h>
56 #include <unistd.h>
57 #include <limits.h>
58 
59 #include "xmalloc.h"
60 #include "ssh.h"
61 #include "log.h"
62 #include "sshkey.h"
63 #include "sshbuf.h"
64 #include "authfd.h"
65 #include "authfile.h"
66 #include "pathnames.h"
67 #include "misc.h"
68 #include "ssherr.h"
69 #include "digest.h"
70 #include "ssh-sk.h"
71 #include "sk-api.h"
72 #include "hostfile.h"
73 
74 /* argv0 */
75 extern char *__progname;
76 
77 /* Default files to add */
78 static const char *default_files[] = {
79           _PATH_SSH_CLIENT_ID_RSA,
80           _PATH_SSH_CLIENT_ID_ECDSA,
81           _PATH_SSH_CLIENT_ID_ECDSA_SK,
82           _PATH_SSH_CLIENT_ID_ED25519,
83           _PATH_SSH_CLIENT_ID_ED25519_SK,
84           _PATH_SSH_CLIENT_ID_XMSS,
85 #ifdef WITH_DSA
86           _PATH_SSH_CLIENT_ID_DSA,
87 #endif
88           NULL
89 };
90 
91 static int fingerprint_hash = SSH_FP_HASH_DEFAULT;
92 
93 /* Default lifetime (0 == forever) */
94 static int lifetime = 0;
95 
96 /* User has to confirm key use */
97 static int confirm = 0;
98 
99 /* Maximum number of signatures (XMSS) */
100 static u_int maxsign = 0;
101 static u_int minleft = 0;
102 
103 /* we keep a cache of one passphrase */
104 static char *pass = NULL;
105 static void
clear_pass(void)106 clear_pass(void)
107 {
108           if (pass) {
109                     freezero(pass, strlen(pass));
110                     pass = NULL;
111           }
112 }
113 
114 static int
delete_one(int agent_fd,const struct sshkey * key,const char * comment,const char * path,int qflag)115 delete_one(int agent_fd, const struct sshkey *key, const char *comment,
116     const char *path, int qflag)
117 {
118           int r;
119 
120           if ((r = ssh_remove_identity(agent_fd, key)) != 0) {
121                     fprintf(stderr, "Could not remove identity \"%s\": %s\n",
122                         path, ssh_err(r));
123                     return r;
124           }
125           if (!qflag) {
126                     fprintf(stderr, "Identity removed: %s %s (%s)\n", path,
127                         sshkey_type(key), comment ? comment : "no comment");
128           }
129           return 0;
130 }
131 
132 static int
delete_stdin(int agent_fd,int qflag,int key_only,int cert_only)133 delete_stdin(int agent_fd, int qflag, int key_only, int cert_only)
134 {
135           char *line = NULL, *cp;
136           size_t linesize = 0;
137           struct sshkey *key = NULL;
138           int lnum = 0, r, ret = -1;
139 
140           while (getline(&line, &linesize, stdin) != -1) {
141                     lnum++;
142                     sshkey_free(key);
143                     key = NULL;
144                     line[strcspn(line, "\n")] = '\0';
145                     cp = line + strspn(line, " \t");
146                     if (*cp == '#' || *cp == '\0')
147                               continue;
148                     if ((key = sshkey_new(KEY_UNSPEC)) == NULL)
149                               fatal_f("sshkey_new");
150                     if ((r = sshkey_read(key, &cp)) != 0) {
151                               error_r(r, "(stdin):%d: invalid key", lnum);
152                               continue;
153                     }
154                     if ((!key_only && !cert_only) ||
155                         (key_only && !sshkey_is_cert(key)) ||
156                         (cert_only && sshkey_is_cert(key))) {
157                               if (delete_one(agent_fd, key, cp,
158                                   "(stdin)", qflag) == 0)
159                                         ret = 0;
160                     }
161           }
162           sshkey_free(key);
163           free(line);
164           return ret;
165 }
166 
167 static int
delete_file(int agent_fd,const char * filename,int key_only,int cert_only,int qflag)168 delete_file(int agent_fd, const char *filename, int key_only,
169     int cert_only, int qflag)
170 {
171           struct sshkey *public, *cert = NULL;
172           char *certpath = NULL, *comment = NULL;
173           int r, ret = -1;
174 
175           if (strcmp(filename, "-") == 0)
176                     return delete_stdin(agent_fd, qflag, key_only, cert_only);
177 
178           if ((r = sshkey_load_public(filename, &public,  &comment)) != 0) {
179                     printf("Bad key file %s: %s\n", filename, ssh_err(r));
180                     return -1;
181           }
182           if ((!key_only && !cert_only) ||
183               (key_only && !sshkey_is_cert(public)) ||
184               (cert_only && sshkey_is_cert(public))) {
185                     if (delete_one(agent_fd, public, comment, filename, qflag) == 0)
186                               ret = 0;
187           }
188 
189           if (key_only)
190                     goto out;
191 
192           /* Now try to delete the corresponding certificate too */
193           free(comment);
194           comment = NULL;
195           xasprintf(&certpath, "%s-cert.pub", filename);
196           if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) {
197                     if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
198                               error_r(r, "Failed to load certificate \"%s\"", certpath);
199                     goto out;
200           }
201 
202           if (!sshkey_equal_public(cert, public))
203                     fatal("Certificate %s does not match private key %s",
204                         certpath, filename);
205 
206           if (delete_one(agent_fd, cert, comment, certpath, qflag) == 0)
207                     ret = 0;
208 
209  out:
210           sshkey_free(cert);
211           sshkey_free(public);
212           free(certpath);
213           free(comment);
214 
215           return ret;
216 }
217 
218 /* Send a request to remove all identities. */
219 static int
delete_all(int agent_fd,int qflag)220 delete_all(int agent_fd, int qflag)
221 {
222           int ret = -1;
223 
224           /*
225            * Since the agent might be forwarded, old or non-OpenSSH, when asked
226            * to remove all keys, attempt to remove both protocol v.1 and v.2
227            * keys.
228            */
229           if (ssh_remove_all_identities(agent_fd, 2) == 0)
230                     ret = 0;
231           /* ignore error-code for ssh1 */
232           ssh_remove_all_identities(agent_fd, 1);
233 
234           if (ret != 0)
235                     fprintf(stderr, "Failed to remove all identities.\n");
236           else if (!qflag)
237                     fprintf(stderr, "All identities removed.\n");
238 
239           return ret;
240 }
241 
242 static int
add_file(int agent_fd,const char * filename,int key_only,int cert_only,int qflag,const char * skprovider,struct dest_constraint ** dest_constraints,size_t ndest_constraints)243 add_file(int agent_fd, const char *filename, int key_only, int cert_only,
244     int qflag, const char *skprovider,
245     struct dest_constraint **dest_constraints,
246     size_t ndest_constraints)
247 {
248           struct sshkey *private, *cert;
249           char *comment = NULL;
250           char msg[1024], *certpath = NULL;
251           int r, fd, ret = -1;
252           size_t i;
253           u_int32_t left;
254           struct sshbuf *keyblob;
255           struct ssh_identitylist *idlist;
256 
257           if (strcmp(filename, "-") == 0) {
258                     fd = STDIN_FILENO;
259                     filename = "(stdin)";
260           } else if ((fd = open(filename, O_RDONLY)) == -1) {
261                     perror(filename);
262                     return -1;
263           }
264 
265           /*
266            * Since we'll try to load a keyfile multiple times, permission errors
267            * will occur multiple times, so check perms first and bail if wrong.
268            */
269           if (fd != STDIN_FILENO) {
270                     if (sshkey_perm_ok(fd, filename) != 0) {
271                               close(fd);
272                               return -1;
273                     }
274           }
275           if ((r = sshbuf_load_fd(fd, &keyblob)) != 0) {
276                     fprintf(stderr, "Error loading key \"%s\": %s\n",
277                         filename, ssh_err(r));
278                     sshbuf_free(keyblob);
279                     close(fd);
280                     return -1;
281           }
282           close(fd);
283 
284           /* At first, try empty passphrase */
285           if ((r = sshkey_parse_private_fileblob(keyblob, "", &private,
286               &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
287                     fprintf(stderr, "Error loading key \"%s\": %s\n",
288                         filename, ssh_err(r));
289                     goto fail_load;
290           }
291           /* try last */
292           if (private == NULL && pass != NULL) {
293                     if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private,
294                         &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
295                               fprintf(stderr, "Error loading key \"%s\": %s\n",
296                                   filename, ssh_err(r));
297                               goto fail_load;
298                     }
299           }
300           if (private == NULL) {
301                     /* clear passphrase since it did not work */
302                     clear_pass();
303                     snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ",
304                         filename, confirm ? " (will confirm each use)" : "");
305                     for (;;) {
306                               pass = read_passphrase(msg, RP_ALLOW_STDIN);
307                               if (strcmp(pass, "") == 0)
308                                         goto fail_load;
309                               if ((r = sshkey_parse_private_fileblob(keyblob, pass,
310                                   &private, &comment)) == 0)
311                                         break;
312                               else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
313                                         fprintf(stderr,
314                                             "Error loading key \"%s\": %s\n",
315                                             filename, ssh_err(r));
316  fail_load:
317                                         clear_pass();
318                                         sshbuf_free(keyblob);
319                                         return -1;
320                               }
321                               clear_pass();
322                               snprintf(msg, sizeof msg,
323                                   "Bad passphrase, try again for %s%s: ", filename,
324                                   confirm ? " (will confirm each use)" : "");
325                     }
326           }
327           if (comment == NULL || *comment == '\0')
328                     comment = xstrdup(filename);
329           sshbuf_free(keyblob);
330 
331           /* For XMSS */
332           if ((r = sshkey_set_filename(private, filename)) != 0) {
333                     fprintf(stderr, "Could not add filename to private key: %s (%s)\n",
334                         filename, comment);
335                     goto out;
336           }
337           if (maxsign && minleft &&
338               (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) {
339                     for (i = 0; i < idlist->nkeys; i++) {
340                               if (!sshkey_equal_public(idlist->keys[i], private))
341                                         continue;
342                               left = sshkey_signatures_left(idlist->keys[i]);
343                               if (left < minleft) {
344                                         fprintf(stderr,
345                                             "Only %d signatures left.\n", left);
346                                         break;
347                               }
348                               fprintf(stderr, "Skipping update: ");
349                               if (left == minleft) {
350                                         fprintf(stderr,
351                                             "required signatures left (%d).\n", left);
352                               } else {
353                                         fprintf(stderr,
354                                             "more signatures left (%d) than"
355                                             " required (%d).\n", left, minleft);
356                               }
357                               ssh_free_identitylist(idlist);
358                               goto out;
359                     }
360                     ssh_free_identitylist(idlist);
361           }
362 
363           if (sshkey_is_sk(private)) {
364                     if (skprovider == NULL) {
365                               fprintf(stderr, "Cannot load FIDO key %s "
366                                   "without provider\n", filename);
367                               goto out;
368                     }
369           } else {
370                     /* Don't send provider constraint for other keys */
371                     skprovider = NULL;
372           }
373 
374           if (!cert_only &&
375               (r = ssh_add_identity_constrained(agent_fd, private, comment,
376               lifetime, confirm, maxsign, skprovider,
377               dest_constraints, ndest_constraints)) == 0) {
378                     ret = 0;
379                     if (!qflag) {
380                               fprintf(stderr, "Identity added: %s (%s)\n",
381                                   filename, comment);
382                               if (lifetime != 0) {
383                                         fprintf(stderr,
384                                             "Lifetime set to %d seconds\n", lifetime);
385                               }
386                               if (confirm != 0) {
387                                         fprintf(stderr, "The user must confirm "
388                                             "each use of the key\n");
389                               }
390                     }
391           } else {
392                     fprintf(stderr, "Could not add identity \"%s\": %s\n",
393                         filename, ssh_err(r));
394           }
395 
396           /* Skip trying to load the cert if requested */
397           if (key_only)
398                     goto out;
399 
400           /* Now try to add the certificate flavour too */
401           xasprintf(&certpath, "%s-cert.pub", filename);
402           if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) {
403                     if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
404                               error_r(r, "Failed to load certificate \"%s\"",
405                                   certpath);
406                     goto out;
407           }
408 
409           if (!sshkey_equal_public(cert, private)) {
410                     error("Certificate %s does not match private key %s",
411                         certpath, filename);
412                     sshkey_free(cert);
413                     goto out;
414           }
415 
416           /* Graft with private bits */
417           if ((r = sshkey_to_certified(private)) != 0) {
418                     error_fr(r, "sshkey_to_certified");
419                     sshkey_free(cert);
420                     goto out;
421           }
422           if ((r = sshkey_cert_copy(cert, private)) != 0) {
423                     error_fr(r, "sshkey_cert_copy");
424                     sshkey_free(cert);
425                     goto out;
426           }
427           sshkey_free(cert);
428 
429           if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
430               lifetime, confirm, maxsign, skprovider,
431               dest_constraints, ndest_constraints)) != 0) {
432                     error_r(r, "Certificate %s (%s) add failed", certpath,
433                         private->cert->key_id);
434                     goto out;
435           }
436           /* success */
437           if (!qflag) {
438                     fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
439                         private->cert->key_id);
440                     if (lifetime != 0) {
441                               fprintf(stderr, "Lifetime set to %d seconds\n",
442                                   lifetime);
443                     }
444                     if (confirm != 0) {
445                               fprintf(stderr, "The user must confirm each use "
446                                   "of the key\n");
447                     }
448           }
449 
450  out:
451           free(certpath);
452           free(comment);
453           sshkey_free(private);
454 
455           return ret;
456 }
457 
458 static int
update_card(int agent_fd,int add,const char * id,int qflag,int key_only,int cert_only,struct dest_constraint ** dest_constraints,size_t ndest_constraints,struct sshkey ** certs,size_t ncerts)459 update_card(int agent_fd, int add, const char *id, int qflag,
460     int key_only, int cert_only,
461     struct dest_constraint **dest_constraints, size_t ndest_constraints,
462     struct sshkey **certs, size_t ncerts)
463 {
464           char *pin = NULL;
465           int r, ret = -1;
466 
467           if (key_only)
468                     ncerts = 0;
469 
470           if (add) {
471                     if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
472                         RP_ALLOW_STDIN)) == NULL)
473                               return -1;
474           }
475 
476           if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin,
477               lifetime, confirm, dest_constraints, ndest_constraints,
478               cert_only, certs, ncerts)) == 0) {
479                     ret = 0;
480                     if (!qflag) {
481                               fprintf(stderr, "Card %s: %s\n",
482                                   add ? "added" : "removed", id);
483                     }
484           } else {
485                     fprintf(stderr, "Could not %s card \"%s\": %s\n",
486                         add ? "add" : "remove", id, ssh_err(r));
487                     ret = -1;
488           }
489           free(pin);
490           return ret;
491 }
492 
493 static int
test_key(int agent_fd,const char * filename)494 test_key(int agent_fd, const char *filename)
495 {
496           struct sshkey *key = NULL;
497           u_char *sig = NULL;
498           const char *alg = NULL;
499           size_t slen = 0;
500           int r, ret = -1;
501           u_char data[1024];
502 
503           if ((r = sshkey_load_public(filename, &key, NULL)) != 0) {
504                     error_r(r, "Couldn't read public key %s", filename);
505                     return -1;
506           }
507           if (sshkey_type_plain(key->type) == KEY_RSA)
508                     alg = "rsa-sha2-256";
509           arc4random_buf(data, sizeof(data));
510           if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data),
511               alg, 0)) != 0) {
512                     error_r(r, "Agent signature failed for %s", filename);
513                     goto done;
514           }
515           if ((r = sshkey_verify(key, sig, slen, data, sizeof(data),
516               alg, 0, NULL)) != 0) {
517                     error_r(r, "Signature verification failed for %s", filename);
518                     goto done;
519           }
520           /* success */
521           ret = 0;
522  done:
523           free(sig);
524           sshkey_free(key);
525           return ret;
526 }
527 
528 static int
list_identities(int agent_fd,int do_fp)529 list_identities(int agent_fd, int do_fp)
530 {
531           char *fp;
532           int r;
533           struct ssh_identitylist *idlist;
534           u_int32_t left;
535           size_t i;
536 
537           if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) {
538                     if (r != SSH_ERR_AGENT_NO_IDENTITIES)
539                               fprintf(stderr, "error fetching identities: %s\n",
540                                   ssh_err(r));
541                     else
542                               printf("The agent has no identities.\n");
543                     return -1;
544           }
545           for (i = 0; i < idlist->nkeys; i++) {
546                     if (do_fp) {
547                               fp = sshkey_fingerprint(idlist->keys[i],
548                                   fingerprint_hash, SSH_FP_DEFAULT);
549                               printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]),
550                                   fp == NULL ? "(null)" : fp, idlist->comments[i],
551                                   sshkey_type(idlist->keys[i]));
552                               free(fp);
553                     } else {
554                               if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) {
555                                         fprintf(stderr, "sshkey_write: %s\n",
556                                             ssh_err(r));
557                                         continue;
558                               }
559                               fprintf(stdout, " %s", idlist->comments[i]);
560                               left = sshkey_signatures_left(idlist->keys[i]);
561                               if (left > 0)
562                                         fprintf(stdout,
563                                             " [signatures left %d]", left);
564                               fprintf(stdout, "\n");
565                     }
566           }
567           ssh_free_identitylist(idlist);
568           return 0;
569 }
570 
571 static int
lock_agent(int agent_fd,int lock)572 lock_agent(int agent_fd, int lock)
573 {
574           char prompt[100], *p1, *p2;
575           int r, passok = 1, ret = -1;
576 
577           strlcpy(prompt, "Enter lock password: ", sizeof(prompt));
578           p1 = read_passphrase(prompt, RP_ALLOW_STDIN);
579           if (lock) {
580                     strlcpy(prompt, "Again: ", sizeof prompt);
581                     p2 = read_passphrase(prompt, RP_ALLOW_STDIN);
582                     if (strcmp(p1, p2) != 0) {
583                               fprintf(stderr, "Passwords do not match.\n");
584                               passok = 0;
585                     }
586                     freezero(p2, strlen(p2));
587           }
588           if (passok) {
589                     if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) {
590                               fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un");
591                               ret = 0;
592                     } else {
593                               fprintf(stderr, "Failed to %slock agent: %s\n",
594                                   lock ? "" : "un", ssh_err(r));
595                     }
596           }
597           freezero(p1, strlen(p1));
598           return (ret);
599 }
600 
601 static int
load_resident_keys(int agent_fd,const char * skprovider,int qflag,struct dest_constraint ** dest_constraints,size_t ndest_constraints)602 load_resident_keys(int agent_fd, const char *skprovider, int qflag,
603     struct dest_constraint **dest_constraints, size_t ndest_constraints)
604 {
605           struct sshsk_resident_key **srks;
606           size_t nsrks, i;
607           struct sshkey *key;
608           int r, ok = 0;
609           char *fp;
610 
611           pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN);
612           if ((r = sshsk_load_resident(skprovider, NULL, pass, 0,
613               &srks, &nsrks)) != 0) {
614                     error_r(r, "Unable to load resident keys");
615                     return r;
616           }
617           for (i = 0; i < nsrks; i++) {
618                     key = srks[i]->key;
619                     if ((fp = sshkey_fingerprint(key,
620                         fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
621                               fatal_f("sshkey_fingerprint failed");
622                     if ((r = ssh_add_identity_constrained(agent_fd, key, "",
623                         lifetime, confirm, maxsign, skprovider,
624                         dest_constraints, ndest_constraints)) != 0) {
625                               error("Unable to add key %s %s",
626                                   sshkey_type(key), fp);
627                               free(fp);
628                               ok = r;
629                               continue;
630                     }
631                     if (ok == 0)
632                               ok = 1;
633                     if (!qflag) {
634                               fprintf(stderr, "Resident identity added: %s %s\n",
635                                   sshkey_type(key), fp);
636                               if (lifetime != 0) {
637                                         fprintf(stderr,
638                                             "Lifetime set to %d seconds\n", lifetime);
639                               }
640                               if (confirm != 0) {
641                                         fprintf(stderr, "The user must confirm "
642                                             "each use of the key\n");
643                               }
644                     }
645                     free(fp);
646           }
647           sshsk_free_resident_keys(srks, nsrks);
648           if (nsrks == 0)
649                     return SSH_ERR_KEY_NOT_FOUND;
650           return ok == 1 ? 0 : ok;
651 }
652 
653 static int
do_file(int agent_fd,int deleting,int key_only,int cert_only,char * file,int qflag,const char * skprovider,struct dest_constraint ** dest_constraints,size_t ndest_constraints)654 do_file(int agent_fd, int deleting, int key_only, int cert_only,
655     char *file, int qflag, const char *skprovider,
656     struct dest_constraint **dest_constraints, size_t ndest_constraints)
657 {
658           if (deleting) {
659                     if (delete_file(agent_fd, file, key_only,
660                         cert_only, qflag) == -1)
661                               return -1;
662           } else {
663                     if (add_file(agent_fd, file, key_only, cert_only, qflag,
664                         skprovider, dest_constraints, ndest_constraints) == -1)
665                               return -1;
666           }
667           return 0;
668 }
669 
670 /* Append string 's' to a NULL-terminated array of strings */
671 static void
stringlist_append(char *** listp,const char * s)672 stringlist_append(char ***listp, const char *s)
673 {
674           size_t i = 0;
675 
676           if (*listp == NULL)
677                     *listp = xcalloc(2, sizeof(**listp));
678           else {
679                     for (i = 0; (*listp)[i] != NULL; i++)
680                               ; /* count */
681                     *listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp));
682           }
683           (*listp)[i] = xstrdup(s);
684 }
685 
686 static void
parse_dest_constraint_hop(const char * s,struct dest_constraint_hop * dch,char ** hostkey_files)687 parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch,
688     char **hostkey_files)
689 {
690           char *user = NULL, *host, *os, *path;
691           size_t i;
692           struct hostkeys *hostkeys;
693           const struct hostkey_entry *hke;
694           int r, want_ca;
695 
696           memset(dch, '\0', sizeof(*dch));
697           os = xstrdup(s);
698           if ((host = strrchr(os, '@')) == NULL)
699                     host = os;
700           else {
701                     *host++ = '\0';
702                     user = os;
703           }
704           cleanhostname(host);
705           /* Trivial case: username@ (all hosts) */
706           if (*host == '\0') {
707                     if (user == NULL) {
708                               fatal("Invalid key destination constraint \"%s\": "
709                                   "does not specify user or host", s);
710                     }
711                     dch->user = xstrdup(user);
712                     /* other fields left blank */
713                     free(os);
714                     return;
715           }
716           if (hostkey_files == NULL)
717                     fatal_f("no hostkey files");
718           /* Otherwise we need to look up the keys for this hostname */
719           hostkeys = init_hostkeys();
720           for (i = 0; hostkey_files[i]; i++) {
721                     path = tilde_expand_filename(hostkey_files[i], getuid());
722                     debug2_f("looking up host keys for \"%s\" in %s", host, path);
723                 load_hostkeys(hostkeys, host, path, 0);
724                     free(path);
725           }
726           dch->user = user == NULL ? NULL : xstrdup(user);
727           dch->hostname = xstrdup(host);
728           for (i = 0; i < hostkeys->num_entries; i++) {
729                     hke = hostkeys->entries + i;
730                     want_ca = hke->marker == MRK_CA;
731                     if (hke->marker != MRK_NONE && !want_ca)
732                               continue;
733                     debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u",
734                         user == NULL ? "": user, user == NULL ? "" : "@",
735                         host, sshkey_type(hke->key), want_ca ? "CA " : "",
736                         hke->file, hke->line, dch->nkeys);
737                     dch->keys = xrecallocarray(dch->keys, dch->nkeys,
738                         dch->nkeys + 1, sizeof(*dch->keys));
739                     dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys,
740                         dch->nkeys + 1, sizeof(*dch->key_is_ca));
741                     if ((r = sshkey_from_private(hke->key,
742                         &(dch->keys[dch->nkeys]))) != 0)
743                               fatal_fr(r, "sshkey_from_private");
744                     dch->key_is_ca[dch->nkeys] = want_ca;
745                     dch->nkeys++;
746           }
747           if (dch->nkeys == 0)
748                     fatal("No host keys found for destination \"%s\"", host);
749           free_hostkeys(hostkeys);
750           free(os);
751           return;
752 }
753 
754 static void
parse_dest_constraint(const char * s,struct dest_constraint *** dcp,size_t * ndcp,char ** hostkey_files)755 parse_dest_constraint(const char *s, struct dest_constraint ***dcp,
756     size_t *ndcp, char **hostkey_files)
757 {
758           struct dest_constraint *dc;
759           char *os, *cp;
760 
761           dc = xcalloc(1, sizeof(*dc));
762           os = xstrdup(s);
763           if ((cp = strchr(os, '>')) == NULL) {
764                     /* initial hop; no 'from' hop specified */
765                     parse_dest_constraint_hop(os, &dc->to, hostkey_files);
766           } else {
767                     /* two hops specified */
768                     *(cp++) = '\0';
769                     parse_dest_constraint_hop(os, &dc->from, hostkey_files);
770                     parse_dest_constraint_hop(cp, &dc->to, hostkey_files);
771                     if (dc->from.user != NULL) {
772                               fatal("Invalid key constraint %s: cannot specify "
773                                   "user on 'from' host", os);
774                     }
775           }
776           /* XXX eliminate or error on duplicates */
777           debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp,
778               dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "",
779               dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys,
780               dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "",
781               dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys);
782           *dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp));
783           (*dcp)[(*ndcp)++] = dc;
784           free(os);
785 }
786 
787 
788 static void
usage(void)789 usage(void)
790 {
791           fprintf(stderr,
792 "usage: ssh-add [-CcDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n"
793 "               [-h destination_constraint] [-S provider] [-t life]\n"
794 #ifdef WITH_XMSS
795 "               [-M maxsign] [-m minleft]\n"
796 #endif
797 "               [file ...]\n"
798 "       ssh-add -s pkcs11 [-Cv] [certificate ...]\n"
799 "       ssh-add -e pkcs11\n"
800 "       ssh-add -T pubkey ...\n"
801           );
802 }
803 
804 int
main(int argc,char ** argv)805 main(int argc, char **argv)
806 {
807           extern char *optarg;
808           extern int optind;
809           int agent_fd;
810           char *pkcs11provider = NULL;
811           const char *skprovider = NULL;
812           char **dest_constraint_strings = NULL, **hostkey_files = NULL;
813           int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0;
814           int do_download = 0, xflag = 0, lflag = 0, Dflag = 0;
815           int qflag = 0, Tflag = 0;
816           SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
817           LogLevel log_level = SYSLOG_LEVEL_INFO;
818           struct sshkey *k, **certs = NULL;
819           struct dest_constraint **dest_constraints = NULL;
820           size_t ndest_constraints = 0, ncerts = 0;
821 
822           /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
823           sanitise_stdfd();
824 
825 #ifdef WITH_OPENSSL
826           OpenSSL_add_all_algorithms();
827 #endif
828           log_init(__progname, log_level, log_facility, 1);
829 
830           setvbuf(stdout, NULL, _IOLBF, 0);
831 
832           /* First, get a connection to the authentication agent. */
833           switch (r = ssh_get_authentication_socket(&agent_fd)) {
834           case 0:
835                     break;
836           case SSH_ERR_AGENT_NOT_PRESENT:
837                     fprintf(stderr, "Could not open a connection to your "
838                         "authentication agent.\n");
839                     exit(2);
840           default:
841                     fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r));
842                     exit(2);
843           }
844 
845           skprovider = getenv("SSH_SK_PROVIDER");
846 
847           while ((ch = getopt(argc, argv, "vkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
848                     switch (ch) {
849                     case 'v':
850                               if (log_level == SYSLOG_LEVEL_INFO)
851                                         log_level = SYSLOG_LEVEL_DEBUG1;
852                               else if (log_level < SYSLOG_LEVEL_DEBUG3)
853                                         log_level++;
854                               break;
855                     case 'E':
856                               fingerprint_hash = ssh_digest_alg_by_name(optarg);
857                               if (fingerprint_hash == -1)
858                                         fatal("Invalid hash algorithm \"%s\"", optarg);
859                               break;
860                     case 'H':
861                               stringlist_append(&hostkey_files, optarg);
862                               break;
863                     case 'h':
864                               stringlist_append(&dest_constraint_strings, optarg);
865                               break;
866                     case 'k':
867                               key_only = 1;
868                               break;
869                     case 'C':
870                               cert_only = 1;
871                               break;
872                     case 'K':
873                               do_download = 1;
874                               break;
875                     case 'l':
876                     case 'L':
877                               if (lflag != 0)
878                                         fatal("-%c flag already specified", lflag);
879                               lflag = ch;
880                               break;
881                     case 'x':
882                     case 'X':
883                               if (xflag != 0)
884                                         fatal("-%c flag already specified", xflag);
885                               xflag = ch;
886                               break;
887                     case 'c':
888                               confirm = 1;
889                               break;
890                     case 'm':
891                               minleft = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
892                               if (minleft == 0) {
893                                         usage();
894                                         ret = 1;
895                                         goto done;
896                               }
897                               break;
898                     case 'M':
899                               maxsign = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
900                               if (maxsign == 0) {
901                                         usage();
902                                         ret = 1;
903                                         goto done;
904                               }
905                               break;
906                     case 'd':
907                               deleting = 1;
908                               break;
909                     case 'D':
910                               Dflag = 1;
911                               break;
912                     case 's':
913                               pkcs11provider = optarg;
914                               break;
915                     case 'S':
916                               skprovider = optarg;
917                               break;
918                     case 'e':
919                               deleting = 1;
920                               pkcs11provider = optarg;
921                               break;
922                     case 't':
923                               if ((lifetime = convtime(optarg)) == -1 ||
924                                   lifetime < 0 || (u_long)lifetime > UINT32_MAX) {
925                                         fprintf(stderr, "Invalid lifetime\n");
926                                         ret = 1;
927                                         goto done;
928                               }
929                               break;
930                     case 'q':
931                               qflag = 1;
932                               break;
933                     case 'T':
934                               Tflag = 1;
935                               break;
936                     default:
937                               usage();
938                               ret = 1;
939                               goto done;
940                     }
941           }
942           log_init(__progname, log_level, log_facility, 1);
943 
944           if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1)
945                     fatal("Invalid combination of actions");
946           else if (xflag) {
947                     if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1)
948                               ret = 1;
949                     goto done;
950           } else if (lflag) {
951                     if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1)
952                               ret = 1;
953                     goto done;
954           } else if (Dflag) {
955                     if (delete_all(agent_fd, qflag) == -1)
956                               ret = 1;
957                     goto done;
958           }
959 
960           if (skprovider == NULL)
961                     skprovider = "internal";
962           if (hostkey_files == NULL) {
963                     /* use defaults from readconf.c */
964                     stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE);
965                     stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2);
966                     stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE);
967                     stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2);
968           }
969           if (dest_constraint_strings != NULL) {
970                     for (i = 0; dest_constraint_strings[i] != NULL; i++) {
971                               parse_dest_constraint(dest_constraint_strings[i],
972                                 &dest_constraints, &ndest_constraints, hostkey_files);
973                     }
974           }
975 
976           argc -= optind;
977           argv += optind;
978           if (Tflag) {
979                     if (argc <= 0)
980                               fatal("no keys to test");
981                     for (r = i = 0; i < argc; i++)
982                               r |= test_key(agent_fd, argv[i]);
983                     ret = r == 0 ? 0 : 1;
984                     goto done;
985           }
986           if (pkcs11provider != NULL) {
987                     for (i = 0; i < argc; i++) {
988                               if ((r = sshkey_load_public(argv[i], &k, NULL)) != 0)
989                                         fatal_fr(r, "load certificate %s", argv[i]);
990                               certs = xrecallocarray(certs, ncerts, ncerts + 1,
991                                   sizeof(*certs));
992                               debug2("%s: %s", argv[i], sshkey_ssh_name(k));
993                               certs[ncerts++] = k;
994                     }
995                     debug2_f("loaded %zu certificates", ncerts);
996                     if (update_card(agent_fd, !deleting, pkcs11provider,
997                         qflag, key_only, cert_only,
998                         dest_constraints, ndest_constraints,
999                         certs, ncerts) == -1)
1000                               ret = 1;
1001                     goto done;
1002           }
1003           if (do_download) {
1004                     if (skprovider == NULL)
1005                               fatal("Cannot download keys without provider");
1006                     if (load_resident_keys(agent_fd, skprovider, qflag,
1007                         dest_constraints, ndest_constraints) != 0)
1008                               ret = 1;
1009                     goto done;
1010           }
1011           if (argc == 0) {
1012                     char buf[PATH_MAX];
1013                     struct passwd *pw;
1014                     struct stat st;
1015                     int count = 0;
1016 
1017                     if ((pw = getpwuid(getuid())) == NULL) {
1018                               fprintf(stderr, "No user found with uid %u\n",
1019                                   (u_int)getuid());
1020                               ret = 1;
1021                               goto done;
1022                     }
1023 
1024                     for (i = 0; default_files[i]; i++) {
1025                               snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir,
1026                                   default_files[i]);
1027                               if (stat(buf, &st) == -1)
1028                                         continue;
1029                               if (do_file(agent_fd, deleting, key_only, cert_only,
1030                                   buf, qflag, skprovider,
1031                                   dest_constraints, ndest_constraints) == -1)
1032                                         ret = 1;
1033                               else
1034                                         count++;
1035                     }
1036                     if (count == 0)
1037                               ret = 1;
1038           } else {
1039                     for (i = 0; i < argc; i++) {
1040                               if (do_file(agent_fd, deleting, key_only, cert_only,
1041                                   argv[i], qflag, skprovider,
1042                                   dest_constraints, ndest_constraints) == -1)
1043                                         ret = 1;
1044                     }
1045           }
1046 done:
1047           clear_pass();
1048           ssh_close_authentication_socket(agent_fd);
1049           return ret;
1050 }
1051