1*ee116499SAntonio Huete Jimenez /* $OpenBSD: ssh-add.c,v 1.166 2022/06/18 02:17:16 dtucker Exp $ */ 218de8d7fSPeter Avalos /* 318de8d7fSPeter Avalos * Author: Tatu Ylonen <ylo@cs.hut.fi> 418de8d7fSPeter Avalos * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 518de8d7fSPeter Avalos * All rights reserved 618de8d7fSPeter Avalos * Adds an identity to the authentication server, or removes an identity. 718de8d7fSPeter Avalos * 818de8d7fSPeter Avalos * As far as I am concerned, the code I have written for this software 918de8d7fSPeter Avalos * can be used freely for any purpose. Any derived versions of this 1018de8d7fSPeter Avalos * software must be clearly marked as such, and if the derived work is 1118de8d7fSPeter Avalos * incompatible with the protocol description in the RFC file, it must be 1218de8d7fSPeter Avalos * called by a name other than "ssh" or "Secure Shell". 1318de8d7fSPeter Avalos * 1418de8d7fSPeter Avalos * SSH2 implementation, 1518de8d7fSPeter Avalos * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. 1618de8d7fSPeter Avalos * 1718de8d7fSPeter Avalos * Redistribution and use in source and binary forms, with or without 1818de8d7fSPeter Avalos * modification, are permitted provided that the following conditions 1918de8d7fSPeter Avalos * are met: 2018de8d7fSPeter Avalos * 1. Redistributions of source code must retain the above copyright 2118de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer. 2218de8d7fSPeter Avalos * 2. Redistributions in binary form must reproduce the above copyright 2318de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer in the 2418de8d7fSPeter Avalos * documentation and/or other materials provided with the distribution. 2518de8d7fSPeter Avalos * 2618de8d7fSPeter Avalos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 2718de8d7fSPeter Avalos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 2818de8d7fSPeter Avalos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2918de8d7fSPeter Avalos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 3018de8d7fSPeter Avalos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 3118de8d7fSPeter Avalos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 3218de8d7fSPeter Avalos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 3318de8d7fSPeter Avalos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 3418de8d7fSPeter Avalos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 3518de8d7fSPeter Avalos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 3618de8d7fSPeter Avalos */ 3718de8d7fSPeter Avalos 3818de8d7fSPeter Avalos #include "includes.h" 3918de8d7fSPeter Avalos 4018de8d7fSPeter Avalos #include <sys/types.h> 4118de8d7fSPeter Avalos #include <sys/stat.h> 4218de8d7fSPeter Avalos 430cbfa66cSDaniel Fojt #ifdef WITH_OPENSSL 4418de8d7fSPeter Avalos # include <openssl/evp.h> 4518de8d7fSPeter Avalos # include "openbsd-compat/openssl-compat.h" 460cbfa66cSDaniel Fojt #endif 4718de8d7fSPeter Avalos 48e9778795SPeter Avalos #include <errno.h> 4918de8d7fSPeter Avalos #include <fcntl.h> 5018de8d7fSPeter Avalos #include <pwd.h> 5118de8d7fSPeter Avalos #include <stdarg.h> 5218de8d7fSPeter Avalos #include <stdio.h> 5318de8d7fSPeter Avalos #include <stdlib.h> 5418de8d7fSPeter Avalos #include <string.h> 5518de8d7fSPeter Avalos #include <unistd.h> 56e9778795SPeter Avalos #include <limits.h> 5718de8d7fSPeter Avalos 5818de8d7fSPeter Avalos #include "xmalloc.h" 5918de8d7fSPeter Avalos #include "ssh.h" 6018de8d7fSPeter Avalos #include "log.h" 61e9778795SPeter Avalos #include "sshkey.h" 62e9778795SPeter Avalos #include "sshbuf.h" 6318de8d7fSPeter Avalos #include "authfd.h" 6418de8d7fSPeter Avalos #include "authfile.h" 6518de8d7fSPeter Avalos #include "pathnames.h" 6618de8d7fSPeter Avalos #include "misc.h" 6736e94dc5SPeter Avalos #include "ssherr.h" 68e9778795SPeter Avalos #include "digest.h" 690cbfa66cSDaniel Fojt #include "ssh-sk.h" 7050a69bb5SSascha Wildner #include "sk-api.h" 71*ee116499SAntonio Huete Jimenez #include "hostfile.h" 7218de8d7fSPeter Avalos 7318de8d7fSPeter Avalos /* argv0 */ 7418de8d7fSPeter Avalos extern char *__progname; 7518de8d7fSPeter Avalos 7618de8d7fSPeter Avalos /* Default files to add */ 7718de8d7fSPeter Avalos static char *default_files[] = { 78e9778795SPeter Avalos #ifdef WITH_OPENSSL 7918de8d7fSPeter Avalos _PATH_SSH_CLIENT_ID_RSA, 809f304aafSPeter Avalos #ifdef OPENSSL_HAS_ECC 819f304aafSPeter Avalos _PATH_SSH_CLIENT_ID_ECDSA, 820cbfa66cSDaniel Fojt _PATH_SSH_CLIENT_ID_ECDSA_SK, 839f304aafSPeter Avalos #endif 84e9778795SPeter Avalos #endif /* WITH_OPENSSL */ 8536e94dc5SPeter Avalos _PATH_SSH_CLIENT_ID_ED25519, 860cbfa66cSDaniel Fojt _PATH_SSH_CLIENT_ID_ED25519_SK, 87664f4763Szrj _PATH_SSH_CLIENT_ID_XMSS, 88*ee116499SAntonio Huete Jimenez _PATH_SSH_CLIENT_ID_DSA, 8918de8d7fSPeter Avalos NULL 9018de8d7fSPeter Avalos }; 9118de8d7fSPeter Avalos 92e9778795SPeter Avalos static int fingerprint_hash = SSH_FP_HASH_DEFAULT; 93e9778795SPeter Avalos 9418de8d7fSPeter Avalos /* Default lifetime (0 == forever) */ 9550a69bb5SSascha Wildner static int lifetime = 0; 9618de8d7fSPeter Avalos 9718de8d7fSPeter Avalos /* User has to confirm key use */ 9818de8d7fSPeter Avalos static int confirm = 0; 9918de8d7fSPeter Avalos 100664f4763Szrj /* Maximum number of signatures (XMSS) */ 101664f4763Szrj static u_int maxsign = 0; 102664f4763Szrj static u_int minleft = 0; 103664f4763Szrj 104e9778795SPeter Avalos /* we keep a cache of one passphrase */ 10518de8d7fSPeter Avalos static char *pass = NULL; 10618de8d7fSPeter Avalos static void 10718de8d7fSPeter Avalos clear_pass(void) 10818de8d7fSPeter Avalos { 10918de8d7fSPeter Avalos if (pass) { 1100cbfa66cSDaniel Fojt freezero(pass, strlen(pass)); 11118de8d7fSPeter Avalos pass = NULL; 11218de8d7fSPeter Avalos } 11318de8d7fSPeter Avalos } 11418de8d7fSPeter Avalos 11518de8d7fSPeter Avalos static int 11650a69bb5SSascha Wildner delete_one(int agent_fd, const struct sshkey *key, const char *comment, 11750a69bb5SSascha Wildner const char *path, int qflag) 11850a69bb5SSascha Wildner { 11950a69bb5SSascha Wildner int r; 12050a69bb5SSascha Wildner 12150a69bb5SSascha Wildner if ((r = ssh_remove_identity(agent_fd, key)) != 0) { 12250a69bb5SSascha Wildner fprintf(stderr, "Could not remove identity \"%s\": %s\n", 12350a69bb5SSascha Wildner path, ssh_err(r)); 12450a69bb5SSascha Wildner return r; 12550a69bb5SSascha Wildner } 12650a69bb5SSascha Wildner if (!qflag) { 12750a69bb5SSascha Wildner fprintf(stderr, "Identity removed: %s %s (%s)\n", path, 128*ee116499SAntonio Huete Jimenez sshkey_type(key), comment ? comment : "no comment"); 12950a69bb5SSascha Wildner } 13050a69bb5SSascha Wildner return 0; 13150a69bb5SSascha Wildner } 13250a69bb5SSascha Wildner 13350a69bb5SSascha Wildner static int 13450a69bb5SSascha Wildner delete_stdin(int agent_fd, int qflag) 13550a69bb5SSascha Wildner { 13650a69bb5SSascha Wildner char *line = NULL, *cp; 13750a69bb5SSascha Wildner size_t linesize = 0; 13850a69bb5SSascha Wildner struct sshkey *key = NULL; 13950a69bb5SSascha Wildner int lnum = 0, r, ret = -1; 14050a69bb5SSascha Wildner 14150a69bb5SSascha Wildner while (getline(&line, &linesize, stdin) != -1) { 14250a69bb5SSascha Wildner lnum++; 14350a69bb5SSascha Wildner sshkey_free(key); 14450a69bb5SSascha Wildner key = NULL; 14550a69bb5SSascha Wildner line[strcspn(line, "\n")] = '\0'; 14650a69bb5SSascha Wildner cp = line + strspn(line, " \t"); 14750a69bb5SSascha Wildner if (*cp == '#' || *cp == '\0') 14850a69bb5SSascha Wildner continue; 14950a69bb5SSascha Wildner if ((key = sshkey_new(KEY_UNSPEC)) == NULL) 15050a69bb5SSascha Wildner fatal_f("sshkey_new"); 15150a69bb5SSascha Wildner if ((r = sshkey_read(key, &cp)) != 0) { 15250a69bb5SSascha Wildner error_r(r, "(stdin):%d: invalid key", lnum); 15350a69bb5SSascha Wildner continue; 15450a69bb5SSascha Wildner } 15550a69bb5SSascha Wildner if (delete_one(agent_fd, key, cp, "(stdin)", qflag) == 0) 15650a69bb5SSascha Wildner ret = 0; 15750a69bb5SSascha Wildner } 15850a69bb5SSascha Wildner sshkey_free(key); 15950a69bb5SSascha Wildner free(line); 16050a69bb5SSascha Wildner return ret; 16150a69bb5SSascha Wildner } 16250a69bb5SSascha Wildner 16350a69bb5SSascha Wildner static int 164ce74bacaSMatthew Dillon delete_file(int agent_fd, const char *filename, int key_only, int qflag) 16518de8d7fSPeter Avalos { 166e9778795SPeter Avalos struct sshkey *public, *cert = NULL; 16736e94dc5SPeter Avalos char *certpath = NULL, *comment = NULL; 168e9778795SPeter Avalos int r, ret = -1; 16918de8d7fSPeter Avalos 17050a69bb5SSascha Wildner if (strcmp(filename, "-") == 0) 17150a69bb5SSascha Wildner return delete_stdin(agent_fd, qflag); 17250a69bb5SSascha Wildner 173e9778795SPeter Avalos if ((r = sshkey_load_public(filename, &public, &comment)) != 0) { 174e9778795SPeter Avalos printf("Bad key file %s: %s\n", filename, ssh_err(r)); 17518de8d7fSPeter Avalos return -1; 17618de8d7fSPeter Avalos } 17750a69bb5SSascha Wildner if (delete_one(agent_fd, public, comment, filename, qflag) == 0) 17818de8d7fSPeter Avalos ret = 0; 17918de8d7fSPeter Avalos 18036e94dc5SPeter Avalos if (key_only) 18136e94dc5SPeter Avalos goto out; 18236e94dc5SPeter Avalos 18336e94dc5SPeter Avalos /* Now try to delete the corresponding certificate too */ 18436e94dc5SPeter Avalos free(comment); 18536e94dc5SPeter Avalos comment = NULL; 18636e94dc5SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename); 187e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) { 188e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 18950a69bb5SSascha Wildner error_r(r, "Failed to load certificate \"%s\"", certpath); 19036e94dc5SPeter Avalos goto out; 191e9778795SPeter Avalos } 192e9778795SPeter Avalos 193e9778795SPeter Avalos if (!sshkey_equal_public(cert, public)) 19436e94dc5SPeter Avalos fatal("Certificate %s does not match private key %s", 19536e94dc5SPeter Avalos certpath, filename); 19636e94dc5SPeter Avalos 19750a69bb5SSascha Wildner if (delete_one(agent_fd, cert, comment, certpath, qflag) == 0) 19836e94dc5SPeter Avalos ret = 0; 19936e94dc5SPeter Avalos 20036e94dc5SPeter Avalos out: 201e9778795SPeter Avalos sshkey_free(cert); 202e9778795SPeter Avalos sshkey_free(public); 20336e94dc5SPeter Avalos free(certpath); 20436e94dc5SPeter Avalos free(comment); 20518de8d7fSPeter Avalos 20618de8d7fSPeter Avalos return ret; 20718de8d7fSPeter Avalos } 20818de8d7fSPeter Avalos 20918de8d7fSPeter Avalos /* Send a request to remove all identities. */ 21018de8d7fSPeter Avalos static int 211664f4763Szrj delete_all(int agent_fd, int qflag) 21218de8d7fSPeter Avalos { 21318de8d7fSPeter Avalos int ret = -1; 21418de8d7fSPeter Avalos 215ce74bacaSMatthew Dillon /* 216ce74bacaSMatthew Dillon * Since the agent might be forwarded, old or non-OpenSSH, when asked 217ce74bacaSMatthew Dillon * to remove all keys, attempt to remove both protocol v.1 and v.2 218ce74bacaSMatthew Dillon * keys. 219ce74bacaSMatthew Dillon */ 220e9778795SPeter Avalos if (ssh_remove_all_identities(agent_fd, 2) == 0) 22118de8d7fSPeter Avalos ret = 0; 222e9778795SPeter Avalos /* ignore error-code for ssh1 */ 223e9778795SPeter Avalos ssh_remove_all_identities(agent_fd, 1); 22418de8d7fSPeter Avalos 225664f4763Szrj if (ret != 0) 22618de8d7fSPeter Avalos fprintf(stderr, "Failed to remove all identities.\n"); 227664f4763Szrj else if (!qflag) 228664f4763Szrj fprintf(stderr, "All identities removed.\n"); 22918de8d7fSPeter Avalos 23018de8d7fSPeter Avalos return ret; 23118de8d7fSPeter Avalos } 23218de8d7fSPeter Avalos 23318de8d7fSPeter Avalos static int 2340cbfa66cSDaniel Fojt add_file(int agent_fd, const char *filename, int key_only, int qflag, 235*ee116499SAntonio Huete Jimenez const char *skprovider, struct dest_constraint **dest_constraints, 236*ee116499SAntonio Huete Jimenez size_t ndest_constraints) 23718de8d7fSPeter Avalos { 238e9778795SPeter Avalos struct sshkey *private, *cert; 23918de8d7fSPeter Avalos char *comment = NULL; 24099e85e0dSPeter Avalos char msg[1024], *certpath = NULL; 241e9778795SPeter Avalos int r, fd, ret = -1; 242664f4763Szrj size_t i; 243664f4763Szrj u_int32_t left; 244e9778795SPeter Avalos struct sshbuf *keyblob; 245664f4763Szrj struct ssh_identitylist *idlist; 24618de8d7fSPeter Avalos 2471c188a7fSPeter Avalos if (strcmp(filename, "-") == 0) { 2481c188a7fSPeter Avalos fd = STDIN_FILENO; 2491c188a7fSPeter Avalos filename = "(stdin)"; 2500cbfa66cSDaniel Fojt } else if ((fd = open(filename, O_RDONLY)) == -1) { 25118de8d7fSPeter Avalos perror(filename); 25218de8d7fSPeter Avalos return -1; 25318de8d7fSPeter Avalos } 25418de8d7fSPeter Avalos 25518de8d7fSPeter Avalos /* 25618de8d7fSPeter Avalos * Since we'll try to load a keyfile multiple times, permission errors 25718de8d7fSPeter Avalos * will occur multiple times, so check perms first and bail if wrong. 25818de8d7fSPeter Avalos */ 2591c188a7fSPeter Avalos if (fd != STDIN_FILENO) { 260e9778795SPeter Avalos if (sshkey_perm_ok(fd, filename) != 0) { 26118de8d7fSPeter Avalos close(fd); 26218de8d7fSPeter Avalos return -1; 2631c188a7fSPeter Avalos } 2641c188a7fSPeter Avalos } 2650cbfa66cSDaniel Fojt if ((r = sshbuf_load_fd(fd, &keyblob)) != 0) { 266e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 267e9778795SPeter Avalos filename, ssh_err(r)); 268e9778795SPeter Avalos sshbuf_free(keyblob); 2691c188a7fSPeter Avalos close(fd); 2701c188a7fSPeter Avalos return -1; 2711c188a7fSPeter Avalos } 2721c188a7fSPeter Avalos close(fd); 27318de8d7fSPeter Avalos 27418de8d7fSPeter Avalos /* At first, try empty passphrase */ 275e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, "", &private, 276e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 277e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 278e9778795SPeter Avalos filename, ssh_err(r)); 279e9778795SPeter Avalos goto fail_load; 280e9778795SPeter Avalos } 28136e94dc5SPeter Avalos /* try last */ 28236e94dc5SPeter Avalos if (private == NULL && pass != NULL) { 283e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private, 284e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 285e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 286e9778795SPeter Avalos filename, ssh_err(r)); 287e9778795SPeter Avalos goto fail_load; 28836e94dc5SPeter Avalos } 289e9778795SPeter Avalos } 29018de8d7fSPeter Avalos if (private == NULL) { 29118de8d7fSPeter Avalos /* clear passphrase since it did not work */ 29218de8d7fSPeter Avalos clear_pass(); 293e9778795SPeter Avalos snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ", 294e9778795SPeter Avalos filename, confirm ? " (will confirm each use)" : ""); 29518de8d7fSPeter Avalos for (;;) { 29618de8d7fSPeter Avalos pass = read_passphrase(msg, RP_ALLOW_STDIN); 297e9778795SPeter Avalos if (strcmp(pass, "") == 0) 298e9778795SPeter Avalos goto fail_load; 299e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass, 300e9778795SPeter Avalos &private, &comment)) == 0) 301e9778795SPeter Avalos break; 302e9778795SPeter Avalos else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 303e9778795SPeter Avalos fprintf(stderr, 304e9778795SPeter Avalos "Error loading key \"%s\": %s\n", 305e9778795SPeter Avalos filename, ssh_err(r)); 306e9778795SPeter Avalos fail_load: 30718de8d7fSPeter Avalos clear_pass(); 308e9778795SPeter Avalos sshbuf_free(keyblob); 30918de8d7fSPeter Avalos return -1; 31018de8d7fSPeter Avalos } 31118de8d7fSPeter Avalos clear_pass(); 31218de8d7fSPeter Avalos snprintf(msg, sizeof msg, 313e9778795SPeter Avalos "Bad passphrase, try again for %s%s: ", filename, 314e9778795SPeter Avalos confirm ? " (will confirm each use)" : ""); 31518de8d7fSPeter Avalos } 31618de8d7fSPeter Avalos } 317e9778795SPeter Avalos if (comment == NULL || *comment == '\0') 318e9778795SPeter Avalos comment = xstrdup(filename); 319e9778795SPeter Avalos sshbuf_free(keyblob); 32018de8d7fSPeter Avalos 321664f4763Szrj /* For XMSS */ 322664f4763Szrj if ((r = sshkey_set_filename(private, filename)) != 0) { 323664f4763Szrj fprintf(stderr, "Could not add filename to private key: %s (%s)\n", 324664f4763Szrj filename, comment); 325664f4763Szrj goto out; 326664f4763Szrj } 327664f4763Szrj if (maxsign && minleft && 328664f4763Szrj (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) { 329664f4763Szrj for (i = 0; i < idlist->nkeys; i++) { 330664f4763Szrj if (!sshkey_equal_public(idlist->keys[i], private)) 331664f4763Szrj continue; 332664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]); 333664f4763Szrj if (left < minleft) { 334664f4763Szrj fprintf(stderr, 335664f4763Szrj "Only %d signatures left.\n", left); 336664f4763Szrj break; 337664f4763Szrj } 338664f4763Szrj fprintf(stderr, "Skipping update: "); 339664f4763Szrj if (left == minleft) { 340664f4763Szrj fprintf(stderr, 341664f4763Szrj "required signatures left (%d).\n", left); 342664f4763Szrj } else { 343664f4763Szrj fprintf(stderr, 344664f4763Szrj "more signatures left (%d) than" 345664f4763Szrj " required (%d).\n", left, minleft); 346664f4763Szrj } 347664f4763Szrj ssh_free_identitylist(idlist); 348664f4763Szrj goto out; 349664f4763Szrj } 350664f4763Szrj ssh_free_identitylist(idlist); 351664f4763Szrj } 352664f4763Szrj 35350a69bb5SSascha Wildner if (sshkey_is_sk(private)) { 35450a69bb5SSascha Wildner if (skprovider == NULL) { 35550a69bb5SSascha Wildner fprintf(stderr, "Cannot load FIDO key %s " 3560cbfa66cSDaniel Fojt "without provider\n", filename); 3570cbfa66cSDaniel Fojt goto out; 3580cbfa66cSDaniel Fojt } 35950a69bb5SSascha Wildner } else { 36050a69bb5SSascha Wildner /* Don't send provider constraint for other keys */ 36150a69bb5SSascha Wildner skprovider = NULL; 36250a69bb5SSascha Wildner } 3630cbfa66cSDaniel Fojt 364e9778795SPeter Avalos if ((r = ssh_add_identity_constrained(agent_fd, private, comment, 365*ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider, 366*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) == 0) { 36718de8d7fSPeter Avalos ret = 0; 368664f4763Szrj if (!qflag) { 369664f4763Szrj fprintf(stderr, "Identity added: %s (%s)\n", 370664f4763Szrj filename, comment); 371664f4763Szrj if (lifetime != 0) { 37218de8d7fSPeter Avalos fprintf(stderr, 37350a69bb5SSascha Wildner "Lifetime set to %d seconds\n", lifetime); 374664f4763Szrj } 375664f4763Szrj if (confirm != 0) { 376664f4763Szrj fprintf(stderr, "The user must confirm " 377664f4763Szrj "each use of the key\n"); 378664f4763Szrj } 379664f4763Szrj } 38018de8d7fSPeter Avalos } else { 381e9778795SPeter Avalos fprintf(stderr, "Could not add identity \"%s\": %s\n", 382e9778795SPeter Avalos filename, ssh_err(r)); 38318de8d7fSPeter Avalos } 38418de8d7fSPeter Avalos 38599e85e0dSPeter Avalos /* Skip trying to load the cert if requested */ 38699e85e0dSPeter Avalos if (key_only) 38799e85e0dSPeter Avalos goto out; 388856ea928SPeter Avalos 389856ea928SPeter Avalos /* Now try to add the certificate flavour too */ 390856ea928SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename); 391e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) { 392e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 39350a69bb5SSascha Wildner error_r(r, "Failed to load certificate \"%s\"", certpath); 394856ea928SPeter Avalos goto out; 395e9778795SPeter Avalos } 396856ea928SPeter Avalos 397e9778795SPeter Avalos if (!sshkey_equal_public(cert, private)) { 398856ea928SPeter Avalos error("Certificate %s does not match private key %s", 399856ea928SPeter Avalos certpath, filename); 400e9778795SPeter Avalos sshkey_free(cert); 401856ea928SPeter Avalos goto out; 402856ea928SPeter Avalos } 403856ea928SPeter Avalos 404856ea928SPeter Avalos /* Graft with private bits */ 405e9778795SPeter Avalos if ((r = sshkey_to_certified(private)) != 0) { 40650a69bb5SSascha Wildner error_fr(r, "sshkey_to_certified"); 407e9778795SPeter Avalos sshkey_free(cert); 408856ea928SPeter Avalos goto out; 409856ea928SPeter Avalos } 410e9778795SPeter Avalos if ((r = sshkey_cert_copy(cert, private)) != 0) { 41150a69bb5SSascha Wildner error_fr(r, "sshkey_cert_copy"); 412e9778795SPeter Avalos sshkey_free(cert); 413e9778795SPeter Avalos goto out; 414e9778795SPeter Avalos } 415e9778795SPeter Avalos sshkey_free(cert); 416856ea928SPeter Avalos 417e9778795SPeter Avalos if ((r = ssh_add_identity_constrained(agent_fd, private, comment, 418*ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider, 419*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) != 0) { 42050a69bb5SSascha Wildner error_r(r, "Certificate %s (%s) add failed", certpath, 42150a69bb5SSascha Wildner private->cert->key_id); 422e9778795SPeter Avalos goto out; 423856ea928SPeter Avalos } 424664f4763Szrj /* success */ 425664f4763Szrj if (!qflag) { 426856ea928SPeter Avalos fprintf(stderr, "Certificate added: %s (%s)\n", certpath, 427856ea928SPeter Avalos private->cert->key_id); 428664f4763Szrj if (lifetime != 0) { 42950a69bb5SSascha Wildner fprintf(stderr, "Lifetime set to %d seconds\n", 430664f4763Szrj lifetime); 431664f4763Szrj } 432664f4763Szrj if (confirm != 0) { 433664f4763Szrj fprintf(stderr, "The user must confirm each use " 434664f4763Szrj "of the key\n"); 435664f4763Szrj } 436664f4763Szrj } 437664f4763Szrj 438856ea928SPeter Avalos out: 43936e94dc5SPeter Avalos free(certpath); 44036e94dc5SPeter Avalos free(comment); 441e9778795SPeter Avalos sshkey_free(private); 44218de8d7fSPeter Avalos 44318de8d7fSPeter Avalos return ret; 44418de8d7fSPeter Avalos } 44518de8d7fSPeter Avalos 44618de8d7fSPeter Avalos static int 447*ee116499SAntonio Huete Jimenez update_card(int agent_fd, int add, const char *id, int qflag, 448*ee116499SAntonio Huete Jimenez struct dest_constraint **dest_constraints, size_t ndest_constraints) 44918de8d7fSPeter Avalos { 45036e94dc5SPeter Avalos char *pin = NULL; 451e9778795SPeter Avalos int r, ret = -1; 45218de8d7fSPeter Avalos 45336e94dc5SPeter Avalos if (add) { 45436e94dc5SPeter Avalos if ((pin = read_passphrase("Enter passphrase for PKCS#11: ", 45536e94dc5SPeter Avalos RP_ALLOW_STDIN)) == NULL) 45618de8d7fSPeter Avalos return -1; 45736e94dc5SPeter Avalos } 45818de8d7fSPeter Avalos 459e9778795SPeter Avalos if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin, 460*ee116499SAntonio Huete Jimenez lifetime, confirm, dest_constraints, ndest_constraints)) == 0) { 461664f4763Szrj ret = 0; 462664f4763Szrj if (!qflag) { 46318de8d7fSPeter Avalos fprintf(stderr, "Card %s: %s\n", 46418de8d7fSPeter Avalos add ? "added" : "removed", id); 465664f4763Szrj } 46618de8d7fSPeter Avalos } else { 467e9778795SPeter Avalos fprintf(stderr, "Could not %s card \"%s\": %s\n", 468e9778795SPeter Avalos add ? "add" : "remove", id, ssh_err(r)); 46918de8d7fSPeter Avalos ret = -1; 47018de8d7fSPeter Avalos } 47136e94dc5SPeter Avalos free(pin); 47218de8d7fSPeter Avalos return ret; 47318de8d7fSPeter Avalos } 47418de8d7fSPeter Avalos 47518de8d7fSPeter Avalos static int 476664f4763Szrj test_key(int agent_fd, const char *filename) 477664f4763Szrj { 478664f4763Szrj struct sshkey *key = NULL; 479664f4763Szrj u_char *sig = NULL; 480664f4763Szrj size_t slen = 0; 481664f4763Szrj int r, ret = -1; 482664f4763Szrj char data[1024]; 483664f4763Szrj 484664f4763Szrj if ((r = sshkey_load_public(filename, &key, NULL)) != 0) { 48550a69bb5SSascha Wildner error_r(r, "Couldn't read public key %s", filename); 486664f4763Szrj return -1; 487664f4763Szrj } 488664f4763Szrj arc4random_buf(data, sizeof(data)); 489664f4763Szrj if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data), 490664f4763Szrj NULL, 0)) != 0) { 49150a69bb5SSascha Wildner error_r(r, "Agent signature failed for %s", filename); 492664f4763Szrj goto done; 493664f4763Szrj } 494664f4763Szrj if ((r = sshkey_verify(key, sig, slen, data, sizeof(data), 4950cbfa66cSDaniel Fojt NULL, 0, NULL)) != 0) { 49650a69bb5SSascha Wildner error_r(r, "Signature verification failed for %s", filename); 497664f4763Szrj goto done; 498664f4763Szrj } 499664f4763Szrj /* success */ 500664f4763Szrj ret = 0; 501664f4763Szrj done: 502664f4763Szrj free(sig); 503664f4763Szrj sshkey_free(key); 504664f4763Szrj return ret; 505664f4763Szrj } 506664f4763Szrj 507664f4763Szrj static int 508e9778795SPeter Avalos list_identities(int agent_fd, int do_fp) 50918de8d7fSPeter Avalos { 510e9778795SPeter Avalos char *fp; 511ce74bacaSMatthew Dillon int r; 512e9778795SPeter Avalos struct ssh_identitylist *idlist; 513664f4763Szrj u_int32_t left; 514e9778795SPeter Avalos size_t i; 51518de8d7fSPeter Avalos 516ce74bacaSMatthew Dillon if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) { 517e9778795SPeter Avalos if (r != SSH_ERR_AGENT_NO_IDENTITIES) 518ce74bacaSMatthew Dillon fprintf(stderr, "error fetching identities: %s\n", 519ce74bacaSMatthew Dillon ssh_err(r)); 520ce74bacaSMatthew Dillon else 521ce74bacaSMatthew Dillon printf("The agent has no identities.\n"); 522ce74bacaSMatthew Dillon return -1; 523e9778795SPeter Avalos } 524e9778795SPeter Avalos for (i = 0; i < idlist->nkeys; i++) { 52518de8d7fSPeter Avalos if (do_fp) { 526e9778795SPeter Avalos fp = sshkey_fingerprint(idlist->keys[i], 527e9778795SPeter Avalos fingerprint_hash, SSH_FP_DEFAULT); 528ce74bacaSMatthew Dillon printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]), 529ce74bacaSMatthew Dillon fp == NULL ? "(null)" : fp, idlist->comments[i], 530e9778795SPeter Avalos sshkey_type(idlist->keys[i])); 53136e94dc5SPeter Avalos free(fp); 53218de8d7fSPeter Avalos } else { 533ce74bacaSMatthew Dillon if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) { 534e9778795SPeter Avalos fprintf(stderr, "sshkey_write: %s\n", 535e9778795SPeter Avalos ssh_err(r)); 536e9778795SPeter Avalos continue; 53718de8d7fSPeter Avalos } 538664f4763Szrj fprintf(stdout, " %s", idlist->comments[i]); 539664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]); 540664f4763Szrj if (left > 0) 541664f4763Szrj fprintf(stdout, 542664f4763Szrj " [signatures left %d]", left); 543664f4763Szrj fprintf(stdout, "\n"); 54418de8d7fSPeter Avalos } 54518de8d7fSPeter Avalos } 546e9778795SPeter Avalos ssh_free_identitylist(idlist); 54718de8d7fSPeter Avalos return 0; 54818de8d7fSPeter Avalos } 54918de8d7fSPeter Avalos 55018de8d7fSPeter Avalos static int 551e9778795SPeter Avalos lock_agent(int agent_fd, int lock) 55218de8d7fSPeter Avalos { 55318de8d7fSPeter Avalos char prompt[100], *p1, *p2; 554e9778795SPeter Avalos int r, passok = 1, ret = -1; 55518de8d7fSPeter Avalos 55618de8d7fSPeter Avalos strlcpy(prompt, "Enter lock password: ", sizeof(prompt)); 55718de8d7fSPeter Avalos p1 = read_passphrase(prompt, RP_ALLOW_STDIN); 55818de8d7fSPeter Avalos if (lock) { 55918de8d7fSPeter Avalos strlcpy(prompt, "Again: ", sizeof prompt); 56018de8d7fSPeter Avalos p2 = read_passphrase(prompt, RP_ALLOW_STDIN); 56118de8d7fSPeter Avalos if (strcmp(p1, p2) != 0) { 56218de8d7fSPeter Avalos fprintf(stderr, "Passwords do not match.\n"); 56318de8d7fSPeter Avalos passok = 0; 56418de8d7fSPeter Avalos } 5650cbfa66cSDaniel Fojt freezero(p2, strlen(p2)); 56618de8d7fSPeter Avalos } 567e9778795SPeter Avalos if (passok) { 568e9778795SPeter Avalos if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) { 56918de8d7fSPeter Avalos fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un"); 57018de8d7fSPeter Avalos ret = 0; 571e9778795SPeter Avalos } else { 572e9778795SPeter Avalos fprintf(stderr, "Failed to %slock agent: %s\n", 573e9778795SPeter Avalos lock ? "" : "un", ssh_err(r)); 574e9778795SPeter Avalos } 575e9778795SPeter Avalos } 5760cbfa66cSDaniel Fojt freezero(p1, strlen(p1)); 57718de8d7fSPeter Avalos return (ret); 57818de8d7fSPeter Avalos } 57918de8d7fSPeter Avalos 58018de8d7fSPeter Avalos static int 581*ee116499SAntonio Huete Jimenez load_resident_keys(int agent_fd, const char *skprovider, int qflag, 582*ee116499SAntonio Huete Jimenez struct dest_constraint **dest_constraints, size_t ndest_constraints) 5830cbfa66cSDaniel Fojt { 584*ee116499SAntonio Huete Jimenez struct sshsk_resident_key **srks; 585*ee116499SAntonio Huete Jimenez size_t nsrks, i; 586*ee116499SAntonio Huete Jimenez struct sshkey *key; 5870cbfa66cSDaniel Fojt int r, ok = 0; 5880cbfa66cSDaniel Fojt char *fp; 5890cbfa66cSDaniel Fojt 5900cbfa66cSDaniel Fojt pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN); 591*ee116499SAntonio Huete Jimenez if ((r = sshsk_load_resident(skprovider, NULL, pass, 0, 592*ee116499SAntonio Huete Jimenez &srks, &nsrks)) != 0) { 59350a69bb5SSascha Wildner error_r(r, "Unable to load resident keys"); 5940cbfa66cSDaniel Fojt return r; 5950cbfa66cSDaniel Fojt } 596*ee116499SAntonio Huete Jimenez for (i = 0; i < nsrks; i++) { 597*ee116499SAntonio Huete Jimenez key = srks[i]->key; 598*ee116499SAntonio Huete Jimenez if ((fp = sshkey_fingerprint(key, 5990cbfa66cSDaniel Fojt fingerprint_hash, SSH_FP_DEFAULT)) == NULL) 60050a69bb5SSascha Wildner fatal_f("sshkey_fingerprint failed"); 601*ee116499SAntonio Huete Jimenez if ((r = ssh_add_identity_constrained(agent_fd, key, "", 602*ee116499SAntonio Huete Jimenez lifetime, confirm, maxsign, skprovider, 603*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints)) != 0) { 6040cbfa66cSDaniel Fojt error("Unable to add key %s %s", 605*ee116499SAntonio Huete Jimenez sshkey_type(key), fp); 6060cbfa66cSDaniel Fojt free(fp); 6070cbfa66cSDaniel Fojt ok = r; 6080cbfa66cSDaniel Fojt continue; 6090cbfa66cSDaniel Fojt } 6100cbfa66cSDaniel Fojt if (ok == 0) 6110cbfa66cSDaniel Fojt ok = 1; 6120cbfa66cSDaniel Fojt if (!qflag) { 6130cbfa66cSDaniel Fojt fprintf(stderr, "Resident identity added: %s %s\n", 614*ee116499SAntonio Huete Jimenez sshkey_type(key), fp); 6150cbfa66cSDaniel Fojt if (lifetime != 0) { 6160cbfa66cSDaniel Fojt fprintf(stderr, 61750a69bb5SSascha Wildner "Lifetime set to %d seconds\n", lifetime); 6180cbfa66cSDaniel Fojt } 6190cbfa66cSDaniel Fojt if (confirm != 0) { 6200cbfa66cSDaniel Fojt fprintf(stderr, "The user must confirm " 6210cbfa66cSDaniel Fojt "each use of the key\n"); 6220cbfa66cSDaniel Fojt } 6230cbfa66cSDaniel Fojt } 6240cbfa66cSDaniel Fojt free(fp); 6250cbfa66cSDaniel Fojt } 626*ee116499SAntonio Huete Jimenez sshsk_free_resident_keys(srks, nsrks); 627*ee116499SAntonio Huete Jimenez if (nsrks == 0) 6280cbfa66cSDaniel Fojt return SSH_ERR_KEY_NOT_FOUND; 6290cbfa66cSDaniel Fojt return ok == 1 ? 0 : ok; 6300cbfa66cSDaniel Fojt } 6310cbfa66cSDaniel Fojt 6320cbfa66cSDaniel Fojt static int 6330cbfa66cSDaniel Fojt do_file(int agent_fd, int deleting, int key_only, char *file, int qflag, 634*ee116499SAntonio Huete Jimenez const char *skprovider, struct dest_constraint **dest_constraints, 635*ee116499SAntonio Huete Jimenez size_t ndest_constraints) 63618de8d7fSPeter Avalos { 63718de8d7fSPeter Avalos if (deleting) { 638ce74bacaSMatthew Dillon if (delete_file(agent_fd, file, key_only, qflag) == -1) 63918de8d7fSPeter Avalos return -1; 64018de8d7fSPeter Avalos } else { 641*ee116499SAntonio Huete Jimenez if (add_file(agent_fd, file, key_only, qflag, skprovider, 642*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) == -1) 64318de8d7fSPeter Avalos return -1; 64418de8d7fSPeter Avalos } 64518de8d7fSPeter Avalos return 0; 64618de8d7fSPeter Avalos } 64718de8d7fSPeter Avalos 648*ee116499SAntonio Huete Jimenez /* Append string 's' to a NULL-terminated array of strings */ 649*ee116499SAntonio Huete Jimenez static void 650*ee116499SAntonio Huete Jimenez stringlist_append(char ***listp, const char *s) 651*ee116499SAntonio Huete Jimenez { 652*ee116499SAntonio Huete Jimenez size_t i = 0; 653*ee116499SAntonio Huete Jimenez 654*ee116499SAntonio Huete Jimenez if (*listp == NULL) 655*ee116499SAntonio Huete Jimenez *listp = xcalloc(2, sizeof(**listp)); 656*ee116499SAntonio Huete Jimenez else { 657*ee116499SAntonio Huete Jimenez for (i = 0; (*listp)[i] != NULL; i++) 658*ee116499SAntonio Huete Jimenez ; /* count */ 659*ee116499SAntonio Huete Jimenez *listp = xrecallocarray(*listp, i + 1, i + 2, sizeof(**listp)); 660*ee116499SAntonio Huete Jimenez } 661*ee116499SAntonio Huete Jimenez (*listp)[i] = xstrdup(s); 662*ee116499SAntonio Huete Jimenez } 663*ee116499SAntonio Huete Jimenez 664*ee116499SAntonio Huete Jimenez static void 665*ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch, 666*ee116499SAntonio Huete Jimenez char **hostkey_files) 667*ee116499SAntonio Huete Jimenez { 668*ee116499SAntonio Huete Jimenez char *user = NULL, *host, *os, *path; 669*ee116499SAntonio Huete Jimenez size_t i; 670*ee116499SAntonio Huete Jimenez struct hostkeys *hostkeys; 671*ee116499SAntonio Huete Jimenez const struct hostkey_entry *hke; 672*ee116499SAntonio Huete Jimenez int r, want_ca; 673*ee116499SAntonio Huete Jimenez 674*ee116499SAntonio Huete Jimenez memset(dch, '\0', sizeof(*dch)); 675*ee116499SAntonio Huete Jimenez os = xstrdup(s); 676*ee116499SAntonio Huete Jimenez if ((host = strchr(os, '@')) == NULL) 677*ee116499SAntonio Huete Jimenez host = os; 678*ee116499SAntonio Huete Jimenez else { 679*ee116499SAntonio Huete Jimenez *host++ = '\0'; 680*ee116499SAntonio Huete Jimenez user = os; 681*ee116499SAntonio Huete Jimenez } 682*ee116499SAntonio Huete Jimenez cleanhostname(host); 683*ee116499SAntonio Huete Jimenez /* Trivial case: username@ (all hosts) */ 684*ee116499SAntonio Huete Jimenez if (*host == '\0') { 685*ee116499SAntonio Huete Jimenez if (user == NULL) { 686*ee116499SAntonio Huete Jimenez fatal("Invalid key destination constraint \"%s\": " 687*ee116499SAntonio Huete Jimenez "does not specify user or host", s); 688*ee116499SAntonio Huete Jimenez } 689*ee116499SAntonio Huete Jimenez dch->user = xstrdup(user); 690*ee116499SAntonio Huete Jimenez /* other fields left blank */ 691*ee116499SAntonio Huete Jimenez free(os); 692*ee116499SAntonio Huete Jimenez return; 693*ee116499SAntonio Huete Jimenez } 694*ee116499SAntonio Huete Jimenez if (hostkey_files == NULL) 695*ee116499SAntonio Huete Jimenez fatal_f("no hostkey files"); 696*ee116499SAntonio Huete Jimenez /* Otherwise we need to look up the keys for this hostname */ 697*ee116499SAntonio Huete Jimenez hostkeys = init_hostkeys(); 698*ee116499SAntonio Huete Jimenez for (i = 0; hostkey_files[i]; i++) { 699*ee116499SAntonio Huete Jimenez path = tilde_expand_filename(hostkey_files[i], getuid()); 700*ee116499SAntonio Huete Jimenez debug2_f("looking up host keys for \"%s\" in %s", host, path); 701*ee116499SAntonio Huete Jimenez load_hostkeys(hostkeys, host, path, 0); 702*ee116499SAntonio Huete Jimenez free(path); 703*ee116499SAntonio Huete Jimenez } 704*ee116499SAntonio Huete Jimenez dch->user = user == NULL ? NULL : xstrdup(user); 705*ee116499SAntonio Huete Jimenez dch->hostname = xstrdup(host); 706*ee116499SAntonio Huete Jimenez for (i = 0; i < hostkeys->num_entries; i++) { 707*ee116499SAntonio Huete Jimenez hke = hostkeys->entries + i; 708*ee116499SAntonio Huete Jimenez want_ca = hke->marker == MRK_CA; 709*ee116499SAntonio Huete Jimenez if (hke->marker != MRK_NONE && !want_ca) 710*ee116499SAntonio Huete Jimenez continue; 711*ee116499SAntonio Huete Jimenez debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u", 712*ee116499SAntonio Huete Jimenez user == NULL ? "": user, user == NULL ? "" : "@", 713*ee116499SAntonio Huete Jimenez host, sshkey_type(hke->key), want_ca ? "CA " : "", 714*ee116499SAntonio Huete Jimenez hke->file, hke->line, dch->nkeys); 715*ee116499SAntonio Huete Jimenez dch->keys = xrecallocarray(dch->keys, dch->nkeys, 716*ee116499SAntonio Huete Jimenez dch->nkeys + 1, sizeof(*dch->keys)); 717*ee116499SAntonio Huete Jimenez dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys, 718*ee116499SAntonio Huete Jimenez dch->nkeys + 1, sizeof(*dch->key_is_ca)); 719*ee116499SAntonio Huete Jimenez if ((r = sshkey_from_private(hke->key, 720*ee116499SAntonio Huete Jimenez &(dch->keys[dch->nkeys]))) != 0) 721*ee116499SAntonio Huete Jimenez fatal_fr(r, "sshkey_from_private"); 722*ee116499SAntonio Huete Jimenez dch->key_is_ca[dch->nkeys] = want_ca; 723*ee116499SAntonio Huete Jimenez dch->nkeys++; 724*ee116499SAntonio Huete Jimenez } 725*ee116499SAntonio Huete Jimenez if (dch->nkeys == 0) 726*ee116499SAntonio Huete Jimenez fatal("No host keys found for destination \"%s\"", host); 727*ee116499SAntonio Huete Jimenez free_hostkeys(hostkeys); 728*ee116499SAntonio Huete Jimenez free(os); 729*ee116499SAntonio Huete Jimenez return; 730*ee116499SAntonio Huete Jimenez } 731*ee116499SAntonio Huete Jimenez 732*ee116499SAntonio Huete Jimenez static void 733*ee116499SAntonio Huete Jimenez parse_dest_constraint(const char *s, struct dest_constraint ***dcp, 734*ee116499SAntonio Huete Jimenez size_t *ndcp, char **hostkey_files) 735*ee116499SAntonio Huete Jimenez { 736*ee116499SAntonio Huete Jimenez struct dest_constraint *dc; 737*ee116499SAntonio Huete Jimenez char *os, *cp; 738*ee116499SAntonio Huete Jimenez 739*ee116499SAntonio Huete Jimenez dc = xcalloc(1, sizeof(*dc)); 740*ee116499SAntonio Huete Jimenez os = xstrdup(s); 741*ee116499SAntonio Huete Jimenez if ((cp = strchr(os, '>')) == NULL) { 742*ee116499SAntonio Huete Jimenez /* initial hop; no 'from' hop specified */ 743*ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(os, &dc->to, hostkey_files); 744*ee116499SAntonio Huete Jimenez } else { 745*ee116499SAntonio Huete Jimenez /* two hops specified */ 746*ee116499SAntonio Huete Jimenez *(cp++) = '\0'; 747*ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(os, &dc->from, hostkey_files); 748*ee116499SAntonio Huete Jimenez parse_dest_constraint_hop(cp, &dc->to, hostkey_files); 749*ee116499SAntonio Huete Jimenez if (dc->from.user != NULL) { 750*ee116499SAntonio Huete Jimenez fatal("Invalid key constraint %s: cannot specify " 751*ee116499SAntonio Huete Jimenez "user on 'from' host", os); 752*ee116499SAntonio Huete Jimenez } 753*ee116499SAntonio Huete Jimenez } 754*ee116499SAntonio Huete Jimenez /* XXX eliminate or error on duplicates */ 755*ee116499SAntonio Huete Jimenez debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp, 756*ee116499SAntonio Huete Jimenez dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "", 757*ee116499SAntonio Huete Jimenez dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys, 758*ee116499SAntonio Huete Jimenez dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "", 759*ee116499SAntonio Huete Jimenez dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys); 760*ee116499SAntonio Huete Jimenez *dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp)); 761*ee116499SAntonio Huete Jimenez (*dcp)[(*ndcp)++] = dc; 762*ee116499SAntonio Huete Jimenez free(os); 763*ee116499SAntonio Huete Jimenez } 764*ee116499SAntonio Huete Jimenez 765*ee116499SAntonio Huete Jimenez 76618de8d7fSPeter Avalos static void 76718de8d7fSPeter Avalos usage(void) 76818de8d7fSPeter Avalos { 7690cbfa66cSDaniel Fojt fprintf(stderr, 770*ee116499SAntonio Huete Jimenez "usage: ssh-add [-cDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n" 771*ee116499SAntonio Huete Jimenez " [-h destination_constraint] [-S provider] [-t life]\n" 7720cbfa66cSDaniel Fojt #ifdef WITH_XMSS 7730cbfa66cSDaniel Fojt " [-M maxsign] [-m minleft]\n" 7740cbfa66cSDaniel Fojt #endif 7750cbfa66cSDaniel Fojt " [file ...]\n" 7760cbfa66cSDaniel Fojt " ssh-add -s pkcs11\n" 7770cbfa66cSDaniel Fojt " ssh-add -e pkcs11\n" 7780cbfa66cSDaniel Fojt " ssh-add -T pubkey ...\n" 7790cbfa66cSDaniel Fojt ); 78018de8d7fSPeter Avalos } 78118de8d7fSPeter Avalos 78218de8d7fSPeter Avalos int 78318de8d7fSPeter Avalos main(int argc, char **argv) 78418de8d7fSPeter Avalos { 78518de8d7fSPeter Avalos extern char *optarg; 78618de8d7fSPeter Avalos extern int optind; 787e9778795SPeter Avalos int agent_fd; 7880cbfa66cSDaniel Fojt char *pkcs11provider = NULL, *skprovider = NULL; 789*ee116499SAntonio Huete Jimenez char **dest_constraint_strings = NULL, **hostkey_files = NULL; 7900cbfa66cSDaniel Fojt int r, i, ch, deleting = 0, ret = 0, key_only = 0, do_download = 0; 791664f4763Szrj int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0; 792664f4763Szrj SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 793664f4763Szrj LogLevel log_level = SYSLOG_LEVEL_INFO; 794*ee116499SAntonio Huete Jimenez struct dest_constraint **dest_constraints = NULL; 795*ee116499SAntonio Huete Jimenez size_t ndest_constraints = 0; 79618de8d7fSPeter Avalos 79718de8d7fSPeter Avalos /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 79818de8d7fSPeter Avalos sanitise_stdfd(); 79918de8d7fSPeter Avalos 80018de8d7fSPeter Avalos __progname = ssh_get_progname(argv[0]); 80118de8d7fSPeter Avalos seed_rng(); 80218de8d7fSPeter Avalos 803664f4763Szrj log_init(__progname, log_level, log_facility, 1); 80418de8d7fSPeter Avalos 805e9778795SPeter Avalos setvbuf(stdout, NULL, _IOLBF, 0); 80636e94dc5SPeter Avalos 807e9778795SPeter Avalos /* First, get a connection to the authentication agent. */ 808e9778795SPeter Avalos switch (r = ssh_get_authentication_socket(&agent_fd)) { 809e9778795SPeter Avalos case 0: 810e9778795SPeter Avalos break; 811e9778795SPeter Avalos case SSH_ERR_AGENT_NOT_PRESENT: 812e9778795SPeter Avalos fprintf(stderr, "Could not open a connection to your " 813e9778795SPeter Avalos "authentication agent.\n"); 814e9778795SPeter Avalos exit(2); 815e9778795SPeter Avalos default: 816e9778795SPeter Avalos fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r)); 81718de8d7fSPeter Avalos exit(2); 81818de8d7fSPeter Avalos } 819e9778795SPeter Avalos 8200cbfa66cSDaniel Fojt skprovider = getenv("SSH_SK_PROVIDER"); 8210cbfa66cSDaniel Fojt 822*ee116499SAntonio Huete Jimenez while ((ch = getopt(argc, argv, "vkKlLcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { 82318de8d7fSPeter Avalos switch (ch) { 824664f4763Szrj case 'v': 825664f4763Szrj if (log_level == SYSLOG_LEVEL_INFO) 826664f4763Szrj log_level = SYSLOG_LEVEL_DEBUG1; 827664f4763Szrj else if (log_level < SYSLOG_LEVEL_DEBUG3) 828664f4763Szrj log_level++; 829664f4763Szrj break; 830e9778795SPeter Avalos case 'E': 831e9778795SPeter Avalos fingerprint_hash = ssh_digest_alg_by_name(optarg); 832e9778795SPeter Avalos if (fingerprint_hash == -1) 833e9778795SPeter Avalos fatal("Invalid hash algorithm \"%s\"", optarg); 834e9778795SPeter Avalos break; 835*ee116499SAntonio Huete Jimenez case 'H': 836*ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, optarg); 837*ee116499SAntonio Huete Jimenez break; 838*ee116499SAntonio Huete Jimenez case 'h': 839*ee116499SAntonio Huete Jimenez stringlist_append(&dest_constraint_strings, optarg); 840*ee116499SAntonio Huete Jimenez break; 84199e85e0dSPeter Avalos case 'k': 84299e85e0dSPeter Avalos key_only = 1; 84399e85e0dSPeter Avalos break; 8440cbfa66cSDaniel Fojt case 'K': 8450cbfa66cSDaniel Fojt do_download = 1; 8460cbfa66cSDaniel Fojt break; 84718de8d7fSPeter Avalos case 'l': 84818de8d7fSPeter Avalos case 'L': 849e9778795SPeter Avalos if (lflag != 0) 850e9778795SPeter Avalos fatal("-%c flag already specified", lflag); 851e9778795SPeter Avalos lflag = ch; 852e9778795SPeter Avalos break; 85318de8d7fSPeter Avalos case 'x': 85418de8d7fSPeter Avalos case 'X': 855e9778795SPeter Avalos if (xflag != 0) 856e9778795SPeter Avalos fatal("-%c flag already specified", xflag); 857e9778795SPeter Avalos xflag = ch; 858e9778795SPeter Avalos break; 85918de8d7fSPeter Avalos case 'c': 86018de8d7fSPeter Avalos confirm = 1; 86118de8d7fSPeter Avalos break; 862664f4763Szrj case 'm': 863664f4763Szrj minleft = (int)strtonum(optarg, 1, UINT_MAX, NULL); 864664f4763Szrj if (minleft == 0) { 865664f4763Szrj usage(); 866664f4763Szrj ret = 1; 867664f4763Szrj goto done; 868664f4763Szrj } 869664f4763Szrj break; 870664f4763Szrj case 'M': 871664f4763Szrj maxsign = (int)strtonum(optarg, 1, UINT_MAX, NULL); 872664f4763Szrj if (maxsign == 0) { 873664f4763Szrj usage(); 874664f4763Szrj ret = 1; 875664f4763Szrj goto done; 876664f4763Szrj } 877664f4763Szrj break; 87818de8d7fSPeter Avalos case 'd': 87918de8d7fSPeter Avalos deleting = 1; 88018de8d7fSPeter Avalos break; 88118de8d7fSPeter Avalos case 'D': 882e9778795SPeter Avalos Dflag = 1; 883e9778795SPeter Avalos break; 88418de8d7fSPeter Avalos case 's': 885856ea928SPeter Avalos pkcs11provider = optarg; 88618de8d7fSPeter Avalos break; 8870cbfa66cSDaniel Fojt case 'S': 8880cbfa66cSDaniel Fojt skprovider = optarg; 8890cbfa66cSDaniel Fojt break; 89018de8d7fSPeter Avalos case 'e': 89118de8d7fSPeter Avalos deleting = 1; 892856ea928SPeter Avalos pkcs11provider = optarg; 89318de8d7fSPeter Avalos break; 89418de8d7fSPeter Avalos case 't': 8950cbfa66cSDaniel Fojt if ((lifetime = convtime(optarg)) == -1 || 8960cbfa66cSDaniel Fojt lifetime < 0 || (u_long)lifetime > UINT32_MAX) { 89718de8d7fSPeter Avalos fprintf(stderr, "Invalid lifetime\n"); 89818de8d7fSPeter Avalos ret = 1; 89918de8d7fSPeter Avalos goto done; 90018de8d7fSPeter Avalos } 90118de8d7fSPeter Avalos break; 902ce74bacaSMatthew Dillon case 'q': 903ce74bacaSMatthew Dillon qflag = 1; 904ce74bacaSMatthew Dillon break; 905664f4763Szrj case 'T': 906664f4763Szrj Tflag = 1; 907664f4763Szrj break; 90818de8d7fSPeter Avalos default: 90918de8d7fSPeter Avalos usage(); 91018de8d7fSPeter Avalos ret = 1; 91118de8d7fSPeter Avalos goto done; 91218de8d7fSPeter Avalos } 91318de8d7fSPeter Avalos } 914664f4763Szrj log_init(__progname, log_level, log_facility, 1); 915e9778795SPeter Avalos 916e9778795SPeter Avalos if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1) 917e9778795SPeter Avalos fatal("Invalid combination of actions"); 918e9778795SPeter Avalos else if (xflag) { 919e9778795SPeter Avalos if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1) 920e9778795SPeter Avalos ret = 1; 921e9778795SPeter Avalos goto done; 922e9778795SPeter Avalos } else if (lflag) { 923e9778795SPeter Avalos if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1) 924e9778795SPeter Avalos ret = 1; 925e9778795SPeter Avalos goto done; 926e9778795SPeter Avalos } else if (Dflag) { 927664f4763Szrj if (delete_all(agent_fd, qflag) == -1) 928e9778795SPeter Avalos ret = 1; 929e9778795SPeter Avalos goto done; 930e9778795SPeter Avalos } 931e9778795SPeter Avalos 9320cbfa66cSDaniel Fojt #ifdef ENABLE_SK_INTERNAL 9330cbfa66cSDaniel Fojt if (skprovider == NULL) 9340cbfa66cSDaniel Fojt skprovider = "internal"; 9350cbfa66cSDaniel Fojt #endif 9360cbfa66cSDaniel Fojt 937*ee116499SAntonio Huete Jimenez if (hostkey_files == NULL) { 938*ee116499SAntonio Huete Jimenez /* use defaults from readconf.c */ 939*ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE); 940*ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2); 941*ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE); 942*ee116499SAntonio Huete Jimenez stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2); 943*ee116499SAntonio Huete Jimenez } 944*ee116499SAntonio Huete Jimenez if (dest_constraint_strings != NULL) { 945*ee116499SAntonio Huete Jimenez for (i = 0; dest_constraint_strings[i] != NULL; i++) { 946*ee116499SAntonio Huete Jimenez parse_dest_constraint(dest_constraint_strings[i], 947*ee116499SAntonio Huete Jimenez &dest_constraints, &ndest_constraints, hostkey_files); 948*ee116499SAntonio Huete Jimenez } 949*ee116499SAntonio Huete Jimenez } 950*ee116499SAntonio Huete Jimenez 95118de8d7fSPeter Avalos argc -= optind; 95218de8d7fSPeter Avalos argv += optind; 953664f4763Szrj if (Tflag) { 954664f4763Szrj if (argc <= 0) 955664f4763Szrj fatal("no keys to test"); 956664f4763Szrj for (r = i = 0; i < argc; i++) 957664f4763Szrj r |= test_key(agent_fd, argv[i]); 958664f4763Szrj ret = r == 0 ? 0 : 1; 959664f4763Szrj goto done; 960664f4763Szrj } 961856ea928SPeter Avalos if (pkcs11provider != NULL) { 962664f4763Szrj if (update_card(agent_fd, !deleting, pkcs11provider, 963*ee116499SAntonio Huete Jimenez qflag, dest_constraints, ndest_constraints) == -1) 96418de8d7fSPeter Avalos ret = 1; 96518de8d7fSPeter Avalos goto done; 96618de8d7fSPeter Avalos } 9670cbfa66cSDaniel Fojt if (do_download) { 9680cbfa66cSDaniel Fojt if (skprovider == NULL) 9690cbfa66cSDaniel Fojt fatal("Cannot download keys without provider"); 970*ee116499SAntonio Huete Jimenez if (load_resident_keys(agent_fd, skprovider, qflag, 971*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) != 0) 9720cbfa66cSDaniel Fojt ret = 1; 9730cbfa66cSDaniel Fojt goto done; 9740cbfa66cSDaniel Fojt } 97518de8d7fSPeter Avalos if (argc == 0) { 976e9778795SPeter Avalos char buf[PATH_MAX]; 97718de8d7fSPeter Avalos struct passwd *pw; 97818de8d7fSPeter Avalos struct stat st; 97918de8d7fSPeter Avalos int count = 0; 98018de8d7fSPeter Avalos 98118de8d7fSPeter Avalos if ((pw = getpwuid(getuid())) == NULL) { 98218de8d7fSPeter Avalos fprintf(stderr, "No user found with uid %u\n", 98318de8d7fSPeter Avalos (u_int)getuid()); 98418de8d7fSPeter Avalos ret = 1; 98518de8d7fSPeter Avalos goto done; 98618de8d7fSPeter Avalos } 98718de8d7fSPeter Avalos 98818de8d7fSPeter Avalos for (i = 0; default_files[i]; i++) { 98918de8d7fSPeter Avalos snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, 99018de8d7fSPeter Avalos default_files[i]); 9910cbfa66cSDaniel Fojt if (stat(buf, &st) == -1) 99218de8d7fSPeter Avalos continue; 993ce74bacaSMatthew Dillon if (do_file(agent_fd, deleting, key_only, buf, 994*ee116499SAntonio Huete Jimenez qflag, skprovider, 995*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) == -1) 99618de8d7fSPeter Avalos ret = 1; 99718de8d7fSPeter Avalos else 99818de8d7fSPeter Avalos count++; 99918de8d7fSPeter Avalos } 100018de8d7fSPeter Avalos if (count == 0) 100118de8d7fSPeter Avalos ret = 1; 100218de8d7fSPeter Avalos } else { 100318de8d7fSPeter Avalos for (i = 0; i < argc; i++) { 1004e9778795SPeter Avalos if (do_file(agent_fd, deleting, key_only, 1005*ee116499SAntonio Huete Jimenez argv[i], qflag, skprovider, 1006*ee116499SAntonio Huete Jimenez dest_constraints, ndest_constraints) == -1) 100718de8d7fSPeter Avalos ret = 1; 100818de8d7fSPeter Avalos } 100918de8d7fSPeter Avalos } 101018de8d7fSPeter Avalos done: 10110cbfa66cSDaniel Fojt clear_pass(); 1012e9778795SPeter Avalos ssh_close_authentication_socket(agent_fd); 101318de8d7fSPeter Avalos return ret; 101418de8d7fSPeter Avalos } 1015