xref: /netbsd-src/crypto/external/bsd/openssh/dist/ssh-add.c (revision 9469f4f13c84743995b7d51c506f9c9849ba30de)
1*9469f4f1Schristos /*	$NetBSD: ssh-add.c,v 1.32 2024/09/24 21:32:19 christos Exp $	*/
2*9469f4f1Schristos /* $OpenBSD: ssh-add.c,v 1.173 2024/09/06 02:30:44 djm Exp $ */
3a629fefcSchristos 
4ca32bd8dSchristos /*
5ca32bd8dSchristos  * Author: Tatu Ylonen <ylo@cs.hut.fi>
6ca32bd8dSchristos  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
7ca32bd8dSchristos  *                    All rights reserved
8ca32bd8dSchristos  * Adds an identity to the authentication server, or removes an identity.
9ca32bd8dSchristos  *
10ca32bd8dSchristos  * As far as I am concerned, the code I have written for this software
11ca32bd8dSchristos  * can be used freely for any purpose.  Any derived versions of this
12ca32bd8dSchristos  * software must be clearly marked as such, and if the derived work is
13ca32bd8dSchristos  * incompatible with the protocol description in the RFC file, it must be
14ca32bd8dSchristos  * called by a name other than "ssh" or "Secure Shell".
15ca32bd8dSchristos  *
16ca32bd8dSchristos  * SSH2 implementation,
17ca32bd8dSchristos  * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
18ca32bd8dSchristos  *
19ca32bd8dSchristos  * Redistribution and use in source and binary forms, with or without
20ca32bd8dSchristos  * modification, are permitted provided that the following conditions
21ca32bd8dSchristos  * are met:
22ca32bd8dSchristos  * 1. Redistributions of source code must retain the above copyright
23ca32bd8dSchristos  *    notice, this list of conditions and the following disclaimer.
24ca32bd8dSchristos  * 2. Redistributions in binary form must reproduce the above copyright
25ca32bd8dSchristos  *    notice, this list of conditions and the following disclaimer in the
26ca32bd8dSchristos  *    documentation and/or other materials provided with the distribution.
27ca32bd8dSchristos  *
28ca32bd8dSchristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
29ca32bd8dSchristos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30ca32bd8dSchristos  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
31ca32bd8dSchristos  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
32ca32bd8dSchristos  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33ca32bd8dSchristos  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34ca32bd8dSchristos  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35ca32bd8dSchristos  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36ca32bd8dSchristos  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
37ca32bd8dSchristos  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38ca32bd8dSchristos  */
39ca32bd8dSchristos 
40313c6c94Schristos #include "includes.h"
41*9469f4f1Schristos __RCSID("$NetBSD: ssh-add.c,v 1.32 2024/09/24 21:32:19 christos Exp $");
42ca32bd8dSchristos #include <sys/types.h>
43ca32bd8dSchristos #include <sys/stat.h>
44ca32bd8dSchristos 
45cd4ada6aSchristos #ifdef WITH_OPENSSL
46ca32bd8dSchristos #include <openssl/evp.h>
47cd4ada6aSchristos #endif
48ca32bd8dSchristos 
49e4d43b82Schristos #include <errno.h>
50ca32bd8dSchristos #include <fcntl.h>
51ca32bd8dSchristos #include <pwd.h>
52ca32bd8dSchristos #include <stdio.h>
53ca32bd8dSchristos #include <stdlib.h>
54ca32bd8dSchristos #include <string.h>
55ed75d7a8Schristos #include <stdarg.h>
56ca32bd8dSchristos #include <unistd.h>
57e4d43b82Schristos #include <limits.h>
58ca32bd8dSchristos 
59ca32bd8dSchristos #include "xmalloc.h"
60ca32bd8dSchristos #include "ssh.h"
61ca32bd8dSchristos #include "log.h"
62e4d43b82Schristos #include "sshkey.h"
63e4d43b82Schristos #include "sshbuf.h"
64ca32bd8dSchristos #include "authfd.h"
65ca32bd8dSchristos #include "authfile.h"
66ca32bd8dSchristos #include "pathnames.h"
67ca32bd8dSchristos #include "misc.h"
688a4530f9Schristos #include "ssherr.h"
69e4d43b82Schristos #include "digest.h"
70ed75d7a8Schristos #include "ssh-sk.h"
712d3b0f52Schristos #include "sk-api.h"
72a03ec00cSchristos #include "hostfile.h"
73ca32bd8dSchristos 
74ca32bd8dSchristos /* argv0 */
75ca32bd8dSchristos extern char *__progname;
76ca32bd8dSchristos 
77ca32bd8dSchristos /* Default files to add */
78185c8f97Schristos static const char *default_files[] = {
79ca32bd8dSchristos 	_PATH_SSH_CLIENT_ID_RSA,
80185c8f97Schristos 	_PATH_SSH_CLIENT_ID_ECDSA,
81ed75d7a8Schristos 	_PATH_SSH_CLIENT_ID_ECDSA_SK,
828a4530f9Schristos 	_PATH_SSH_CLIENT_ID_ED25519,
83ed75d7a8Schristos 	_PATH_SSH_CLIENT_ID_ED25519_SK,
84ffae97bbSchristos 	_PATH_SSH_CLIENT_ID_XMSS,
85c5555919Schristos #ifdef WITH_DSA
86a03ec00cSchristos 	_PATH_SSH_CLIENT_ID_DSA,
87c5555919Schristos #endif
88ca32bd8dSchristos 	NULL
89ca32bd8dSchristos };
90ca32bd8dSchristos 
91e4d43b82Schristos static int fingerprint_hash = SSH_FP_HASH_DEFAULT;
92e4d43b82Schristos 
93ca32bd8dSchristos /* Default lifetime (0 == forever) */
9417418e98Schristos static int lifetime = 0;
95ca32bd8dSchristos 
96ca32bd8dSchristos /* User has to confirm key use */
97ca32bd8dSchristos static int confirm = 0;
98ca32bd8dSchristos 
99ffae97bbSchristos /* Maximum number of signatures (XMSS) */
100ffae97bbSchristos static u_int maxsign = 0;
101ffae97bbSchristos static u_int minleft = 0;
102ffae97bbSchristos 
10379976551Schristos /* we keep a cache of one passphrase */
104ca32bd8dSchristos static char *pass = NULL;
105ca32bd8dSchristos static void
106ca32bd8dSchristos clear_pass(void)
107ca32bd8dSchristos {
108ca32bd8dSchristos 	if (pass) {
1098db691beSchristos 		freezero(pass, strlen(pass));
110ca32bd8dSchristos 		pass = NULL;
111ca32bd8dSchristos 	}
112ca32bd8dSchristos }
113ca32bd8dSchristos 
114ca32bd8dSchristos static int
1152d3b0f52Schristos delete_one(int agent_fd, const struct sshkey *key, const char *comment,
1162d3b0f52Schristos     const char *path, int qflag)
1172d3b0f52Schristos {
1182d3b0f52Schristos 	int r;
1192d3b0f52Schristos 
1202d3b0f52Schristos 	if ((r = ssh_remove_identity(agent_fd, key)) != 0) {
1212d3b0f52Schristos 		fprintf(stderr, "Could not remove identity \"%s\": %s\n",
1222d3b0f52Schristos 		    path, ssh_err(r));
1232d3b0f52Schristos 		return r;
1242d3b0f52Schristos 	}
1252d3b0f52Schristos 	if (!qflag) {
1262d3b0f52Schristos 		fprintf(stderr, "Identity removed: %s %s (%s)\n", path,
127e160b4e8Schristos 		    sshkey_type(key), comment ? comment : "no comment");
1282d3b0f52Schristos 	}
1292d3b0f52Schristos 	return 0;
1302d3b0f52Schristos }
1312d3b0f52Schristos 
1322d3b0f52Schristos static int
133514b5d45Schristos delete_stdin(int agent_fd, int qflag, int key_only, int cert_only)
1342d3b0f52Schristos {
1352d3b0f52Schristos 	char *line = NULL, *cp;
1362d3b0f52Schristos 	size_t linesize = 0;
1372d3b0f52Schristos 	struct sshkey *key = NULL;
1382d3b0f52Schristos 	int lnum = 0, r, ret = -1;
1392d3b0f52Schristos 
1402d3b0f52Schristos 	while (getline(&line, &linesize, stdin) != -1) {
1412d3b0f52Schristos 		lnum++;
1422d3b0f52Schristos 		sshkey_free(key);
1432d3b0f52Schristos 		key = NULL;
1442d3b0f52Schristos 		line[strcspn(line, "\n")] = '\0';
1452d3b0f52Schristos 		cp = line + strspn(line, " \t");
1462d3b0f52Schristos 		if (*cp == '#' || *cp == '\0')
1472d3b0f52Schristos 			continue;
1482d3b0f52Schristos 		if ((key = sshkey_new(KEY_UNSPEC)) == NULL)
14917418e98Schristos 			fatal_f("sshkey_new");
1502d3b0f52Schristos 		if ((r = sshkey_read(key, &cp)) != 0) {
15117418e98Schristos 			error_r(r, "(stdin):%d: invalid key", lnum);
1522d3b0f52Schristos 			continue;
1532d3b0f52Schristos 		}
154514b5d45Schristos 		if ((!key_only && !cert_only) ||
155514b5d45Schristos 		    (key_only && !sshkey_is_cert(key)) ||
156514b5d45Schristos 		    (cert_only && sshkey_is_cert(key))) {
157514b5d45Schristos 			if (delete_one(agent_fd, key, cp,
158514b5d45Schristos 			    "(stdin)", qflag) == 0)
1592d3b0f52Schristos 				ret = 0;
1602d3b0f52Schristos 		}
161514b5d45Schristos 	}
1622d3b0f52Schristos 	sshkey_free(key);
1632d3b0f52Schristos 	free(line);
1642d3b0f52Schristos 	return ret;
1652d3b0f52Schristos }
1662d3b0f52Schristos 
1672d3b0f52Schristos static int
168514b5d45Schristos delete_file(int agent_fd, const char *filename, int key_only,
169514b5d45Schristos     int cert_only, int qflag)
170ca32bd8dSchristos {
171e4d43b82Schristos 	struct sshkey *public, *cert = NULL;
172ce11a51fSchristos 	char *certpath = NULL, *comment = NULL;
173e4d43b82Schristos 	int r, ret = -1;
174ca32bd8dSchristos 
1752d3b0f52Schristos 	if (strcmp(filename, "-") == 0)
176514b5d45Schristos 		return delete_stdin(agent_fd, qflag, key_only, cert_only);
1772d3b0f52Schristos 
178e4d43b82Schristos 	if ((r = sshkey_load_public(filename, &public,  &comment)) != 0) {
179e4d43b82Schristos 		printf("Bad key file %s: %s\n", filename, ssh_err(r));
180ca32bd8dSchristos 		return -1;
181ca32bd8dSchristos 	}
182514b5d45Schristos 	if ((!key_only && !cert_only) ||
183514b5d45Schristos 	    (key_only && !sshkey_is_cert(public)) ||
184514b5d45Schristos 	    (cert_only && sshkey_is_cert(public))) {
1852d3b0f52Schristos 		if (delete_one(agent_fd, public, comment, filename, qflag) == 0)
186ca32bd8dSchristos 			ret = 0;
187514b5d45Schristos 	}
188ca32bd8dSchristos 
189ce11a51fSchristos 	if (key_only)
190ce11a51fSchristos 		goto out;
191ce11a51fSchristos 
192ce11a51fSchristos 	/* Now try to delete the corresponding certificate too */
193ce11a51fSchristos 	free(comment);
194ce11a51fSchristos 	comment = NULL;
195ce11a51fSchristos 	xasprintf(&certpath, "%s-cert.pub", filename);
196e4d43b82Schristos 	if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) {
197e4d43b82Schristos 		if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
19817418e98Schristos 			error_r(r, "Failed to load certificate \"%s\"", certpath);
199ce11a51fSchristos 		goto out;
200e4d43b82Schristos 	}
201e4d43b82Schristos 
202e4d43b82Schristos 	if (!sshkey_equal_public(cert, public))
203ce11a51fSchristos 		fatal("Certificate %s does not match private key %s",
204ce11a51fSchristos 		    certpath, filename);
205ce11a51fSchristos 
2062d3b0f52Schristos 	if (delete_one(agent_fd, cert, comment, certpath, qflag) == 0)
207ce11a51fSchristos 		ret = 0;
208ce11a51fSchristos 
209ce11a51fSchristos  out:
210e4d43b82Schristos 	sshkey_free(cert);
211e4d43b82Schristos 	sshkey_free(public);
212ce11a51fSchristos 	free(certpath);
213ce11a51fSchristos 	free(comment);
214ca32bd8dSchristos 
215ca32bd8dSchristos 	return ret;
216ca32bd8dSchristos }
217ca32bd8dSchristos 
218ca32bd8dSchristos /* Send a request to remove all identities. */
219ca32bd8dSchristos static int
220aa36fcacSchristos delete_all(int agent_fd, int qflag)
221ca32bd8dSchristos {
222ca32bd8dSchristos 	int ret = -1;
223ca32bd8dSchristos 
2247a183406Schristos 	/*
2257a183406Schristos 	 * Since the agent might be forwarded, old or non-OpenSSH, when asked
2267a183406Schristos 	 * to remove all keys, attempt to remove both protocol v.1 and v.2
2277a183406Schristos 	 * keys.
2287a183406Schristos 	 */
2294054ffb0Schristos 	if (ssh_remove_all_identities(agent_fd, 2) == 0)
230ca32bd8dSchristos 		ret = 0;
2314054ffb0Schristos 	/* ignore error-code for ssh1 */
2324054ffb0Schristos 	ssh_remove_all_identities(agent_fd, 1);
233ca32bd8dSchristos 
234aa36fcacSchristos 	if (ret != 0)
235ca32bd8dSchristos 		fprintf(stderr, "Failed to remove all identities.\n");
236aa36fcacSchristos 	else if (!qflag)
237aa36fcacSchristos 		fprintf(stderr, "All identities removed.\n");
238ca32bd8dSchristos 
239ca32bd8dSchristos 	return ret;
240ca32bd8dSchristos }
241ca32bd8dSchristos 
242ca32bd8dSchristos static int
243514b5d45Schristos add_file(int agent_fd, const char *filename, int key_only, int cert_only,
244514b5d45Schristos     int qflag, const char *skprovider,
245514b5d45Schristos     struct dest_constraint **dest_constraints,
246a03ec00cSchristos     size_t ndest_constraints)
247ca32bd8dSchristos {
248e4d43b82Schristos 	struct sshkey *private, *cert;
249ca32bd8dSchristos 	char *comment = NULL;
250091c4109Schristos 	char msg[1024], *certpath = NULL;
251e4d43b82Schristos 	int r, fd, ret = -1;
252ffae97bbSchristos 	size_t i;
253ffae97bbSchristos 	u_int32_t left;
254e4d43b82Schristos 	struct sshbuf *keyblob;
255ffae97bbSchristos 	struct ssh_identitylist *idlist;
256ca32bd8dSchristos 
2576f47b660Schristos 	if (strcmp(filename, "-") == 0) {
2586f47b660Schristos 		fd = STDIN_FILENO;
2596f47b660Schristos 		filename = "(stdin)";
260cd4ada6aSchristos 	} else if ((fd = open(filename, O_RDONLY)) == -1) {
261ca32bd8dSchristos 		perror(filename);
262ca32bd8dSchristos 		return -1;
263ca32bd8dSchristos 	}
264ca32bd8dSchristos 
265ca32bd8dSchristos 	/*
266ca32bd8dSchristos 	 * Since we'll try to load a keyfile multiple times, permission errors
267ca32bd8dSchristos 	 * will occur multiple times, so check perms first and bail if wrong.
268ca32bd8dSchristos 	 */
2696f47b660Schristos 	if (fd != STDIN_FILENO) {
270e4d43b82Schristos 		if (sshkey_perm_ok(fd, filename) != 0) {
271ca32bd8dSchristos 			close(fd);
272ca32bd8dSchristos 			return -1;
2736f47b660Schristos 		}
2746f47b660Schristos 	}
275ed75d7a8Schristos 	if ((r = sshbuf_load_fd(fd, &keyblob)) != 0) {
276e4d43b82Schristos 		fprintf(stderr, "Error loading key \"%s\": %s\n",
277e4d43b82Schristos 		    filename, ssh_err(r));
278e4d43b82Schristos 		sshbuf_free(keyblob);
2796f47b660Schristos 		close(fd);
2806f47b660Schristos 		return -1;
2816f47b660Schristos 	}
2826f47b660Schristos 	close(fd);
283ca32bd8dSchristos 
284ca32bd8dSchristos 	/* At first, try empty passphrase */
28579976551Schristos 	if ((r = sshkey_parse_private_fileblob(keyblob, "", &private,
28679976551Schristos 	    &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
287e4d43b82Schristos 		fprintf(stderr, "Error loading key \"%s\": %s\n",
288e4d43b82Schristos 		    filename, ssh_err(r));
289e4d43b82Schristos 		goto fail_load;
290e4d43b82Schristos 	}
2918a4530f9Schristos 	/* try last */
2928a4530f9Schristos 	if (private == NULL && pass != NULL) {
29379976551Schristos 		if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private,
29479976551Schristos 		    &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
295e4d43b82Schristos 			fprintf(stderr, "Error loading key \"%s\": %s\n",
296e4d43b82Schristos 			    filename, ssh_err(r));
297e4d43b82Schristos 			goto fail_load;
298e4d43b82Schristos 		}
2998a4530f9Schristos 	}
300ca32bd8dSchristos 	if (private == NULL) {
301ca32bd8dSchristos 		/* clear passphrase since it did not work */
302ca32bd8dSchristos 		clear_pass();
30379976551Schristos 		snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ",
30479976551Schristos 		    filename, confirm ? " (will confirm each use)" : "");
305ca32bd8dSchristos 		for (;;) {
306ca32bd8dSchristos 			pass = read_passphrase(msg, RP_ALLOW_STDIN);
307e4d43b82Schristos 			if (strcmp(pass, "") == 0)
308e4d43b82Schristos 				goto fail_load;
309e4d43b82Schristos 			if ((r = sshkey_parse_private_fileblob(keyblob, pass,
31079976551Schristos 			    &private, &comment)) == 0)
311e4d43b82Schristos 				break;
312e4d43b82Schristos 			else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) {
313e4d43b82Schristos 				fprintf(stderr,
314e4d43b82Schristos 				    "Error loading key \"%s\": %s\n",
315e4d43b82Schristos 				    filename, ssh_err(r));
316e4d43b82Schristos  fail_load:
317ca32bd8dSchristos 				clear_pass();
318e4d43b82Schristos 				sshbuf_free(keyblob);
319ca32bd8dSchristos 				return -1;
320ca32bd8dSchristos 			}
321ca32bd8dSchristos 			clear_pass();
322ca32bd8dSchristos 			snprintf(msg, sizeof msg,
32379976551Schristos 			    "Bad passphrase, try again for %s%s: ", filename,
324e4d43b82Schristos 			    confirm ? " (will confirm each use)" : "");
325ca32bd8dSchristos 		}
326ca32bd8dSchristos 	}
32779976551Schristos 	if (comment == NULL || *comment == '\0')
32879976551Schristos 		comment = xstrdup(filename);
329e4d43b82Schristos 	sshbuf_free(keyblob);
330ca32bd8dSchristos 
331ffae97bbSchristos 	/* For XMSS */
332ffae97bbSchristos 	if ((r = sshkey_set_filename(private, filename)) != 0) {
333ffae97bbSchristos 		fprintf(stderr, "Could not add filename to private key: %s (%s)\n",
334ffae97bbSchristos 		    filename, comment);
335ffae97bbSchristos 		goto out;
336ffae97bbSchristos 	}
337ffae97bbSchristos 	if (maxsign && minleft &&
338ffae97bbSchristos 	    (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) {
339ffae97bbSchristos 		for (i = 0; i < idlist->nkeys; i++) {
340ffae97bbSchristos 			if (!sshkey_equal_public(idlist->keys[i], private))
341ffae97bbSchristos 				continue;
342ffae97bbSchristos 			left = sshkey_signatures_left(idlist->keys[i]);
343ffae97bbSchristos 			if (left < minleft) {
344ffae97bbSchristos 				fprintf(stderr,
345ffae97bbSchristos 				    "Only %d signatures left.\n", left);
346ffae97bbSchristos 				break;
347ffae97bbSchristos 			}
348ffae97bbSchristos 			fprintf(stderr, "Skipping update: ");
349ffae97bbSchristos 			if (left == minleft) {
350ffae97bbSchristos 				fprintf(stderr,
351ffae97bbSchristos 				    "required signatures left (%d).\n", left);
352ffae97bbSchristos 			} else {
353ffae97bbSchristos 				fprintf(stderr,
354ffae97bbSchristos 				    "more signatures left (%d) than"
355ffae97bbSchristos 				    " required (%d).\n", left, minleft);
356ffae97bbSchristos 			}
357ffae97bbSchristos 			ssh_free_identitylist(idlist);
358ffae97bbSchristos 			goto out;
359ffae97bbSchristos 		}
360ffae97bbSchristos 		ssh_free_identitylist(idlist);
361ffae97bbSchristos 	}
362ffae97bbSchristos 
3632d3b0f52Schristos 	if (sshkey_is_sk(private)) {
3642d3b0f52Schristos 		if (skprovider == NULL) {
3652d3b0f52Schristos 			fprintf(stderr, "Cannot load FIDO key %s "
366ed75d7a8Schristos 			    "without provider\n", filename);
367ed75d7a8Schristos 			goto out;
368ed75d7a8Schristos 		}
3692d3b0f52Schristos 	} else {
3702d3b0f52Schristos 		/* Don't send provider constraint for other keys */
3712d3b0f52Schristos 		skprovider = NULL;
3722d3b0f52Schristos 	}
373ed75d7a8Schristos 
374514b5d45Schristos 	if (!cert_only &&
375514b5d45Schristos 	    (r = ssh_add_identity_constrained(agent_fd, private, comment,
376a03ec00cSchristos 	    lifetime, confirm, maxsign, skprovider,
377a03ec00cSchristos 	    dest_constraints, ndest_constraints)) == 0) {
378ca32bd8dSchristos 		ret = 0;
379aa36fcacSchristos 		if (!qflag) {
380aa36fcacSchristos 			fprintf(stderr, "Identity added: %s (%s)\n",
381aa36fcacSchristos 			    filename, comment);
382aa36fcacSchristos 			if (lifetime != 0) {
383ca32bd8dSchristos 				fprintf(stderr,
38417418e98Schristos 				    "Lifetime set to %d seconds\n", lifetime);
385aa36fcacSchristos 			}
386aa36fcacSchristos 			if (confirm != 0) {
387aa36fcacSchristos 				fprintf(stderr, "The user must confirm "
388aa36fcacSchristos 				    "each use of the key\n");
389aa36fcacSchristos 			}
390aa36fcacSchristos 		}
391ca32bd8dSchristos 	} else {
392e4d43b82Schristos 		fprintf(stderr, "Could not add identity \"%s\": %s\n",
393e4d43b82Schristos 		    filename, ssh_err(r));
394ca32bd8dSchristos 	}
395ca32bd8dSchristos 
396091c4109Schristos 	/* Skip trying to load the cert if requested */
397091c4109Schristos 	if (key_only)
398091c4109Schristos 		goto out;
39934b27b53Sadam 
40034b27b53Sadam 	/* Now try to add the certificate flavour too */
40134b27b53Sadam 	xasprintf(&certpath, "%s-cert.pub", filename);
402e4d43b82Schristos 	if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) {
403e4d43b82Schristos 		if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT)
404514b5d45Schristos 			error_r(r, "Failed to load certificate \"%s\"",
405514b5d45Schristos 			    certpath);
40634b27b53Sadam 		goto out;
407e4d43b82Schristos 	}
40834b27b53Sadam 
409e4d43b82Schristos 	if (!sshkey_equal_public(cert, private)) {
41034b27b53Sadam 		error("Certificate %s does not match private key %s",
41134b27b53Sadam 		    certpath, filename);
412e4d43b82Schristos 		sshkey_free(cert);
41334b27b53Sadam 		goto out;
41434b27b53Sadam 	}
41534b27b53Sadam 
41634b27b53Sadam 	/* Graft with private bits */
4178395c133Schristos 	if ((r = sshkey_to_certified(private)) != 0) {
41817418e98Schristos 		error_fr(r, "sshkey_to_certified");
419e4d43b82Schristos 		sshkey_free(cert);
42034b27b53Sadam 		goto out;
42134b27b53Sadam 	}
422e4d43b82Schristos 	if ((r = sshkey_cert_copy(cert, private)) != 0) {
42317418e98Schristos 		error_fr(r, "sshkey_cert_copy");
424e4d43b82Schristos 		sshkey_free(cert);
425e4d43b82Schristos 		goto out;
426e4d43b82Schristos 	}
427e4d43b82Schristos 	sshkey_free(cert);
42834b27b53Sadam 
429e4d43b82Schristos 	if ((r = ssh_add_identity_constrained(agent_fd, private, comment,
430a03ec00cSchristos 	    lifetime, confirm, maxsign, skprovider,
431a03ec00cSchristos 	    dest_constraints, ndest_constraints)) != 0) {
43217418e98Schristos 		error_r(r, "Certificate %s (%s) add failed", certpath,
43317418e98Schristos 		    private->cert->key_id);
434e4d43b82Schristos 		goto out;
43534b27b53Sadam 	}
436aa36fcacSchristos 	/* success */
437aa36fcacSchristos 	if (!qflag) {
43834b27b53Sadam 		fprintf(stderr, "Certificate added: %s (%s)\n", certpath,
43934b27b53Sadam 		    private->cert->key_id);
440aa36fcacSchristos 		if (lifetime != 0) {
44117418e98Schristos 			fprintf(stderr, "Lifetime set to %d seconds\n",
442aa36fcacSchristos 			    lifetime);
443aa36fcacSchristos 		}
444aa36fcacSchristos 		if (confirm != 0) {
445aa36fcacSchristos 			fprintf(stderr, "The user must confirm each use "
446aa36fcacSchristos 			    "of the key\n");
447aa36fcacSchristos 		}
448aa36fcacSchristos 	}
449aa36fcacSchristos 
45034b27b53Sadam  out:
45100a838c4Schristos 	free(certpath);
45200a838c4Schristos 	free(comment);
453e4d43b82Schristos 	sshkey_free(private);
454ca32bd8dSchristos 
455ca32bd8dSchristos 	return ret;
456ca32bd8dSchristos }
457ca32bd8dSchristos 
458ca32bd8dSchristos static int
459a03ec00cSchristos update_card(int agent_fd, int add, const char *id, int qflag,
460514b5d45Schristos     int key_only, int cert_only,
461514b5d45Schristos     struct dest_constraint **dest_constraints, size_t ndest_constraints,
462514b5d45Schristos     struct sshkey **certs, size_t ncerts)
463ca32bd8dSchristos {
4648a4530f9Schristos 	char *pin = NULL;
465e4d43b82Schristos 	int r, ret = -1;
466ca32bd8dSchristos 
467514b5d45Schristos 	if (key_only)
468514b5d45Schristos 		ncerts = 0;
469514b5d45Schristos 
4708a4530f9Schristos 	if (add) {
4718a4530f9Schristos 		if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
4728a4530f9Schristos 		    RP_ALLOW_STDIN)) == NULL)
473ca32bd8dSchristos 			return -1;
4748a4530f9Schristos 	}
475ca32bd8dSchristos 
476e4d43b82Schristos 	if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin,
477514b5d45Schristos 	    lifetime, confirm, dest_constraints, ndest_constraints,
478514b5d45Schristos 	    cert_only, certs, ncerts)) == 0) {
479aa36fcacSchristos 		ret = 0;
480aa36fcacSchristos 		if (!qflag) {
481ca32bd8dSchristos 			fprintf(stderr, "Card %s: %s\n",
482ca32bd8dSchristos 			    add ? "added" : "removed", id);
483aa36fcacSchristos 		}
484ca32bd8dSchristos 	} else {
485e4d43b82Schristos 		fprintf(stderr, "Could not %s card \"%s\": %s\n",
486e4d43b82Schristos 		    add ? "add" : "remove", id, ssh_err(r));
487ca32bd8dSchristos 		ret = -1;
488ca32bd8dSchristos 	}
48900a838c4Schristos 	free(pin);
490ca32bd8dSchristos 	return ret;
491ca32bd8dSchristos }
492ca32bd8dSchristos 
493ca32bd8dSchristos static int
494aa36fcacSchristos test_key(int agent_fd, const char *filename)
495aa36fcacSchristos {
496aa36fcacSchristos 	struct sshkey *key = NULL;
497aa36fcacSchristos 	u_char *sig = NULL;
498b1066cf3Schristos 	const char *alg = NULL;
499aa36fcacSchristos 	size_t slen = 0;
500aa36fcacSchristos 	int r, ret = -1;
501aa36fcacSchristos 	u_char data[1024];
502aa36fcacSchristos 
503aa36fcacSchristos 	if ((r = sshkey_load_public(filename, &key, NULL)) != 0) {
50417418e98Schristos 		error_r(r, "Couldn't read public key %s", filename);
505aa36fcacSchristos 		return -1;
506aa36fcacSchristos 	}
507b1066cf3Schristos 	if (sshkey_type_plain(key->type) == KEY_RSA)
508b1066cf3Schristos 		alg = "rsa-sha2-256";
509aa36fcacSchristos 	arc4random_buf(data, sizeof(data));
510aa36fcacSchristos 	if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data),
511b1066cf3Schristos 	    alg, 0)) != 0) {
51217418e98Schristos 		error_r(r, "Agent signature failed for %s", filename);
513aa36fcacSchristos 		goto done;
514aa36fcacSchristos 	}
515aa36fcacSchristos 	if ((r = sshkey_verify(key, sig, slen, data, sizeof(data),
516b1066cf3Schristos 	    alg, 0, NULL)) != 0) {
51717418e98Schristos 		error_r(r, "Signature verification failed for %s", filename);
518aa36fcacSchristos 		goto done;
519aa36fcacSchristos 	}
520aa36fcacSchristos 	/* success */
521aa36fcacSchristos 	ret = 0;
522aa36fcacSchristos  done:
523aa36fcacSchristos 	free(sig);
524aa36fcacSchristos 	sshkey_free(key);
525aa36fcacSchristos 	return ret;
526aa36fcacSchristos }
527aa36fcacSchristos 
528aa36fcacSchristos static int
529e4d43b82Schristos list_identities(int agent_fd, int do_fp)
530ca32bd8dSchristos {
531e4d43b82Schristos 	char *fp;
5327a183406Schristos 	int r;
533e4d43b82Schristos 	struct ssh_identitylist *idlist;
534ffae97bbSchristos 	u_int32_t left;
535e4d43b82Schristos 	size_t i;
536ca32bd8dSchristos 
5377a183406Schristos 	if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) {
538e4d43b82Schristos 		if (r != SSH_ERR_AGENT_NO_IDENTITIES)
5397a183406Schristos 			fprintf(stderr, "error fetching identities: %s\n",
5407a183406Schristos 			    ssh_err(r));
5417a183406Schristos 		else
5427a183406Schristos 			printf("The agent has no identities.\n");
5437a183406Schristos 		return -1;
544e4d43b82Schristos 	}
545e4d43b82Schristos 	for (i = 0; i < idlist->nkeys; i++) {
546ca32bd8dSchristos 		if (do_fp) {
547e4d43b82Schristos 			fp = sshkey_fingerprint(idlist->keys[i],
548e4d43b82Schristos 			    fingerprint_hash, SSH_FP_DEFAULT);
5497a183406Schristos 			printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]),
5507a183406Schristos 			    fp == NULL ? "(null)" : fp, idlist->comments[i],
551e4d43b82Schristos 			    sshkey_type(idlist->keys[i]));
55200a838c4Schristos 			free(fp);
553ca32bd8dSchristos 		} else {
5547a183406Schristos 			if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) {
555e4d43b82Schristos 				fprintf(stderr, "sshkey_write: %s\n",
556e4d43b82Schristos 				    ssh_err(r));
557e4d43b82Schristos 				continue;
558ca32bd8dSchristos 			}
559ffae97bbSchristos 			fprintf(stdout, " %s", idlist->comments[i]);
560ffae97bbSchristos 			left = sshkey_signatures_left(idlist->keys[i]);
561ffae97bbSchristos 			if (left > 0)
562ffae97bbSchristos 				fprintf(stdout,
563ffae97bbSchristos 				    " [signatures left %d]", left);
564ffae97bbSchristos 			fprintf(stdout, "\n");
565ca32bd8dSchristos 		}
566ca32bd8dSchristos 	}
567e4d43b82Schristos 	ssh_free_identitylist(idlist);
568ca32bd8dSchristos 	return 0;
569ca32bd8dSchristos }
570ca32bd8dSchristos 
571ca32bd8dSchristos static int
572e4d43b82Schristos lock_agent(int agent_fd, int lock)
573ca32bd8dSchristos {
574ca32bd8dSchristos 	char prompt[100], *p1, *p2;
575e4d43b82Schristos 	int r, passok = 1, ret = -1;
576ca32bd8dSchristos 
577ca32bd8dSchristos 	strlcpy(prompt, "Enter lock password: ", sizeof(prompt));
578ca32bd8dSchristos 	p1 = read_passphrase(prompt, RP_ALLOW_STDIN);
579ca32bd8dSchristos 	if (lock) {
580ca32bd8dSchristos 		strlcpy(prompt, "Again: ", sizeof prompt);
581ca32bd8dSchristos 		p2 = read_passphrase(prompt, RP_ALLOW_STDIN);
582ca32bd8dSchristos 		if (strcmp(p1, p2) != 0) {
583ca32bd8dSchristos 			fprintf(stderr, "Passwords do not match.\n");
584ca32bd8dSchristos 			passok = 0;
585ca32bd8dSchristos 		}
5868db691beSchristos 		freezero(p2, strlen(p2));
587ca32bd8dSchristos 	}
588e4d43b82Schristos 	if (passok) {
589e4d43b82Schristos 		if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) {
590ca32bd8dSchristos 			fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un");
591ca32bd8dSchristos 			ret = 0;
592e4d43b82Schristos 		} else {
593e4d43b82Schristos 			fprintf(stderr, "Failed to %slock agent: %s\n",
594e4d43b82Schristos 			    lock ? "" : "un", ssh_err(r));
595e4d43b82Schristos 		}
596e4d43b82Schristos 	}
5978db691beSchristos 	freezero(p1, strlen(p1));
598ca32bd8dSchristos 	return (ret);
599ca32bd8dSchristos }
600ca32bd8dSchristos 
601ca32bd8dSchristos static int
602a03ec00cSchristos load_resident_keys(int agent_fd, const char *skprovider, int qflag,
603a03ec00cSchristos     struct dest_constraint **dest_constraints, size_t ndest_constraints)
604ed75d7a8Schristos {
605a03ec00cSchristos 	struct sshsk_resident_key **srks;
606a03ec00cSchristos 	size_t nsrks, i;
607a03ec00cSchristos 	struct sshkey *key;
608ed75d7a8Schristos 	int r, ok = 0;
609ed75d7a8Schristos 	char *fp;
610ed75d7a8Schristos 
611ed75d7a8Schristos 	pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN);
612a03ec00cSchristos 	if ((r = sshsk_load_resident(skprovider, NULL, pass, 0,
613a03ec00cSchristos 	    &srks, &nsrks)) != 0) {
61417418e98Schristos 		error_r(r, "Unable to load resident keys");
615ed75d7a8Schristos 		return r;
616ed75d7a8Schristos 	}
617a03ec00cSchristos 	for (i = 0; i < nsrks; i++) {
618a03ec00cSchristos 		key = srks[i]->key;
619a03ec00cSchristos 		if ((fp = sshkey_fingerprint(key,
620ed75d7a8Schristos 		    fingerprint_hash, SSH_FP_DEFAULT)) == NULL)
62117418e98Schristos 			fatal_f("sshkey_fingerprint failed");
622a03ec00cSchristos 		if ((r = ssh_add_identity_constrained(agent_fd, key, "",
623a03ec00cSchristos 		    lifetime, confirm, maxsign, skprovider,
624a03ec00cSchristos 		    dest_constraints, ndest_constraints)) != 0) {
625ed75d7a8Schristos 			error("Unable to add key %s %s",
626a03ec00cSchristos 			    sshkey_type(key), fp);
627ed75d7a8Schristos 			free(fp);
628ed75d7a8Schristos 			ok = r;
629ed75d7a8Schristos 			continue;
630ed75d7a8Schristos 		}
631ed75d7a8Schristos 		if (ok == 0)
632ed75d7a8Schristos 			ok = 1;
633ed75d7a8Schristos 		if (!qflag) {
634ed75d7a8Schristos 			fprintf(stderr, "Resident identity added: %s %s\n",
635a03ec00cSchristos 			    sshkey_type(key), fp);
636ed75d7a8Schristos 			if (lifetime != 0) {
637ed75d7a8Schristos 				fprintf(stderr,
63817418e98Schristos 				    "Lifetime set to %d seconds\n", lifetime);
639ed75d7a8Schristos 			}
640ed75d7a8Schristos 			if (confirm != 0) {
641ed75d7a8Schristos 				fprintf(stderr, "The user must confirm "
642ed75d7a8Schristos 				    "each use of the key\n");
643ed75d7a8Schristos 			}
644ed75d7a8Schristos 		}
645ed75d7a8Schristos 		free(fp);
646ed75d7a8Schristos 	}
647a03ec00cSchristos 	sshsk_free_resident_keys(srks, nsrks);
648a03ec00cSchristos 	if (nsrks == 0)
649ed75d7a8Schristos 		return SSH_ERR_KEY_NOT_FOUND;
650ed75d7a8Schristos 	return ok == 1 ? 0 : ok;
651ed75d7a8Schristos }
652ed75d7a8Schristos 
653ed75d7a8Schristos static int
654514b5d45Schristos do_file(int agent_fd, int deleting, int key_only, int cert_only,
655514b5d45Schristos     char *file, int qflag, const char *skprovider,
656514b5d45Schristos     struct dest_constraint **dest_constraints, size_t ndest_constraints)
657ca32bd8dSchristos {
658ca32bd8dSchristos 	if (deleting) {
659514b5d45Schristos 		if (delete_file(agent_fd, file, key_only,
660514b5d45Schristos 		    cert_only, qflag) == -1)
661ca32bd8dSchristos 			return -1;
662ca32bd8dSchristos 	} else {
663514b5d45Schristos 		if (add_file(agent_fd, file, key_only, cert_only, qflag,
664514b5d45Schristos 		    skprovider, dest_constraints, ndest_constraints) == -1)
665ca32bd8dSchristos 			return -1;
666ca32bd8dSchristos 	}
667ca32bd8dSchristos 	return 0;
668ca32bd8dSchristos }
669ca32bd8dSchristos 
670a03ec00cSchristos /* Append string 's' to a NULL-terminated array of strings */
671a03ec00cSchristos static void
672a03ec00cSchristos stringlist_append(char ***listp, const char *s)
673a03ec00cSchristos {
674a03ec00cSchristos 	size_t i = 0;
675a03ec00cSchristos 
676a03ec00cSchristos 	if (*listp == NULL)
677a03ec00cSchristos 		*listp = xcalloc(2, sizeof(**listp));
678a03ec00cSchristos 	else {
679a03ec00cSchristos 		for (i = 0; (*listp)[i] != NULL; i++)
680a03ec00cSchristos 			; /* count */
681a03ec00cSchristos 		*listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp));
682a03ec00cSchristos 	}
683a03ec00cSchristos 	(*listp)[i] = xstrdup(s);
684a03ec00cSchristos }
685a03ec00cSchristos 
686a03ec00cSchristos static void
687a03ec00cSchristos parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch,
688a03ec00cSchristos     char **hostkey_files)
689a03ec00cSchristos {
690a03ec00cSchristos 	char *user = NULL, *host, *os, *path;
691a03ec00cSchristos 	size_t i;
692a03ec00cSchristos 	struct hostkeys *hostkeys;
693a03ec00cSchristos 	const struct hostkey_entry *hke;
694a03ec00cSchristos 	int r, want_ca;
695a03ec00cSchristos 
696a03ec00cSchristos 	memset(dch, '\0', sizeof(*dch));
697a03ec00cSchristos 	os = xstrdup(s);
698*9469f4f1Schristos 	if ((host = strrchr(os, '@')) == NULL)
699a03ec00cSchristos 		host = os;
700a03ec00cSchristos 	else {
701a03ec00cSchristos 		*host++ = '\0';
702a03ec00cSchristos 		user = os;
703a03ec00cSchristos 	}
704a03ec00cSchristos 	cleanhostname(host);
705a03ec00cSchristos 	/* Trivial case: username@ (all hosts) */
706a03ec00cSchristos 	if (*host == '\0') {
707a03ec00cSchristos 		if (user == NULL) {
708a03ec00cSchristos 			fatal("Invalid key destination constraint \"%s\": "
709a03ec00cSchristos 			    "does not specify user or host", s);
710a03ec00cSchristos 		}
711a03ec00cSchristos 		dch->user = xstrdup(user);
712a03ec00cSchristos 		/* other fields left blank */
713a03ec00cSchristos 		free(os);
714a03ec00cSchristos 		return;
715a03ec00cSchristos 	}
716a03ec00cSchristos 	if (hostkey_files == NULL)
717a03ec00cSchristos 		fatal_f("no hostkey files");
718a03ec00cSchristos 	/* Otherwise we need to look up the keys for this hostname */
719a03ec00cSchristos 	hostkeys = init_hostkeys();
720a03ec00cSchristos 	for (i = 0; hostkey_files[i]; i++) {
721a03ec00cSchristos 		path = tilde_expand_filename(hostkey_files[i], getuid());
722a03ec00cSchristos 		debug2_f("looking up host keys for \"%s\" in %s", host, path);
723a03ec00cSchristos                 load_hostkeys(hostkeys, host, path, 0);
724a03ec00cSchristos 		free(path);
725a03ec00cSchristos 	}
726a03ec00cSchristos 	dch->user = user == NULL ? NULL : xstrdup(user);
727a03ec00cSchristos 	dch->hostname = xstrdup(host);
728a03ec00cSchristos 	for (i = 0; i < hostkeys->num_entries; i++) {
729a03ec00cSchristos 		hke = hostkeys->entries + i;
730a03ec00cSchristos 		want_ca = hke->marker == MRK_CA;
731a03ec00cSchristos 		if (hke->marker != MRK_NONE && !want_ca)
732a03ec00cSchristos 			continue;
733a03ec00cSchristos 		debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u",
734a03ec00cSchristos 		    user == NULL ? "": user, user == NULL ? "" : "@",
735a03ec00cSchristos 		    host, sshkey_type(hke->key), want_ca ? "CA " : "",
736a03ec00cSchristos 		    hke->file, hke->line, dch->nkeys);
737a03ec00cSchristos 		dch->keys = xrecallocarray(dch->keys, dch->nkeys,
738a03ec00cSchristos 		    dch->nkeys + 1, sizeof(*dch->keys));
739a03ec00cSchristos 		dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys,
740a03ec00cSchristos 		    dch->nkeys + 1, sizeof(*dch->key_is_ca));
741a03ec00cSchristos 		if ((r = sshkey_from_private(hke->key,
742a03ec00cSchristos 		    &(dch->keys[dch->nkeys]))) != 0)
743a03ec00cSchristos 			fatal_fr(r, "sshkey_from_private");
744a03ec00cSchristos 		dch->key_is_ca[dch->nkeys] = want_ca;
745a03ec00cSchristos 		dch->nkeys++;
746a03ec00cSchristos 	}
747a03ec00cSchristos 	if (dch->nkeys == 0)
748a03ec00cSchristos 		fatal("No host keys found for destination \"%s\"", host);
749a03ec00cSchristos 	free_hostkeys(hostkeys);
750a03ec00cSchristos 	free(os);
751a03ec00cSchristos 	return;
752a03ec00cSchristos }
753a03ec00cSchristos 
754a03ec00cSchristos static void
755a03ec00cSchristos parse_dest_constraint(const char *s, struct dest_constraint ***dcp,
756a03ec00cSchristos     size_t *ndcp, char **hostkey_files)
757a03ec00cSchristos {
758a03ec00cSchristos 	struct dest_constraint *dc;
759a03ec00cSchristos 	char *os, *cp;
760a03ec00cSchristos 
761a03ec00cSchristos 	dc = xcalloc(1, sizeof(*dc));
762a03ec00cSchristos 	os = xstrdup(s);
763a03ec00cSchristos 	if ((cp = strchr(os, '>')) == NULL) {
764a03ec00cSchristos 		/* initial hop; no 'from' hop specified */
765a03ec00cSchristos 		parse_dest_constraint_hop(os, &dc->to, hostkey_files);
766a03ec00cSchristos 	} else {
767a03ec00cSchristos 		/* two hops specified */
768a03ec00cSchristos 		*(cp++) = '\0';
769a03ec00cSchristos 		parse_dest_constraint_hop(os, &dc->from, hostkey_files);
770a03ec00cSchristos 		parse_dest_constraint_hop(cp, &dc->to, hostkey_files);
771a03ec00cSchristos 		if (dc->from.user != NULL) {
772a03ec00cSchristos 			fatal("Invalid key constraint %s: cannot specify "
773a03ec00cSchristos 			    "user on 'from' host", os);
774a03ec00cSchristos 		}
775a03ec00cSchristos 	}
776a03ec00cSchristos 	/* XXX eliminate or error on duplicates */
777a03ec00cSchristos 	debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp,
778a03ec00cSchristos 	    dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "",
779a03ec00cSchristos 	    dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys,
780a03ec00cSchristos 	    dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "",
781a03ec00cSchristos 	    dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys);
782a03ec00cSchristos 	*dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp));
783a03ec00cSchristos 	(*dcp)[(*ndcp)++] = dc;
784a03ec00cSchristos 	free(os);
785a03ec00cSchristos }
786a03ec00cSchristos 
787a03ec00cSchristos 
788ca32bd8dSchristos static void
789ca32bd8dSchristos usage(void)
790ca32bd8dSchristos {
791ed75d7a8Schristos 	fprintf(stderr,
792c5555919Schristos "usage: ssh-add [-CcDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n"
793a03ec00cSchristos "               [-h destination_constraint] [-S provider] [-t life]\n"
794ed75d7a8Schristos #ifdef WITH_XMSS
795ed75d7a8Schristos "               [-M maxsign] [-m minleft]\n"
796ed75d7a8Schristos #endif
797ed75d7a8Schristos "               [file ...]\n"
798c5555919Schristos "       ssh-add -s pkcs11 [-Cv] [certificate ...]\n"
799ed75d7a8Schristos "       ssh-add -e pkcs11\n"
800ed75d7a8Schristos "       ssh-add -T pubkey ...\n"
801ed75d7a8Schristos 	);
802ca32bd8dSchristos }
803ca32bd8dSchristos 
804ca32bd8dSchristos int
805ca32bd8dSchristos main(int argc, char **argv)
806ca32bd8dSchristos {
807ca32bd8dSchristos 	extern char *optarg;
808ca32bd8dSchristos 	extern int optind;
809e4d43b82Schristos 	int agent_fd;
81034b27b53Sadam 	char *pkcs11provider = NULL;
811ed75d7a8Schristos 	const char *skprovider = NULL;
812a03ec00cSchristos 	char **dest_constraint_strings = NULL, **hostkey_files = NULL;
813514b5d45Schristos 	int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0;
814514b5d45Schristos 	int do_download = 0, xflag = 0, lflag = 0, Dflag = 0;
815514b5d45Schristos 	int qflag = 0, Tflag = 0;
816aa36fcacSchristos 	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
817aa36fcacSchristos 	LogLevel log_level = SYSLOG_LEVEL_INFO;
818514b5d45Schristos 	struct sshkey *k, **certs = NULL;
819a03ec00cSchristos 	struct dest_constraint **dest_constraints = NULL;
820c5555919Schristos 	size_t ndest_constraints = 0, ncerts = 0;
821ca32bd8dSchristos 
822ca32bd8dSchristos 	/* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
823ca32bd8dSchristos 	sanitise_stdfd();
824ca32bd8dSchristos 
825cd4ada6aSchristos #ifdef WITH_OPENSSL
826185c8f97Schristos 	OpenSSL_add_all_algorithms();
827cd4ada6aSchristos #endif
828aa36fcacSchristos 	log_init(__progname, log_level, log_facility, 1);
829aa36fcacSchristos 
830e4d43b82Schristos 	setvbuf(stdout, NULL, _IOLBF, 0);
8318a4530f9Schristos 
832e4d43b82Schristos 	/* First, get a connection to the authentication agent. */
833e4d43b82Schristos 	switch (r = ssh_get_authentication_socket(&agent_fd)) {
834e4d43b82Schristos 	case 0:
835e4d43b82Schristos 		break;
836e4d43b82Schristos 	case SSH_ERR_AGENT_NOT_PRESENT:
837e4d43b82Schristos 		fprintf(stderr, "Could not open a connection to your "
838e4d43b82Schristos 		    "authentication agent.\n");
839e4d43b82Schristos 		exit(2);
840e4d43b82Schristos 	default:
841e4d43b82Schristos 		fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r));
842ca32bd8dSchristos 		exit(2);
843ca32bd8dSchristos 	}
844e4d43b82Schristos 
845ed75d7a8Schristos 	skprovider = getenv("SSH_SK_PROVIDER");
846ed75d7a8Schristos 
847514b5d45Schristos 	while ((ch = getopt(argc, argv, "vkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) {
848ca32bd8dSchristos 		switch (ch) {
849aa36fcacSchristos 		case 'v':
850aa36fcacSchristos 			if (log_level == SYSLOG_LEVEL_INFO)
851aa36fcacSchristos 				log_level = SYSLOG_LEVEL_DEBUG1;
852aa36fcacSchristos 			else if (log_level < SYSLOG_LEVEL_DEBUG3)
853aa36fcacSchristos 				log_level++;
854aa36fcacSchristos 			break;
855e4d43b82Schristos 		case 'E':
856e4d43b82Schristos 			fingerprint_hash = ssh_digest_alg_by_name(optarg);
857e4d43b82Schristos 			if (fingerprint_hash == -1)
858e4d43b82Schristos 				fatal("Invalid hash algorithm \"%s\"", optarg);
859e4d43b82Schristos 			break;
860a03ec00cSchristos 		case 'H':
861a03ec00cSchristos 			stringlist_append(&hostkey_files, optarg);
862a03ec00cSchristos 			break;
863a03ec00cSchristos 		case 'h':
864a03ec00cSchristos 			stringlist_append(&dest_constraint_strings, optarg);
865a03ec00cSchristos 			break;
866091c4109Schristos 		case 'k':
867091c4109Schristos 			key_only = 1;
868091c4109Schristos 			break;
869514b5d45Schristos 		case 'C':
870514b5d45Schristos 			cert_only = 1;
871514b5d45Schristos 			break;
872ed75d7a8Schristos 		case 'K':
873ed75d7a8Schristos 			do_download = 1;
874ed75d7a8Schristos 			break;
875ca32bd8dSchristos 		case 'l':
876ca32bd8dSchristos 		case 'L':
877e4d43b82Schristos 			if (lflag != 0)
878e4d43b82Schristos 				fatal("-%c flag already specified", lflag);
879e4d43b82Schristos 			lflag = ch;
880e4d43b82Schristos 			break;
881ca32bd8dSchristos 		case 'x':
882ca32bd8dSchristos 		case 'X':
883e4d43b82Schristos 			if (xflag != 0)
884e4d43b82Schristos 				fatal("-%c flag already specified", xflag);
885e4d43b82Schristos 			xflag = ch;
886e4d43b82Schristos 			break;
887ca32bd8dSchristos 		case 'c':
888ca32bd8dSchristos 			confirm = 1;
889ca32bd8dSchristos 			break;
890ffae97bbSchristos 		case 'm':
891a629fefcSchristos 			minleft = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
892ffae97bbSchristos 			if (minleft == 0) {
893ffae97bbSchristos 				usage();
894ffae97bbSchristos 				ret = 1;
895ffae97bbSchristos 				goto done;
896ffae97bbSchristos 			}
897ffae97bbSchristos 			break;
898ffae97bbSchristos 		case 'M':
899a629fefcSchristos 			maxsign = (u_int)strtonum(optarg, 1, UINT_MAX, NULL);
900ffae97bbSchristos 			if (maxsign == 0) {
901ffae97bbSchristos 				usage();
902ffae97bbSchristos 				ret = 1;
903ffae97bbSchristos 				goto done;
904ffae97bbSchristos 			}
905ffae97bbSchristos 			break;
906ca32bd8dSchristos 		case 'd':
907ca32bd8dSchristos 			deleting = 1;
908ca32bd8dSchristos 			break;
909ca32bd8dSchristos 		case 'D':
910e4d43b82Schristos 			Dflag = 1;
911e4d43b82Schristos 			break;
912ca32bd8dSchristos 		case 's':
91334b27b53Sadam 			pkcs11provider = optarg;
914ca32bd8dSchristos 			break;
915ed75d7a8Schristos 		case 'S':
916ed75d7a8Schristos 			skprovider = optarg;
917ed75d7a8Schristos 			break;
918ca32bd8dSchristos 		case 'e':
919ca32bd8dSchristos 			deleting = 1;
92034b27b53Sadam 			pkcs11provider = optarg;
921ca32bd8dSchristos 			break;
922ca32bd8dSchristos 		case 't':
9238db691beSchristos 			if ((lifetime = convtime(optarg)) == -1 ||
9248db691beSchristos 			    lifetime < 0 || (u_long)lifetime > UINT32_MAX) {
925ca32bd8dSchristos 				fprintf(stderr, "Invalid lifetime\n");
926ca32bd8dSchristos 				ret = 1;
927ca32bd8dSchristos 				goto done;
928ca32bd8dSchristos 			}
929ca32bd8dSchristos 			break;
9307a183406Schristos 		case 'q':
9317a183406Schristos 			qflag = 1;
9327a183406Schristos 			break;
933aa36fcacSchristos 		case 'T':
934aa36fcacSchristos 			Tflag = 1;
935aa36fcacSchristos 			break;
936ca32bd8dSchristos 		default:
937ca32bd8dSchristos 			usage();
938ca32bd8dSchristos 			ret = 1;
939ca32bd8dSchristos 			goto done;
940ca32bd8dSchristos 		}
941ca32bd8dSchristos 	}
942aa36fcacSchristos 	log_init(__progname, log_level, log_facility, 1);
943e4d43b82Schristos 
944e4d43b82Schristos 	if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1)
945e4d43b82Schristos 		fatal("Invalid combination of actions");
946e4d43b82Schristos 	else if (xflag) {
947e4d43b82Schristos 		if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1)
948e4d43b82Schristos 			ret = 1;
949e4d43b82Schristos 		goto done;
950e4d43b82Schristos 	} else if (lflag) {
951e4d43b82Schristos 		if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1)
952e4d43b82Schristos 			ret = 1;
953e4d43b82Schristos 		goto done;
954e4d43b82Schristos 	} else if (Dflag) {
955aa36fcacSchristos 		if (delete_all(agent_fd, qflag) == -1)
956e4d43b82Schristos 			ret = 1;
957e4d43b82Schristos 		goto done;
958e4d43b82Schristos 	}
959e4d43b82Schristos 
960ed75d7a8Schristos 	if (skprovider == NULL)
961ed75d7a8Schristos 		skprovider = "internal";
962a03ec00cSchristos 	if (hostkey_files == NULL) {
963a03ec00cSchristos 		/* use defaults from readconf.c */
964a03ec00cSchristos 		stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE);
965a03ec00cSchristos 		stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2);
966a03ec00cSchristos 		stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE);
967a03ec00cSchristos 		stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2);
968a03ec00cSchristos 	}
969a03ec00cSchristos 	if (dest_constraint_strings != NULL) {
970a03ec00cSchristos 		for (i = 0; dest_constraint_strings[i] != NULL; i++) {
971a03ec00cSchristos 			parse_dest_constraint(dest_constraint_strings[i],
972a03ec00cSchristos 			  &dest_constraints, &ndest_constraints, hostkey_files);
973a03ec00cSchristos 		}
974a03ec00cSchristos 	}
975ed75d7a8Schristos 
976ca32bd8dSchristos 	argc -= optind;
977ca32bd8dSchristos 	argv += optind;
978aa36fcacSchristos 	if (Tflag) {
979aa36fcacSchristos 		if (argc <= 0)
980aa36fcacSchristos 			fatal("no keys to test");
981aa36fcacSchristos 		for (r = i = 0; i < argc; i++)
982aa36fcacSchristos 			r |= test_key(agent_fd, argv[i]);
983aa36fcacSchristos 		ret = r == 0 ? 0 : 1;
984aa36fcacSchristos 		goto done;
985aa36fcacSchristos 	}
98634b27b53Sadam 	if (pkcs11provider != NULL) {
987514b5d45Schristos 		for (i = 0; i < argc; i++) {
988514b5d45Schristos 			if ((r = sshkey_load_public(argv[i], &k, NULL)) != 0)
989514b5d45Schristos 				fatal_fr(r, "load certificate %s", argv[i]);
990514b5d45Schristos 			certs = xrecallocarray(certs, ncerts, ncerts + 1,
991514b5d45Schristos 			    sizeof(*certs));
992514b5d45Schristos 			debug2("%s: %s", argv[i], sshkey_ssh_name(k));
993514b5d45Schristos 			certs[ncerts++] = k;
994514b5d45Schristos 		}
995514b5d45Schristos 		debug2_f("loaded %zu certificates", ncerts);
996aa36fcacSchristos 		if (update_card(agent_fd, !deleting, pkcs11provider,
997514b5d45Schristos 		    qflag, key_only, cert_only,
998514b5d45Schristos 		    dest_constraints, ndest_constraints,
999514b5d45Schristos 		    certs, ncerts) == -1)
1000ca32bd8dSchristos 			ret = 1;
1001ca32bd8dSchristos 		goto done;
1002ca32bd8dSchristos 	}
1003ed75d7a8Schristos 	if (do_download) {
1004ed75d7a8Schristos 		if (skprovider == NULL)
1005ed75d7a8Schristos 			fatal("Cannot download keys without provider");
1006a03ec00cSchristos 		if (load_resident_keys(agent_fd, skprovider, qflag,
1007a03ec00cSchristos 		    dest_constraints, ndest_constraints) != 0)
1008ed75d7a8Schristos 			ret = 1;
1009ed75d7a8Schristos 		goto done;
1010ed75d7a8Schristos 	}
1011ca32bd8dSchristos 	if (argc == 0) {
1012e4d43b82Schristos 		char buf[PATH_MAX];
1013ca32bd8dSchristos 		struct passwd *pw;
1014ca32bd8dSchristos 		struct stat st;
1015ca32bd8dSchristos 		int count = 0;
1016ca32bd8dSchristos 
1017ca32bd8dSchristos 		if ((pw = getpwuid(getuid())) == NULL) {
1018ca32bd8dSchristos 			fprintf(stderr, "No user found with uid %u\n",
1019ca32bd8dSchristos 			    (u_int)getuid());
1020ca32bd8dSchristos 			ret = 1;
1021ca32bd8dSchristos 			goto done;
1022ca32bd8dSchristos 		}
1023ca32bd8dSchristos 
1024ca32bd8dSchristos 		for (i = 0; default_files[i]; i++) {
1025ca32bd8dSchristos 			snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir,
1026ca32bd8dSchristos 			    default_files[i]);
1027cd4ada6aSchristos 			if (stat(buf, &st) == -1)
1028ca32bd8dSchristos 				continue;
1029514b5d45Schristos 			if (do_file(agent_fd, deleting, key_only, cert_only,
1030514b5d45Schristos 			    buf, qflag, skprovider,
1031a03ec00cSchristos 			    dest_constraints, ndest_constraints) == -1)
1032ca32bd8dSchristos 				ret = 1;
1033ca32bd8dSchristos 			else
1034ca32bd8dSchristos 				count++;
1035ca32bd8dSchristos 		}
1036ca32bd8dSchristos 		if (count == 0)
1037ca32bd8dSchristos 			ret = 1;
1038ca32bd8dSchristos 	} else {
1039ca32bd8dSchristos 		for (i = 0; i < argc; i++) {
1040514b5d45Schristos 			if (do_file(agent_fd, deleting, key_only, cert_only,
1041a03ec00cSchristos 			    argv[i], qflag, skprovider,
1042a03ec00cSchristos 			    dest_constraints, ndest_constraints) == -1)
1043ca32bd8dSchristos 				ret = 1;
1044ca32bd8dSchristos 		}
1045ca32bd8dSchristos 	}
1046ca32bd8dSchristos done:
1047ed75d7a8Schristos 	clear_pass();
1048e4d43b82Schristos 	ssh_close_authentication_socket(agent_fd);
1049ca32bd8dSchristos 	return ret;
1050ca32bd8dSchristos }
1051