1*ba1276acSMatthew Dillon /* $OpenBSD: ssh-add.c,v 1.172 2024/01/11 01:45:36 djm Exp $ */
218de8d7fSPeter Avalos /*
318de8d7fSPeter Avalos * Author: Tatu Ylonen <ylo@cs.hut.fi>
418de8d7fSPeter Avalos * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
518de8d7fSPeter Avalos * All rights reserved
618de8d7fSPeter Avalos * Adds an identity to the authentication server, or removes an identity.
718de8d7fSPeter Avalos *
818de8d7fSPeter Avalos * As far as I am concerned, the code I have written for this software
918de8d7fSPeter Avalos * can be used freely for any purpose. Any derived versions of this
1018de8d7fSPeter Avalos * software must be clearly marked as such, and if the derived work is
1118de8d7fSPeter Avalos * incompatible with the protocol description in the RFC file, it must be
1218de8d7fSPeter Avalos * called by a name other than "ssh" or "Secure Shell".
1318de8d7fSPeter Avalos *
1418de8d7fSPeter Avalos * SSH2 implementation,
1518de8d7fSPeter Avalos * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved.
1618de8d7fSPeter Avalos *
1718de8d7fSPeter Avalos * Redistribution and use in source and binary forms, with or without
1818de8d7fSPeter Avalos * modification, are permitted provided that the following conditions
1918de8d7fSPeter Avalos * are met:
2018de8d7fSPeter Avalos * 1. Redistributions of source code must retain the above copyright
2118de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer.
2218de8d7fSPeter Avalos * 2. Redistributions in binary form must reproduce the above copyright
2318de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer in the
2418de8d7fSPeter Avalos * documentation and/or other materials provided with the distribution.
2518de8d7fSPeter Avalos *
2618de8d7fSPeter Avalos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2718de8d7fSPeter Avalos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
2818de8d7fSPeter Avalos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2918de8d7fSPeter Avalos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
3018de8d7fSPeter Avalos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
3118de8d7fSPeter Avalos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3218de8d7fSPeter Avalos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3318de8d7fSPeter Avalos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3418de8d7fSPeter Avalos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
3518de8d7fSPeter Avalos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3618de8d7fSPeter Avalos */
3718de8d7fSPeter Avalos
3818de8d7fSPeter Avalos #include "includes.h"
3918de8d7fSPeter Avalos
4018de8d7fSPeter Avalos #include <sys/types.h>
4118de8d7fSPeter Avalos #include <sys/stat.h>
4218de8d7fSPeter Avalos
430cbfa66cSDaniel Fojt #ifdef WITH_OPENSSL
4418de8d7fSPeter Avalos # include <openssl/evp.h>
4518de8d7fSPeter Avalos # include "openbsd-compat/openssl-compat.h"
460cbfa66cSDaniel Fojt #endif
4718de8d7fSPeter Avalos
48e9778795SPeter Avalos #include <errno.h>
4918de8d7fSPeter Avalos #include <fcntl.h>
5018de8d7fSPeter Avalos #include <pwd.h>
5118de8d7fSPeter Avalos #include <stdarg.h>
5218de8d7fSPeter Avalos #include <stdio.h>
5318de8d7fSPeter Avalos #include <stdlib.h>
5418de8d7fSPeter Avalos #include <string.h>
5518de8d7fSPeter Avalos #include <unistd.h>
56e9778795SPeter Avalos #include <limits.h>
5718de8d7fSPeter Avalos
5818de8d7fSPeter Avalos #include "xmalloc.h"
5918de8d7fSPeter Avalos #include "ssh.h"
6018de8d7fSPeter Avalos #include "log.h"
61e9778795SPeter Avalos #include "sshkey.h"
62e9778795SPeter Avalos #include "sshbuf.h"
6318de8d7fSPeter Avalos #include "authfd.h"
6418de8d7fSPeter Avalos #include "authfile.h"
6518de8d7fSPeter Avalos #include "pathnames.h"
6618de8d7fSPeter Avalos #include "misc.h"
6736e94dc5SPeter Avalos #include "ssherr.h"
68e9778795SPeter Avalos #include "digest.h"
690cbfa66cSDaniel Fojt #include "ssh-sk.h"
7050a69bb5SSascha Wildner #include "sk-api.h"
71ee116499SAntonio Huete Jimenez #include "hostfile.h"
7218de8d7fSPeter Avalos
7318de8d7fSPeter Avalos /* argv0 */
7418de8d7fSPeter Avalos extern char *__progname;
7518de8d7fSPeter Avalos
7618de8d7fSPeter Avalos /* Default files to add */
7718de8d7fSPeter Avalos static char *default_files[] = {
78e9778795SPeter Avalos #ifdef WITH_OPENSSL
7918de8d7fSPeter Avalos _PATH_SSH_CLIENT_ID_RSA,
809f304aafSPeter Avalos #ifdef OPENSSL_HAS_ECC
819f304aafSPeter Avalos _PATH_SSH_CLIENT_ID_ECDSA,
820cbfa66cSDaniel Fojt _PATH_SSH_CLIENT_ID_ECDSA_SK,
839f304aafSPeter Avalos #endif
84e9778795SPeter Avalos #endif /* WITH_OPENSSL */
8536e94dc5SPeter Avalos _PATH_SSH_CLIENT_ID_ED25519,
860cbfa66cSDaniel Fojt _PATH_SSH_CLIENT_ID_ED25519_SK,
87664f4763Szrj _PATH_SSH_CLIENT_ID_XMSS,
88*ba1276acSMatthew Dillon #ifdef WITH_DSA
89ee116499SAntonio Huete Jimenez _PATH_SSH_CLIENT_ID_DSA,
90*ba1276acSMatthew Dillon #endif
9118de8d7fSPeter Avalos NULL
9218de8d7fSPeter Avalos };
9318de8d7fSPeter Avalos
94e9778795SPeter Avalos static int fingerprint_hash = SSH_FP_HASH_DEFAULT;
95e9778795SPeter Avalos
9618de8d7fSPeter Avalos /* Default lifetime (0 == forever) */
9750a69bb5SSascha Wildner static int lifetime = 0;
9818de8d7fSPeter Avalos
9918de8d7fSPeter Avalos /* User has to confirm key use */
10018de8d7fSPeter Avalos static int confirm = 0;
10118de8d7fSPeter Avalos
102664f4763Szrj /* Maximum number of signatures (XMSS) */
103664f4763Szrj static u_int maxsign = 0;
104664f4763Szrj static u_int minleft = 0;
105664f4763Szrj
106e9778795SPeter Avalos /* we keep a cache of one passphrase */
10718de8d7fSPeter Avalos static char *pass = NULL;
10818de8d7fSPeter Avalos static void
clear_pass(void)10918de8d7fSPeter Avalos clear_pass(void)
11018de8d7fSPeter Avalos {
11118de8d7fSPeter Avalos if (pass) {
1120cbfa66cSDaniel Fojt freezero(pass, strlen(pass));
11318de8d7fSPeter Avalos pass = NULL;
11418de8d7fSPeter Avalos }
11518de8d7fSPeter Avalos }
11618de8d7fSPeter Avalos
11718de8d7fSPeter Avalos static int
delete_one(int agent_fd,const struct sshkey * key,const char * comment,const char * path,int qflag)11850a69bb5SSascha Wildner delete_one(int agent_fd, const struct sshkey *key, const char *comment,
11950a69bb5SSascha Wildner const char *path, int qflag)
12050a69bb5SSascha Wildner {
12150a69bb5SSascha Wildner int r;
12250a69bb5SSascha Wildner
12350a69bb5SSascha Wildner if ((r = ssh_remove_identity(agent_fd, key)) != 0) {
12450a69bb5SSascha Wildner fprintf(stderr, "Could not remove identity \"%s\": %s\n",
12550a69bb5SSascha Wildner path, ssh_err(r));
12650a69bb5SSascha Wildner return r;
12750a69bb5SSascha Wildner }
12850a69bb5SSascha Wildner if (!qflag) {
12950a69bb5SSascha Wildner fprintf(stderr, "Identity removed: %s %s (%s)\n", path,
130ee116499SAntonio Huete Jimenez sshkey_type(key), comment ? comment : "no comment");
13150a69bb5SSascha Wildner }
13250a69bb5SSascha Wildner return 0;
13350a69bb5SSascha Wildner }
13450a69bb5SSascha Wildner
13550a69bb5SSascha Wildner static int
delete_stdin(int agent_fd,int qflag,int key_only,int cert_only)136*ba1276acSMatthew Dillon delete_stdin(int agent_fd, int qflag, int key_only, int cert_only)
13750a69bb5SSascha Wildner {
13850a69bb5SSascha Wildner char *line = NULL, *cp;
13950a69bb5SSascha Wildner size_t linesize = 0;
14050a69bb5SSascha Wildner struct sshkey *key = NULL;
14150a69bb5SSascha Wildner int lnum = 0, r, ret = -1;
14250a69bb5SSascha Wildner
14350a69bb5SSascha Wildner while (getline(&line, &linesize, stdin) != -1) {
14450a69bb5SSascha Wildner lnum++;
14550a69bb5SSascha Wildner sshkey_free(key);
14650a69bb5SSascha Wildner key = NULL;
14750a69bb5SSascha Wildner line[strcspn(line, "\n")] = '\0';
14850a69bb5SSascha Wildner cp = line + strspn(line, " \t");
14950a69bb5SSascha Wildner if (*cp == '#' || *cp == '\0')
15050a69bb5SSascha Wildner continue;
15150a69bb5SSascha Wildner if ((key = sshkey_new(KEY_UNSPEC)) == NULL)
15250a69bb5SSascha Wildner fatal_f("sshkey_new");
15350a69bb5SSascha Wildner if ((r = sshkey_read(key, &cp)) != 0) {
15450a69bb5SSascha Wildner error_r(r, "(stdin):%d: invalid key", lnum);
15550a69bb5SSascha Wildner continue;
15650a69bb5SSascha Wildner }
157*ba1276acSMatthew Dillon if ((!key_only && !cert_only) ||
158*ba1276acSMatthew Dillon (key_only && !sshkey_is_cert(key)) ||
159*ba1276acSMatthew Dillon (cert_only && sshkey_is_cert(key))) {
160*ba1276acSMatthew Dillon if (delete_one(agent_fd, key, cp,
161*ba1276acSMatthew Dillon "(stdin)", qflag) == 0)
16250a69bb5SSascha Wildner ret = 0;
16350a69bb5SSascha Wildner }
164*ba1276acSMatthew Dillon }
16550a69bb5SSascha Wildner sshkey_free(key);
16650a69bb5SSascha Wildner free(line);
16750a69bb5SSascha Wildner return ret;
16850a69bb5SSascha Wildner }
16950a69bb5SSascha Wildner
17050a69bb5SSascha Wildner static int
delete_file(int agent_fd,const char * filename,int key_only,int cert_only,int qflag)171*ba1276acSMatthew Dillon delete_file(int agent_fd, const char *filename, int key_only,
172*ba1276acSMatthew Dillon int cert_only, int qflag)
17318de8d7fSPeter Avalos {
174e9778795SPeter Avalos struct sshkey *public, *cert = NULL;
17536e94dc5SPeter Avalos char *certpath = NULL, *comment = NULL;
176e9778795SPeter Avalos int r, ret = -1;
17718de8d7fSPeter Avalos
17850a69bb5SSascha Wildner if (strcmp(filename, "-") == 0)
179*ba1276acSMatthew Dillon return delete_stdin(agent_fd, qflag, key_only, cert_only);
18050a69bb5SSascha Wildner
181e9778795SPeter Avalos if ((r = sshkey_load_public(filename, &public, &comment)) != 0) {
182e9778795SPeter Avalos printf("Bad key file %s: %s\n", filename, ssh_err(r));
18318de8d7fSPeter Avalos return -1;
18418de8d7fSPeter Avalos }
185*ba1276acSMatthew Dillon if ((!key_only && !cert_only) ||
186*ba1276acSMatthew Dillon (key_only && !sshkey_is_cert(public)) ||
187*ba1276acSMatthew Dillon (cert_only && sshkey_is_cert(public))) {
18850a69bb5SSascha Wildner if (delete_one(agent_fd, public, comment, filename, qflag) == 0)
18918de8d7fSPeter Avalos ret = 0;
190*ba1276acSMatthew Dillon }
19118de8d7fSPeter Avalos
19236e94dc5SPeter Avalos if (key_only)
19336e94dc5SPeter Avalos goto out;
19436e94dc5SPeter Avalos
19536e94dc5SPeter Avalos /* Now try to delete the corresponding certificate too */
19636e94dc5SPeter Avalos free(comment);
19736e94dc5SPeter Avalos comment = NULL;
19836e94dc5SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename);
199e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) {
200e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
20150a69bb5SSascha Wildner error_r(r, "Failed to load certificate \"%s\"", certpath);
20236e94dc5SPeter Avalos goto out;
203e9778795SPeter Avalos }
204e9778795SPeter Avalos
205e9778795SPeter Avalos if (!sshkey_equal_public(cert, public))
20636e94dc5SPeter Avalos fatal("Certificate %s does not match private key %s",
20736e94dc5SPeter Avalos certpath, filename);
20836e94dc5SPeter Avalos
20950a69bb5SSascha Wildner if (delete_one(agent_fd, cert, comment, certpath, qflag) == 0)
21036e94dc5SPeter Avalos ret = 0;
21136e94dc5SPeter Avalos
21236e94dc5SPeter Avalos out:
213e9778795SPeter Avalos sshkey_free(cert);
214e9778795SPeter Avalos sshkey_free(public);
21536e94dc5SPeter Avalos free(certpath);
21636e94dc5SPeter Avalos free(comment);
21718de8d7fSPeter Avalos
21818de8d7fSPeter Avalos return ret;
21918de8d7fSPeter Avalos }
22018de8d7fSPeter Avalos
22118de8d7fSPeter Avalos /* Send a request to remove all identities. */
22218de8d7fSPeter Avalos static int
delete_all(int agent_fd,int qflag)223664f4763Szrj delete_all(int agent_fd, int qflag)
22418de8d7fSPeter Avalos {
22518de8d7fSPeter Avalos int ret = -1;
22618de8d7fSPeter Avalos
227ce74bacaSMatthew Dillon /*
228ce74bacaSMatthew Dillon * Since the agent might be forwarded, old or non-OpenSSH, when asked
229ce74bacaSMatthew Dillon * to remove all keys, attempt to remove both protocol v.1 and v.2
230ce74bacaSMatthew Dillon * keys.
231ce74bacaSMatthew Dillon */
232e9778795SPeter Avalos if (ssh_remove_all_identities(agent_fd, 2) == 0)
23318de8d7fSPeter Avalos ret = 0;
234e9778795SPeter Avalos /* ignore error-code for ssh1 */
235e9778795SPeter Avalos ssh_remove_all_identities(agent_fd, 1);
23618de8d7fSPeter Avalos
237664f4763Szrj if (ret != 0)
23818de8d7fSPeter Avalos fprintf(stderr, "Failed to remove all identities.\n");
239664f4763Szrj else if (!qflag)
240664f4763Szrj fprintf(stderr, "All identities removed.\n");
24118de8d7fSPeter Avalos
24218de8d7fSPeter Avalos return ret;
24318de8d7fSPeter Avalos }
24418de8d7fSPeter Avalos
24518de8d7fSPeter Avalos 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)246*ba1276acSMatthew Dillon add_file(int agent_fd, const char *filename, int key_only, int cert_only,
247*ba1276acSMatthew Dillon int qflag, const char *skprovider,
248*ba1276acSMatthew Dillon struct dest_constraint **dest_constraints,
249ee116499SAntonio Huete Jimenez size_t ndest_constraints)
25018de8d7fSPeter Avalos {
251e9778795SPeter Avalos struct sshkey *private, *cert;
25218de8d7fSPeter Avalos char *comment = NULL;
25399e85e0dSPeter Avalos char msg[1024], *certpath = NULL;
254e9778795SPeter Avalos int r, fd, ret = -1;
255664f4763Szrj size_t i;
256664f4763Szrj u_int32_t left;
257e9778795SPeter Avalos struct sshbuf *keyblob;
258664f4763Szrj struct ssh_identitylist *idlist;
25918de8d7fSPeter Avalos
2601c188a7fSPeter Avalos if (strcmp(filename, "-") == 0) {
2611c188a7fSPeter Avalos fd = STDIN_FILENO;
2621c188a7fSPeter Avalos filename = "(stdin)";
2630cbfa66cSDaniel Fojt } else if ((fd = open(filename, O_RDONLY)) == -1) {
26418de8d7fSPeter Avalos perror(filename);
26518de8d7fSPeter Avalos return -1;
26618de8d7fSPeter Avalos }
26718de8d7fSPeter Avalos
26818de8d7fSPeter Avalos /*
26918de8d7fSPeter Avalos * Since we'll try to load a keyfile multiple times, permission errors
27018de8d7fSPeter Avalos * will occur multiple times, so check perms first and bail if wrong.
27118de8d7fSPeter Avalos */
2721c188a7fSPeter Avalos if (fd != STDIN_FILENO) {
273e9778795SPeter Avalos if (sshkey_perm_ok(fd, filename) != 0) {
27418de8d7fSPeter Avalos close(fd);
27518de8d7fSPeter Avalos return -1;
2761c188a7fSPeter Avalos }
2771c188a7fSPeter Avalos }
2780cbfa66cSDaniel Fojt if ((r = sshbuf_load_fd(fd, &keyblob)) != 0) {
279e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n",
280e9778795SPeter Avalos filename, ssh_err(r));
281e9778795SPeter Avalos sshbuf_free(keyblob);
2821c188a7fSPeter Avalos close(fd);
2831c188a7fSPeter Avalos return -1;
2841c188a7fSPeter Avalos }
2851c188a7fSPeter Avalos close(fd);
28618de8d7fSPeter Avalos
28718de8d7fSPeter Avalos /* At first, try empty passphrase */
288e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, "", &private,
289e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
290e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n",
291e9778795SPeter Avalos filename, ssh_err(r));
292e9778795SPeter Avalos goto fail_load;
293e9778795SPeter Avalos }
29436e94dc5SPeter Avalos /* try last */
29536e94dc5SPeter Avalos if (private == NULL && pass != NULL) {
296e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private,
297e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
298e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n",
299e9778795SPeter Avalos filename, ssh_err(r));
300e9778795SPeter Avalos goto fail_load;
30136e94dc5SPeter Avalos }
302e9778795SPeter Avalos }
30318de8d7fSPeter Avalos if (private == NULL) {
30418de8d7fSPeter Avalos /* clear passphrase since it did not work */
30518de8d7fSPeter Avalos clear_pass();
306e9778795SPeter Avalos snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ",
307e9778795SPeter Avalos filename, confirm ? " (will confirm each use)" : "");
30818de8d7fSPeter Avalos for (;;) {
30918de8d7fSPeter Avalos pass = read_passphrase(msg, RP_ALLOW_STDIN);
310e9778795SPeter Avalos if (strcmp(pass, "") == 0)
311e9778795SPeter Avalos goto fail_load;
312e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass,
313e9778795SPeter Avalos &private, &comment)) == 0)
314e9778795SPeter Avalos break;
315e9778795SPeter Avalos else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
316e9778795SPeter Avalos fprintf(stderr,
317e9778795SPeter Avalos "Error loading key \"%s\": %s\n",
318e9778795SPeter Avalos filename, ssh_err(r));
319e9778795SPeter Avalos fail_load:
32018de8d7fSPeter Avalos clear_pass();
321e9778795SPeter Avalos sshbuf_free(keyblob);
32218de8d7fSPeter Avalos return -1;
32318de8d7fSPeter Avalos }
32418de8d7fSPeter Avalos clear_pass();
32518de8d7fSPeter Avalos snprintf(msg, sizeof msg,
326e9778795SPeter Avalos "Bad passphrase, try again for %s%s: ", filename,
327e9778795SPeter Avalos confirm ? " (will confirm each use)" : "");
32818de8d7fSPeter Avalos }
32918de8d7fSPeter Avalos }
330e9778795SPeter Avalos if (comment == NULL || *comment == '\0')
331e9778795SPeter Avalos comment = xstrdup(filename);
332e9778795SPeter Avalos sshbuf_free(keyblob);
33318de8d7fSPeter Avalos
334664f4763Szrj /* For XMSS */
335664f4763Szrj if ((r = sshkey_set_filename(private, filename)) != 0) {
336664f4763Szrj fprintf(stderr, "Could not add filename to private key: %s (%s)\n",
337664f4763Szrj filename, comment);
338664f4763Szrj goto out;
339664f4763Szrj }
340664f4763Szrj if (maxsign && minleft &&
341664f4763Szrj (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) {
342664f4763Szrj for (i = 0; i < idlist->nkeys; i++) {
343664f4763Szrj if (!sshkey_equal_public(idlist->keys[i], private))
344664f4763Szrj continue;
345664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]);
346664f4763Szrj if (left < minleft) {
347664f4763Szrj fprintf(stderr,
348664f4763Szrj "Only %d signatures left.\n", left);
349664f4763Szrj break;
350664f4763Szrj }
351664f4763Szrj fprintf(stderr, "Skipping update: ");
352664f4763Szrj if (left == minleft) {
353664f4763Szrj fprintf(stderr,
354664f4763Szrj "required signatures left (%d).\n", left);
355664f4763Szrj } else {
356664f4763Szrj fprintf(stderr,
357664f4763Szrj "more signatures left (%d) than"
358664f4763Szrj " required (%d).\n", left, minleft);
359664f4763Szrj }
360664f4763Szrj ssh_free_identitylist(idlist);
361664f4763Szrj goto out;
362664f4763Szrj }
363664f4763Szrj ssh_free_identitylist(idlist);
364664f4763Szrj }
365664f4763Szrj
36650a69bb5SSascha Wildner if (sshkey_is_sk(private)) {
36750a69bb5SSascha Wildner if (skprovider == NULL) {
36850a69bb5SSascha Wildner fprintf(stderr, "Cannot load FIDO key %s "
3690cbfa66cSDaniel Fojt "without provider\n", filename);
3700cbfa66cSDaniel Fojt goto out;
3710cbfa66cSDaniel Fojt }
37250a69bb5SSascha Wildner } else {
37350a69bb5SSascha Wildner /* Don't send provider constraint for other keys */
37450a69bb5SSascha Wildner skprovider = NULL;
37550a69bb5SSascha Wildner }
3760cbfa66cSDaniel Fojt
377*ba1276acSMatthew Dillon if (!cert_only &&
378*ba1276acSMatthew Dillon (r = ssh_add_identity_constrained(agent_fd, private, comment,
379ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider,
380ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) == 0) {
38118de8d7fSPeter Avalos ret = 0;
382664f4763Szrj if (!qflag) {
383664f4763Szrj fprintf(stderr, "Identity added: %s (%s)\n",
384664f4763Szrj filename, comment);
385664f4763Szrj if (lifetime != 0) {
38618de8d7fSPeter Avalos fprintf(stderr,
38750a69bb5SSascha Wildner "Lifetime set to %d seconds\n", lifetime);
388664f4763Szrj }
389664f4763Szrj if (confirm != 0) {
390664f4763Szrj fprintf(stderr, "The user must confirm "
391664f4763Szrj "each use of the key\n");
392664f4763Szrj }
393664f4763Szrj }
39418de8d7fSPeter Avalos } else {
395e9778795SPeter Avalos fprintf(stderr, "Could not add identity \"%s\": %s\n",
396e9778795SPeter Avalos filename, ssh_err(r));
39718de8d7fSPeter Avalos }
39818de8d7fSPeter Avalos
39999e85e0dSPeter Avalos /* Skip trying to load the cert if requested */
40099e85e0dSPeter Avalos if (key_only)
40199e85e0dSPeter Avalos goto out;
402856ea928SPeter Avalos
403856ea928SPeter Avalos /* Now try to add the certificate flavour too */
404856ea928SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename);
405e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) {
406e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
407*ba1276acSMatthew Dillon error_r(r, "Failed to load certificate \"%s\"",
408*ba1276acSMatthew Dillon certpath);
409856ea928SPeter Avalos goto out;
410e9778795SPeter Avalos }
411856ea928SPeter Avalos
412e9778795SPeter Avalos if (!sshkey_equal_public(cert, private)) {
413856ea928SPeter Avalos error("Certificate %s does not match private key %s",
414856ea928SPeter Avalos certpath, filename);
415e9778795SPeter Avalos sshkey_free(cert);
416856ea928SPeter Avalos goto out;
417856ea928SPeter Avalos }
418856ea928SPeter Avalos
419856ea928SPeter Avalos /* Graft with private bits */
420e9778795SPeter Avalos if ((r = sshkey_to_certified(private)) != 0) {
42150a69bb5SSascha Wildner error_fr(r, "sshkey_to_certified");
422e9778795SPeter Avalos sshkey_free(cert);
423856ea928SPeter Avalos goto out;
424856ea928SPeter Avalos }
425e9778795SPeter Avalos if ((r = sshkey_cert_copy(cert, private)) != 0) {
42650a69bb5SSascha Wildner error_fr(r, "sshkey_cert_copy");
427e9778795SPeter Avalos sshkey_free(cert);
428e9778795SPeter Avalos goto out;
429e9778795SPeter Avalos }
430e9778795SPeter Avalos sshkey_free(cert);
431856ea928SPeter Avalos
432e9778795SPeter Avalos if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
433ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider,
434ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) != 0) {
43550a69bb5SSascha Wildner error_r(r, "Certificate %s (%s) add failed", certpath,
43650a69bb5SSascha Wildner private->cert->key_id);
437e9778795SPeter Avalos goto out;
438856ea928SPeter Avalos }
439664f4763Szrj /* success */
440664f4763Szrj if (!qflag) {
441856ea928SPeter Avalos fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
442856ea928SPeter Avalos private->cert->key_id);
443664f4763Szrj if (lifetime != 0) {
44450a69bb5SSascha Wildner fprintf(stderr, "Lifetime set to %d seconds\n",
445664f4763Szrj lifetime);
446664f4763Szrj }
447664f4763Szrj if (confirm != 0) {
448664f4763Szrj fprintf(stderr, "The user must confirm each use "
449664f4763Szrj "of the key\n");
450664f4763Szrj }
451664f4763Szrj }
452664f4763Szrj
453856ea928SPeter Avalos out:
45436e94dc5SPeter Avalos free(certpath);
45536e94dc5SPeter Avalos free(comment);
456e9778795SPeter Avalos sshkey_free(private);
45718de8d7fSPeter Avalos
45818de8d7fSPeter Avalos return ret;
45918de8d7fSPeter Avalos }
46018de8d7fSPeter Avalos
46118de8d7fSPeter Avalos 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)462ee116499SAntonio Huete Jimenez update_card(int agent_fd, int add, const char *id, int qflag,
463*ba1276acSMatthew Dillon int key_only, int cert_only,
464*ba1276acSMatthew Dillon struct dest_constraint **dest_constraints, size_t ndest_constraints,
465*ba1276acSMatthew Dillon struct sshkey **certs, size_t ncerts)
46618de8d7fSPeter Avalos {
46736e94dc5SPeter Avalos char *pin = NULL;
468e9778795SPeter Avalos int r, ret = -1;
46918de8d7fSPeter Avalos
470*ba1276acSMatthew Dillon if (key_only)
471*ba1276acSMatthew Dillon ncerts = 0;
472*ba1276acSMatthew Dillon
47336e94dc5SPeter Avalos if (add) {
47436e94dc5SPeter Avalos if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
47536e94dc5SPeter Avalos RP_ALLOW_STDIN)) == NULL)
47618de8d7fSPeter Avalos return -1;
47736e94dc5SPeter Avalos }
47818de8d7fSPeter Avalos
479e9778795SPeter Avalos if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin,
480*ba1276acSMatthew Dillon lifetime, confirm, dest_constraints, ndest_constraints,
481*ba1276acSMatthew Dillon cert_only, certs, ncerts)) == 0) {
482664f4763Szrj ret = 0;
483664f4763Szrj if (!qflag) {
48418de8d7fSPeter Avalos fprintf(stderr, "Card %s: %s\n",
48518de8d7fSPeter Avalos add ? "added" : "removed", id);
486664f4763Szrj }
48718de8d7fSPeter Avalos } else {
488e9778795SPeter Avalos fprintf(stderr, "Could not %s card \"%s\": %s\n",
489e9778795SPeter Avalos add ? "add" : "remove", id, ssh_err(r));
49018de8d7fSPeter Avalos ret = -1;
49118de8d7fSPeter Avalos }
49236e94dc5SPeter Avalos free(pin);
49318de8d7fSPeter Avalos return ret;
49418de8d7fSPeter Avalos }
49518de8d7fSPeter Avalos
49618de8d7fSPeter Avalos static int
test_key(int agent_fd,const char * filename)497664f4763Szrj test_key(int agent_fd, const char *filename)
498664f4763Szrj {
499664f4763Szrj struct sshkey *key = NULL;
500664f4763Szrj u_char *sig = NULL;
501*ba1276acSMatthew Dillon const char *alg = NULL;
502664f4763Szrj size_t slen = 0;
503664f4763Szrj int r, ret = -1;
504664f4763Szrj char data[1024];
505664f4763Szrj
506664f4763Szrj if ((r = sshkey_load_public(filename, &key, NULL)) != 0) {
50750a69bb5SSascha Wildner error_r(r, "Couldn't read public key %s", filename);
508664f4763Szrj return -1;
509664f4763Szrj }
510*ba1276acSMatthew Dillon if (sshkey_type_plain(key->type) == KEY_RSA)
511*ba1276acSMatthew Dillon alg = "rsa-sha2-256";
512664f4763Szrj arc4random_buf(data, sizeof(data));
513664f4763Szrj if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data),
514*ba1276acSMatthew Dillon alg, 0)) != 0) {
51550a69bb5SSascha Wildner error_r(r, "Agent signature failed for %s", filename);
516664f4763Szrj goto done;
517664f4763Szrj }
518664f4763Szrj if ((r = sshkey_verify(key, sig, slen, data, sizeof(data),
519*ba1276acSMatthew Dillon alg, 0, NULL)) != 0) {
52050a69bb5SSascha Wildner error_r(r, "Signature verification failed for %s", filename);
521664f4763Szrj goto done;
522664f4763Szrj }
523664f4763Szrj /* success */
524664f4763Szrj ret = 0;
525664f4763Szrj done:
526664f4763Szrj free(sig);
527664f4763Szrj sshkey_free(key);
528664f4763Szrj return ret;
529664f4763Szrj }
530664f4763Szrj
531664f4763Szrj static int
list_identities(int agent_fd,int do_fp)532e9778795SPeter Avalos list_identities(int agent_fd, int do_fp)
53318de8d7fSPeter Avalos {
534e9778795SPeter Avalos char *fp;
535ce74bacaSMatthew Dillon int r;
536e9778795SPeter Avalos struct ssh_identitylist *idlist;
537664f4763Szrj u_int32_t left;
538e9778795SPeter Avalos size_t i;
53918de8d7fSPeter Avalos
540ce74bacaSMatthew Dillon if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) {
541e9778795SPeter Avalos if (r != SSH_ERR_AGENT_NO_IDENTITIES)
542ce74bacaSMatthew Dillon fprintf(stderr, "error fetching identities: %s\n",
543ce74bacaSMatthew Dillon ssh_err(r));
544ce74bacaSMatthew Dillon else
545ce74bacaSMatthew Dillon printf("The agent has no identities.\n");
546ce74bacaSMatthew Dillon return -1;
547e9778795SPeter Avalos }
548e9778795SPeter Avalos for (i = 0; i < idlist->nkeys; i++) {
54918de8d7fSPeter Avalos if (do_fp) {
550e9778795SPeter Avalos fp = sshkey_fingerprint(idlist->keys[i],
551e9778795SPeter Avalos fingerprint_hash, SSH_FP_DEFAULT);
552ce74bacaSMatthew Dillon printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]),
553ce74bacaSMatthew Dillon fp == NULL ? "(null)" : fp, idlist->comments[i],
554e9778795SPeter Avalos sshkey_type(idlist->keys[i]));
55536e94dc5SPeter Avalos free(fp);
55618de8d7fSPeter Avalos } else {
557ce74bacaSMatthew Dillon if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) {
558e9778795SPeter Avalos fprintf(stderr, "sshkey_write: %s\n",
559e9778795SPeter Avalos ssh_err(r));
560e9778795SPeter Avalos continue;
56118de8d7fSPeter Avalos }
562664f4763Szrj fprintf(stdout, " %s", idlist->comments[i]);
563664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]);
564664f4763Szrj if (left > 0)
565664f4763Szrj fprintf(stdout,
566664f4763Szrj " [signatures left %d]", left);
567664f4763Szrj fprintf(stdout, "\n");
56818de8d7fSPeter Avalos }
56918de8d7fSPeter Avalos }
570e9778795SPeter Avalos ssh_free_identitylist(idlist);
57118de8d7fSPeter Avalos return 0;
57218de8d7fSPeter Avalos }
57318de8d7fSPeter Avalos
57418de8d7fSPeter Avalos static int
lock_agent(int agent_fd,int lock)575e9778795SPeter Avalos lock_agent(int agent_fd, int lock)
57618de8d7fSPeter Avalos {
57718de8d7fSPeter Avalos char prompt[100], *p1, *p2;
578e9778795SPeter Avalos int r, passok = 1, ret = -1;
57918de8d7fSPeter Avalos
58018de8d7fSPeter Avalos strlcpy(prompt, "Enter lock password: ", sizeof(prompt));
58118de8d7fSPeter Avalos p1 = read_passphrase(prompt, RP_ALLOW_STDIN);
58218de8d7fSPeter Avalos if (lock) {
58318de8d7fSPeter Avalos strlcpy(prompt, "Again: ", sizeof prompt);
58418de8d7fSPeter Avalos p2 = read_passphrase(prompt, RP_ALLOW_STDIN);
58518de8d7fSPeter Avalos if (strcmp(p1, p2) != 0) {
58618de8d7fSPeter Avalos fprintf(stderr, "Passwords do not match.\n");
58718de8d7fSPeter Avalos passok = 0;
58818de8d7fSPeter Avalos }
5890cbfa66cSDaniel Fojt freezero(p2, strlen(p2));
59018de8d7fSPeter Avalos }
591e9778795SPeter Avalos if (passok) {
592e9778795SPeter Avalos if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) {
59318de8d7fSPeter Avalos fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un");
59418de8d7fSPeter Avalos ret = 0;
595e9778795SPeter Avalos } else {
596e9778795SPeter Avalos fprintf(stderr, "Failed to %slock agent: %s\n",
597e9778795SPeter Avalos lock ? "" : "un", ssh_err(r));
598e9778795SPeter Avalos }
599e9778795SPeter Avalos }
6000cbfa66cSDaniel Fojt freezero(p1, strlen(p1));
60118de8d7fSPeter Avalos return (ret);
60218de8d7fSPeter Avalos }
60318de8d7fSPeter Avalos
60418de8d7fSPeter Avalos static int
load_resident_keys(int agent_fd,const char * skprovider,int qflag,struct dest_constraint ** dest_constraints,size_t ndest_constraints)605ee116499SAntonio Huete Jimenez load_resident_keys(int agent_fd, const char *skprovider, int qflag,
606ee116499SAntonio Huete Jimenez struct dest_constraint **dest_constraints, size_t ndest_constraints)
6070cbfa66cSDaniel Fojt {
608ee116499SAntonio Huete Jimenez struct sshsk_resident_key **srks;
609ee116499SAntonio Huete Jimenez size_t nsrks, i;
610ee116499SAntonio Huete Jimenez struct sshkey *key;
6110cbfa66cSDaniel Fojt int r, ok = 0;
6120cbfa66cSDaniel Fojt char *fp;
6130cbfa66cSDaniel Fojt
6140cbfa66cSDaniel Fojt pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN);
615ee116499SAntonio Huete Jimenez if ((r = sshsk_load_resident(skprovider, NULL, pass, 0,
616ee116499SAntonio Huete Jimenez &srks, &nsrks)) != 0) {
61750a69bb5SSascha Wildner error_r(r, "Unable to load resident keys");
6180cbfa66cSDaniel Fojt return r;
6190cbfa66cSDaniel Fojt }
620ee116499SAntonio Huete Jimenez for (i = 0; i < nsrks; i++) {
621ee116499SAntonio Huete Jimenez key = srks[i]->key;
622ee116499SAntonio Huete Jimenez if ((fp = sshkey_fingerprint(key,
6230cbfa66cSDaniel Fojt fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
62450a69bb5SSascha Wildner fatal_f("sshkey_fingerprint failed");
625ee116499SAntonio Huete Jimenez if ((r = ssh_add_identity_constrained(agent_fd, key, "",
626ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider,
627ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) != 0) {
6280cbfa66cSDaniel Fojt error("Unable to add key %s %s",
629ee116499SAntonio Huete Jimenez sshkey_type(key), fp);
6300cbfa66cSDaniel Fojt free(fp);
6310cbfa66cSDaniel Fojt ok = r;
6320cbfa66cSDaniel Fojt continue;
6330cbfa66cSDaniel Fojt }
6340cbfa66cSDaniel Fojt if (ok == 0)
6350cbfa66cSDaniel Fojt ok = 1;
6360cbfa66cSDaniel Fojt if (!qflag) {
6370cbfa66cSDaniel Fojt fprintf(stderr, "Resident identity added: %s %s\n",
638ee116499SAntonio Huete Jimenez sshkey_type(key), fp);
6390cbfa66cSDaniel Fojt if (lifetime != 0) {
6400cbfa66cSDaniel Fojt fprintf(stderr,
64150a69bb5SSascha Wildner "Lifetime set to %d seconds\n", lifetime);
6420cbfa66cSDaniel Fojt }
6430cbfa66cSDaniel Fojt if (confirm != 0) {
6440cbfa66cSDaniel Fojt fprintf(stderr, "The user must confirm "
6450cbfa66cSDaniel Fojt "each use of the key\n");
6460cbfa66cSDaniel Fojt }
6470cbfa66cSDaniel Fojt }
6480cbfa66cSDaniel Fojt free(fp);
6490cbfa66cSDaniel Fojt }
650ee116499SAntonio Huete Jimenez sshsk_free_resident_keys(srks, nsrks);
651ee116499SAntonio Huete Jimenez if (nsrks == 0)
6520cbfa66cSDaniel Fojt return SSH_ERR_KEY_NOT_FOUND;
6530cbfa66cSDaniel Fojt return ok == 1 ? 0 : ok;
6540cbfa66cSDaniel Fojt }
6550cbfa66cSDaniel Fojt
6560cbfa66cSDaniel Fojt 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)657*ba1276acSMatthew Dillon do_file(int agent_fd, int deleting, int key_only, int cert_only,
658*ba1276acSMatthew Dillon char *file, int qflag, const char *skprovider,
659*ba1276acSMatthew Dillon struct dest_constraint **dest_constraints, size_t ndest_constraints)
66018de8d7fSPeter Avalos {
66118de8d7fSPeter Avalos if (deleting) {
662*ba1276acSMatthew Dillon if (delete_file(agent_fd, file, key_only,
663*ba1276acSMatthew Dillon cert_only, qflag) == -1)
66418de8d7fSPeter Avalos return -1;
66518de8d7fSPeter Avalos } else {
666*ba1276acSMatthew Dillon if (add_file(agent_fd, file, key_only, cert_only, qflag,
667*ba1276acSMatthew Dillon skprovider, dest_constraints, ndest_constraints) == -1)
66818de8d7fSPeter Avalos return -1;
66918de8d7fSPeter Avalos }
67018de8d7fSPeter Avalos return 0;
67118de8d7fSPeter Avalos }
67218de8d7fSPeter Avalos
673ee116499SAntonio Huete Jimenez /* Append string 's' to a NULL-terminated array of strings */
674ee116499SAntonio Huete Jimenez static void
stringlist_append(char *** listp,const char * s)675ee116499SAntonio Huete Jimenez stringlist_append(char ***listp, const char *s)
676ee116499SAntonio Huete Jimenez {
677ee116499SAntonio Huete Jimenez size_t i = 0;
678ee116499SAntonio Huete Jimenez
679ee116499SAntonio Huete Jimenez if (*listp == NULL)
680ee116499SAntonio Huete Jimenez *listp = xcalloc(2, sizeof(**listp));
681ee116499SAntonio Huete Jimenez else {
682ee116499SAntonio Huete Jimenez for (i = 0; (*listp)[i] != NULL; i++)
683ee116499SAntonio Huete Jimenez ; /* count */
684ee116499SAntonio Huete Jimenez *listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp));
685ee116499SAntonio Huete Jimenez }
686ee116499SAntonio Huete Jimenez (*listp)[i] = xstrdup(s);
687ee116499SAntonio Huete Jimenez }
688ee116499SAntonio Huete Jimenez
689ee116499SAntonio Huete Jimenez static void
parse_dest_constraint_hop(const char * s,struct dest_constraint_hop * dch,char ** hostkey_files)690ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch,
691ee116499SAntonio Huete Jimenez char **hostkey_files)
692ee116499SAntonio Huete Jimenez {
693ee116499SAntonio Huete Jimenez char *user = NULL, *host, *os, *path;
694ee116499SAntonio Huete Jimenez size_t i;
695ee116499SAntonio Huete Jimenez struct hostkeys *hostkeys;
696ee116499SAntonio Huete Jimenez const struct hostkey_entry *hke;
697ee116499SAntonio Huete Jimenez int r, want_ca;
698ee116499SAntonio Huete Jimenez
699ee116499SAntonio Huete Jimenez memset(dch, '\0', sizeof(*dch));
700ee116499SAntonio Huete Jimenez os = xstrdup(s);
701ee116499SAntonio Huete Jimenez if ((host = strchr(os, '@')) == NULL)
702ee116499SAntonio Huete Jimenez host = os;
703ee116499SAntonio Huete Jimenez else {
704ee116499SAntonio Huete Jimenez *host++ = '\0';
705ee116499SAntonio Huete Jimenez user = os;
706ee116499SAntonio Huete Jimenez }
707ee116499SAntonio Huete Jimenez cleanhostname(host);
708ee116499SAntonio Huete Jimenez /* Trivial case: username@ (all hosts) */
709ee116499SAntonio Huete Jimenez if (*host == '\0') {
710ee116499SAntonio Huete Jimenez if (user == NULL) {
711ee116499SAntonio Huete Jimenez fatal("Invalid key destination constraint \"%s\": "
712ee116499SAntonio Huete Jimenez "does not specify user or host", s);
713ee116499SAntonio Huete Jimenez }
714ee116499SAntonio Huete Jimenez dch->user = xstrdup(user);
715ee116499SAntonio Huete Jimenez /* other fields left blank */
716ee116499SAntonio Huete Jimenez free(os);
717ee116499SAntonio Huete Jimenez return;
718ee116499SAntonio Huete Jimenez }
719ee116499SAntonio Huete Jimenez if (hostkey_files == NULL)
720ee116499SAntonio Huete Jimenez fatal_f("no hostkey files");
721ee116499SAntonio Huete Jimenez /* Otherwise we need to look up the keys for this hostname */
722ee116499SAntonio Huete Jimenez hostkeys = init_hostkeys();
723ee116499SAntonio Huete Jimenez for (i = 0; hostkey_files[i]; i++) {
724ee116499SAntonio Huete Jimenez path = tilde_expand_filename(hostkey_files[i], getuid());
725ee116499SAntonio Huete Jimenez debug2_f("looking up host keys for \"%s\" in %s", host, path);
726ee116499SAntonio Huete Jimenez load_hostkeys(hostkeys, host, path, 0);
727ee116499SAntonio Huete Jimenez free(path);
728ee116499SAntonio Huete Jimenez }
729ee116499SAntonio Huete Jimenez dch->user = user == NULL ? NULL : xstrdup(user);
730ee116499SAntonio Huete Jimenez dch->hostname = xstrdup(host);
731ee116499SAntonio Huete Jimenez for (i = 0; i < hostkeys->num_entries; i++) {
732ee116499SAntonio Huete Jimenez hke = hostkeys->entries + i;
733ee116499SAntonio Huete Jimenez want_ca = hke->marker == MRK_CA;
734ee116499SAntonio Huete Jimenez if (hke->marker != MRK_NONE && !want_ca)
735ee116499SAntonio Huete Jimenez continue;
736ee116499SAntonio Huete Jimenez debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u",
737ee116499SAntonio Huete Jimenez user == NULL ? "": user, user == NULL ? "" : "@",
738ee116499SAntonio Huete Jimenez host, sshkey_type(hke->key), want_ca ? "CA " : "",
739ee116499SAntonio Huete Jimenez hke->file, hke->line, dch->nkeys);
740ee116499SAntonio Huete Jimenez dch->keys = xrecallocarray(dch->keys, dch->nkeys,
741ee116499SAntonio Huete Jimenez dch->nkeys + 1, sizeof(*dch->keys));
742ee116499SAntonio Huete Jimenez dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys,
743ee116499SAntonio Huete Jimenez dch->nkeys + 1, sizeof(*dch->key_is_ca));
744ee116499SAntonio Huete Jimenez if ((r = sshkey_from_private(hke->key,
745ee116499SAntonio Huete Jimenez &(dch->keys[dch->nkeys]))) != 0)
746ee116499SAntonio Huete Jimenez fatal_fr(r, "sshkey_from_private");
747ee116499SAntonio Huete Jimenez dch->key_is_ca[dch->nkeys] = want_ca;
748ee116499SAntonio Huete Jimenez dch->nkeys++;
749ee116499SAntonio Huete Jimenez }
750ee116499SAntonio Huete Jimenez if (dch->nkeys == 0)
751ee116499SAntonio Huete Jimenez fatal("No host keys found for destination \"%s\"", host);
752ee116499SAntonio Huete Jimenez free_hostkeys(hostkeys);
753ee116499SAntonio Huete Jimenez free(os);
754ee116499SAntonio Huete Jimenez return;
755ee116499SAntonio Huete Jimenez }
756ee116499SAntonio Huete Jimenez
757ee116499SAntonio Huete Jimenez static void
parse_dest_constraint(const char * s,struct dest_constraint *** dcp,size_t * ndcp,char ** hostkey_files)758ee116499SAntonio Huete Jimenez parse_dest_constraint(const char *s, struct dest_constraint ***dcp,
759ee116499SAntonio Huete Jimenez size_t *ndcp, char **hostkey_files)
760ee116499SAntonio Huete Jimenez {
761ee116499SAntonio Huete Jimenez struct dest_constraint *dc;
762ee116499SAntonio Huete Jimenez char *os, *cp;
763ee116499SAntonio Huete Jimenez
764ee116499SAntonio Huete Jimenez dc = xcalloc(1, sizeof(*dc));
765ee116499SAntonio Huete Jimenez os = xstrdup(s);
766ee116499SAntonio Huete Jimenez if ((cp = strchr(os, '>')) == NULL) {
767ee116499SAntonio Huete Jimenez /* initial hop; no 'from' hop specified */
768ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(os, &dc->to, hostkey_files);
769ee116499SAntonio Huete Jimenez } else {
770ee116499SAntonio Huete Jimenez /* two hops specified */
771ee116499SAntonio Huete Jimenez *(cp++) = '\0';
772ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(os, &dc->from, hostkey_files);
773ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(cp, &dc->to, hostkey_files);
774ee116499SAntonio Huete Jimenez if (dc->from.user != NULL) {
775ee116499SAntonio Huete Jimenez fatal("Invalid key constraint %s: cannot specify "
776ee116499SAntonio Huete Jimenez "user on 'from' host", os);
777ee116499SAntonio Huete Jimenez }
778ee116499SAntonio Huete Jimenez }
779ee116499SAntonio Huete Jimenez /* XXX eliminate or error on duplicates */
780ee116499SAntonio Huete Jimenez debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp,
781ee116499SAntonio Huete Jimenez dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "",
782ee116499SAntonio Huete Jimenez dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys,
783ee116499SAntonio Huete Jimenez dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "",
784ee116499SAntonio Huete Jimenez dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys);
785ee116499SAntonio Huete Jimenez *dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp));
786ee116499SAntonio Huete Jimenez (*dcp)[(*ndcp)++] = dc;
787ee116499SAntonio Huete Jimenez free(os);
788ee116499SAntonio Huete Jimenez }
789ee116499SAntonio Huete Jimenez
790ee116499SAntonio Huete Jimenez
79118de8d7fSPeter Avalos static void
usage(void)79218de8d7fSPeter Avalos usage(void)
79318de8d7fSPeter Avalos {
7940cbfa66cSDaniel Fojt fprintf(stderr,
795*ba1276acSMatthew Dillon "usage: ssh-add [-CcDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n"
796ee116499SAntonio Huete Jimenez " [-h destination_constraint] [-S provider] [-t life]\n"
7970cbfa66cSDaniel Fojt #ifdef WITH_XMSS
7980cbfa66cSDaniel Fojt " [-M maxsign] [-m minleft]\n"
7990cbfa66cSDaniel Fojt #endif
8000cbfa66cSDaniel Fojt " [file ...]\n"
801*ba1276acSMatthew Dillon " ssh-add -s pkcs11 [-Cv] [certificate ...]\n"
8020cbfa66cSDaniel Fojt " ssh-add -e pkcs11\n"
8030cbfa66cSDaniel Fojt " ssh-add -T pubkey ...\n"
8040cbfa66cSDaniel Fojt );
80518de8d7fSPeter Avalos }
80618de8d7fSPeter Avalos
80718de8d7fSPeter Avalos int
main(int argc,char ** argv)80818de8d7fSPeter Avalos main(int argc, char **argv)
80918de8d7fSPeter Avalos {
81018de8d7fSPeter Avalos extern char *optarg;
81118de8d7fSPeter Avalos extern int optind;
812e9778795SPeter Avalos int agent_fd;
8130cbfa66cSDaniel Fojt char *pkcs11provider = NULL, *skprovider = NULL;
814ee116499SAntonio Huete Jimenez char **dest_constraint_strings = NULL, **hostkey_files = NULL;
815*ba1276acSMatthew Dillon int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0;
816*ba1276acSMatthew Dillon int do_download = 0, xflag = 0, lflag = 0, Dflag = 0;
817*ba1276acSMatthew Dillon int qflag = 0, Tflag = 0;
818664f4763Szrj SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
819664f4763Szrj LogLevel log_level = SYSLOG_LEVEL_INFO;
820*ba1276acSMatthew Dillon struct sshkey *k, **certs = NULL;
821ee116499SAntonio Huete Jimenez struct dest_constraint **dest_constraints = NULL;
822*ba1276acSMatthew Dillon size_t ndest_constraints = 0, ncerts = 0;
82318de8d7fSPeter Avalos
82418de8d7fSPeter Avalos /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
82518de8d7fSPeter Avalos sanitise_stdfd();
82618de8d7fSPeter Avalos
82718de8d7fSPeter Avalos __progname = ssh_get_progname(argv[0]);
82818de8d7fSPeter Avalos seed_rng();
82918de8d7fSPeter Avalos
830664f4763Szrj log_init(__progname, log_level, log_facility, 1);
83118de8d7fSPeter Avalos
832e9778795SPeter Avalos setvbuf(stdout, NULL, _IOLBF, 0);
83336e94dc5SPeter Avalos
834e9778795SPeter Avalos /* First, get a connection to the authentication agent. */
835e9778795SPeter Avalos switch (r = ssh_get_authentication_socket(&agent_fd)) {
836e9778795SPeter Avalos case 0:
837e9778795SPeter Avalos break;
838e9778795SPeter Avalos case SSH_ERR_AGENT_NOT_PRESENT:
839e9778795SPeter Avalos fprintf(stderr, "Could not open a connection to your "
840e9778795SPeter Avalos "authentication agent.\n");
841e9778795SPeter Avalos exit(2);
842e9778795SPeter Avalos default:
843e9778795SPeter Avalos fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r));
84418de8d7fSPeter Avalos exit(2);
84518de8d7fSPeter Avalos }
846e9778795SPeter Avalos
8470cbfa66cSDaniel Fojt skprovider = getenv("SSH_SK_PROVIDER");
8480cbfa66cSDaniel Fojt
849*ba1276acSMatthew Dillon while ((ch = getopt(argc, argv, "vkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
85018de8d7fSPeter Avalos switch (ch) {
851664f4763Szrj case 'v':
852664f4763Szrj if (log_level == SYSLOG_LEVEL_INFO)
853664f4763Szrj log_level = SYSLOG_LEVEL_DEBUG1;
854664f4763Szrj else if (log_level < SYSLOG_LEVEL_DEBUG3)
855664f4763Szrj log_level++;
856664f4763Szrj break;
857e9778795SPeter Avalos case 'E':
858e9778795SPeter Avalos fingerprint_hash = ssh_digest_alg_by_name(optarg);
859e9778795SPeter Avalos if (fingerprint_hash == -1)
860e9778795SPeter Avalos fatal("Invalid hash algorithm \"%s\"", optarg);
861e9778795SPeter Avalos break;
862ee116499SAntonio Huete Jimenez case 'H':
863ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, optarg);
864ee116499SAntonio Huete Jimenez break;
865ee116499SAntonio Huete Jimenez case 'h':
866ee116499SAntonio Huete Jimenez stringlist_append(&dest_constraint_strings, optarg);
867ee116499SAntonio Huete Jimenez break;
86899e85e0dSPeter Avalos case 'k':
86999e85e0dSPeter Avalos key_only = 1;
87099e85e0dSPeter Avalos break;
871*ba1276acSMatthew Dillon case 'C':
872*ba1276acSMatthew Dillon cert_only = 1;
873*ba1276acSMatthew Dillon break;
8740cbfa66cSDaniel Fojt case 'K':
8750cbfa66cSDaniel Fojt do_download = 1;
8760cbfa66cSDaniel Fojt break;
87718de8d7fSPeter Avalos case 'l':
87818de8d7fSPeter Avalos case 'L':
879e9778795SPeter Avalos if (lflag != 0)
880e9778795SPeter Avalos fatal("-%c flag already specified", lflag);
881e9778795SPeter Avalos lflag = ch;
882e9778795SPeter Avalos break;
88318de8d7fSPeter Avalos case 'x':
88418de8d7fSPeter Avalos case 'X':
885e9778795SPeter Avalos if (xflag != 0)
886e9778795SPeter Avalos fatal("-%c flag already specified", xflag);
887e9778795SPeter Avalos xflag = ch;
888e9778795SPeter Avalos break;
88918de8d7fSPeter Avalos case 'c':
89018de8d7fSPeter Avalos confirm = 1;
89118de8d7fSPeter Avalos break;
892664f4763Szrj case 'm':
893*ba1276acSMatthew Dillon minleft = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
894664f4763Szrj if (minleft == 0) {
895664f4763Szrj usage();
896664f4763Szrj ret = 1;
897664f4763Szrj goto done;
898664f4763Szrj }
899664f4763Szrj break;
900664f4763Szrj case 'M':
901*ba1276acSMatthew Dillon maxsign = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
902664f4763Szrj if (maxsign == 0) {
903664f4763Szrj usage();
904664f4763Szrj ret = 1;
905664f4763Szrj goto done;
906664f4763Szrj }
907664f4763Szrj break;
90818de8d7fSPeter Avalos case 'd':
90918de8d7fSPeter Avalos deleting = 1;
91018de8d7fSPeter Avalos break;
91118de8d7fSPeter Avalos case 'D':
912e9778795SPeter Avalos Dflag = 1;
913e9778795SPeter Avalos break;
91418de8d7fSPeter Avalos case 's':
915856ea928SPeter Avalos pkcs11provider = optarg;
91618de8d7fSPeter Avalos break;
9170cbfa66cSDaniel Fojt case 'S':
9180cbfa66cSDaniel Fojt skprovider = optarg;
9190cbfa66cSDaniel Fojt break;
92018de8d7fSPeter Avalos case 'e':
92118de8d7fSPeter Avalos deleting = 1;
922856ea928SPeter Avalos pkcs11provider = optarg;
92318de8d7fSPeter Avalos break;
92418de8d7fSPeter Avalos case 't':
9250cbfa66cSDaniel Fojt if ((lifetime = convtime(optarg)) == -1 ||
9260cbfa66cSDaniel Fojt lifetime < 0 || (u_long)lifetime > UINT32_MAX) {
92718de8d7fSPeter Avalos fprintf(stderr, "Invalid lifetime\n");
92818de8d7fSPeter Avalos ret = 1;
92918de8d7fSPeter Avalos goto done;
93018de8d7fSPeter Avalos }
93118de8d7fSPeter Avalos break;
932ce74bacaSMatthew Dillon case 'q':
933ce74bacaSMatthew Dillon qflag = 1;
934ce74bacaSMatthew Dillon break;
935664f4763Szrj case 'T':
936664f4763Szrj Tflag = 1;
937664f4763Szrj break;
93818de8d7fSPeter Avalos default:
93918de8d7fSPeter Avalos usage();
94018de8d7fSPeter Avalos ret = 1;
94118de8d7fSPeter Avalos goto done;
94218de8d7fSPeter Avalos }
94318de8d7fSPeter Avalos }
944664f4763Szrj log_init(__progname, log_level, log_facility, 1);
945e9778795SPeter Avalos
946e9778795SPeter Avalos if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1)
947e9778795SPeter Avalos fatal("Invalid combination of actions");
948e9778795SPeter Avalos else if (xflag) {
949e9778795SPeter Avalos if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1)
950e9778795SPeter Avalos ret = 1;
951e9778795SPeter Avalos goto done;
952e9778795SPeter Avalos } else if (lflag) {
953e9778795SPeter Avalos if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1)
954e9778795SPeter Avalos ret = 1;
955e9778795SPeter Avalos goto done;
956e9778795SPeter Avalos } else if (Dflag) {
957664f4763Szrj if (delete_all(agent_fd, qflag) == -1)
958e9778795SPeter Avalos ret = 1;
959e9778795SPeter Avalos goto done;
960e9778795SPeter Avalos }
961e9778795SPeter Avalos
9620cbfa66cSDaniel Fojt #ifdef ENABLE_SK_INTERNAL
9630cbfa66cSDaniel Fojt if (skprovider == NULL)
9640cbfa66cSDaniel Fojt skprovider = "internal";
9650cbfa66cSDaniel Fojt #endif
9660cbfa66cSDaniel Fojt
967ee116499SAntonio Huete Jimenez if (hostkey_files == NULL) {
968ee116499SAntonio Huete Jimenez /* use defaults from readconf.c */
969ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE);
970ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2);
971ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE);
972ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2);
973ee116499SAntonio Huete Jimenez }
974ee116499SAntonio Huete Jimenez if (dest_constraint_strings != NULL) {
975ee116499SAntonio Huete Jimenez for (i = 0; dest_constraint_strings[i] != NULL; i++) {
976ee116499SAntonio Huete Jimenez parse_dest_constraint(dest_constraint_strings[i],
977ee116499SAntonio Huete Jimenez &dest_constraints, &ndest_constraints, hostkey_files);
978ee116499SAntonio Huete Jimenez }
979ee116499SAntonio Huete Jimenez }
980ee116499SAntonio Huete Jimenez
98118de8d7fSPeter Avalos argc -= optind;
98218de8d7fSPeter Avalos argv += optind;
983664f4763Szrj if (Tflag) {
984664f4763Szrj if (argc <= 0)
985664f4763Szrj fatal("no keys to test");
986664f4763Szrj for (r = i = 0; i < argc; i++)
987664f4763Szrj r |= test_key(agent_fd, argv[i]);
988664f4763Szrj ret = r == 0 ? 0 : 1;
989664f4763Szrj goto done;
990664f4763Szrj }
991856ea928SPeter Avalos if (pkcs11provider != NULL) {
992*ba1276acSMatthew Dillon for (i = 0; i < argc; i++) {
993*ba1276acSMatthew Dillon if ((r = sshkey_load_public(argv[i], &k, NULL)) != 0)
994*ba1276acSMatthew Dillon fatal_fr(r, "load certificate %s", argv[i]);
995*ba1276acSMatthew Dillon certs = xrecallocarray(certs, ncerts, ncerts + 1,
996*ba1276acSMatthew Dillon sizeof(*certs));
997*ba1276acSMatthew Dillon debug2("%s: %s", argv[i], sshkey_ssh_name(k));
998*ba1276acSMatthew Dillon certs[ncerts++] = k;
999*ba1276acSMatthew Dillon }
1000*ba1276acSMatthew Dillon debug2_f("loaded %zu certificates", ncerts);
1001664f4763Szrj if (update_card(agent_fd, !deleting, pkcs11provider,
1002*ba1276acSMatthew Dillon qflag, key_only, cert_only,
1003*ba1276acSMatthew Dillon dest_constraints, ndest_constraints,
1004*ba1276acSMatthew Dillon certs, ncerts) == -1)
100518de8d7fSPeter Avalos ret = 1;
100618de8d7fSPeter Avalos goto done;
100718de8d7fSPeter Avalos }
10080cbfa66cSDaniel Fojt if (do_download) {
10090cbfa66cSDaniel Fojt if (skprovider == NULL)
10100cbfa66cSDaniel Fojt fatal("Cannot download keys without provider");
1011ee116499SAntonio Huete Jimenez if (load_resident_keys(agent_fd, skprovider, qflag,
1012ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) != 0)
10130cbfa66cSDaniel Fojt ret = 1;
10140cbfa66cSDaniel Fojt goto done;
10150cbfa66cSDaniel Fojt }
101618de8d7fSPeter Avalos if (argc == 0) {
1017e9778795SPeter Avalos char buf[PATH_MAX];
101818de8d7fSPeter Avalos struct passwd *pw;
101918de8d7fSPeter Avalos struct stat st;
102018de8d7fSPeter Avalos int count = 0;
102118de8d7fSPeter Avalos
102218de8d7fSPeter Avalos if ((pw = getpwuid(getuid())) == NULL) {
102318de8d7fSPeter Avalos fprintf(stderr, "No user found with uid %u\n",
102418de8d7fSPeter Avalos (u_int)getuid());
102518de8d7fSPeter Avalos ret = 1;
102618de8d7fSPeter Avalos goto done;
102718de8d7fSPeter Avalos }
102818de8d7fSPeter Avalos
102918de8d7fSPeter Avalos for (i = 0; default_files[i]; i++) {
103018de8d7fSPeter Avalos snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir,
103118de8d7fSPeter Avalos default_files[i]);
10320cbfa66cSDaniel Fojt if (stat(buf, &st) == -1)
103318de8d7fSPeter Avalos continue;
1034*ba1276acSMatthew Dillon if (do_file(agent_fd, deleting, key_only, cert_only,
1035*ba1276acSMatthew Dillon buf, qflag, skprovider,
1036ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) == -1)
103718de8d7fSPeter Avalos ret = 1;
103818de8d7fSPeter Avalos else
103918de8d7fSPeter Avalos count++;
104018de8d7fSPeter Avalos }
104118de8d7fSPeter Avalos if (count == 0)
104218de8d7fSPeter Avalos ret = 1;
104318de8d7fSPeter Avalos } else {
104418de8d7fSPeter Avalos for (i = 0; i < argc; i++) {
1045*ba1276acSMatthew Dillon if (do_file(agent_fd, deleting, key_only, cert_only,
1046ee116499SAntonio Huete Jimenez argv[i], qflag, skprovider,
1047ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) == -1)
104818de8d7fSPeter Avalos ret = 1;
104918de8d7fSPeter Avalos }
105018de8d7fSPeter Avalos }
105118de8d7fSPeter Avalos done:
10520cbfa66cSDaniel Fojt clear_pass();
1053e9778795SPeter Avalos ssh_close_authentication_socket(agent_fd);
105418de8d7fSPeter Avalos return ret;
105518de8d7fSPeter Avalos }
1056