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