1*664f4763Szrj /* $OpenBSD: ssh-add.c,v 1.138 2019/01/21 12:53:35 djm Exp $ */ 218de8d7fSPeter Avalos /* 318de8d7fSPeter Avalos * Author: Tatu Ylonen <ylo@cs.hut.fi> 418de8d7fSPeter Avalos * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 518de8d7fSPeter Avalos * All rights reserved 618de8d7fSPeter Avalos * Adds an identity to the authentication server, or removes an identity. 718de8d7fSPeter Avalos * 818de8d7fSPeter Avalos * As far as I am concerned, the code I have written for this software 918de8d7fSPeter Avalos * can be used freely for any purpose. Any derived versions of this 1018de8d7fSPeter Avalos * software must be clearly marked as such, and if the derived work is 1118de8d7fSPeter Avalos * incompatible with the protocol description in the RFC file, it must be 1218de8d7fSPeter Avalos * called by a name other than "ssh" or "Secure Shell". 1318de8d7fSPeter Avalos * 1418de8d7fSPeter Avalos * SSH2 implementation, 1518de8d7fSPeter Avalos * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. 1618de8d7fSPeter Avalos * 1718de8d7fSPeter Avalos * Redistribution and use in source and binary forms, with or without 1818de8d7fSPeter Avalos * modification, are permitted provided that the following conditions 1918de8d7fSPeter Avalos * are met: 2018de8d7fSPeter Avalos * 1. Redistributions of source code must retain the above copyright 2118de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer. 2218de8d7fSPeter Avalos * 2. Redistributions in binary form must reproduce the above copyright 2318de8d7fSPeter Avalos * notice, this list of conditions and the following disclaimer in the 2418de8d7fSPeter Avalos * documentation and/or other materials provided with the distribution. 2518de8d7fSPeter Avalos * 2618de8d7fSPeter Avalos * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 2718de8d7fSPeter Avalos * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 2818de8d7fSPeter Avalos * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2918de8d7fSPeter Avalos * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 3018de8d7fSPeter Avalos * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 3118de8d7fSPeter Avalos * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 3218de8d7fSPeter Avalos * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 3318de8d7fSPeter Avalos * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 3418de8d7fSPeter Avalos * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 3518de8d7fSPeter Avalos * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 3618de8d7fSPeter Avalos */ 3718de8d7fSPeter Avalos 3818de8d7fSPeter Avalos #include "includes.h" 3918de8d7fSPeter Avalos 4018de8d7fSPeter Avalos #include <sys/types.h> 4118de8d7fSPeter Avalos #include <sys/stat.h> 4218de8d7fSPeter Avalos 4318de8d7fSPeter Avalos #include <openssl/evp.h> 4418de8d7fSPeter Avalos #include "openbsd-compat/openssl-compat.h" 4518de8d7fSPeter Avalos 46e9778795SPeter Avalos #include <errno.h> 4718de8d7fSPeter Avalos #include <fcntl.h> 4818de8d7fSPeter Avalos #include <pwd.h> 4918de8d7fSPeter Avalos #include <stdarg.h> 5018de8d7fSPeter Avalos #include <stdio.h> 5118de8d7fSPeter Avalos #include <stdlib.h> 5218de8d7fSPeter Avalos #include <string.h> 5318de8d7fSPeter Avalos #include <unistd.h> 54e9778795SPeter Avalos #include <limits.h> 5518de8d7fSPeter Avalos 5618de8d7fSPeter Avalos #include "xmalloc.h" 5718de8d7fSPeter Avalos #include "ssh.h" 5818de8d7fSPeter Avalos #include "log.h" 59e9778795SPeter Avalos #include "sshkey.h" 60e9778795SPeter Avalos #include "sshbuf.h" 6118de8d7fSPeter Avalos #include "authfd.h" 6218de8d7fSPeter Avalos #include "authfile.h" 6318de8d7fSPeter Avalos #include "pathnames.h" 6418de8d7fSPeter Avalos #include "misc.h" 6536e94dc5SPeter Avalos #include "ssherr.h" 66e9778795SPeter Avalos #include "digest.h" 6718de8d7fSPeter Avalos 6818de8d7fSPeter Avalos /* argv0 */ 6918de8d7fSPeter Avalos extern char *__progname; 7018de8d7fSPeter Avalos 7118de8d7fSPeter Avalos /* Default files to add */ 7218de8d7fSPeter Avalos static char *default_files[] = { 73e9778795SPeter Avalos #ifdef WITH_OPENSSL 7418de8d7fSPeter Avalos _PATH_SSH_CLIENT_ID_RSA, 7518de8d7fSPeter Avalos _PATH_SSH_CLIENT_ID_DSA, 769f304aafSPeter Avalos #ifdef OPENSSL_HAS_ECC 779f304aafSPeter Avalos _PATH_SSH_CLIENT_ID_ECDSA, 789f304aafSPeter Avalos #endif 79e9778795SPeter Avalos #endif /* WITH_OPENSSL */ 8036e94dc5SPeter Avalos _PATH_SSH_CLIENT_ID_ED25519, 81*664f4763Szrj _PATH_SSH_CLIENT_ID_XMSS, 8218de8d7fSPeter Avalos NULL 8318de8d7fSPeter Avalos }; 8418de8d7fSPeter Avalos 85e9778795SPeter Avalos static int fingerprint_hash = SSH_FP_HASH_DEFAULT; 86e9778795SPeter Avalos 8718de8d7fSPeter Avalos /* Default lifetime (0 == forever) */ 8818de8d7fSPeter Avalos static int lifetime = 0; 8918de8d7fSPeter Avalos 9018de8d7fSPeter Avalos /* User has to confirm key use */ 9118de8d7fSPeter Avalos static int confirm = 0; 9218de8d7fSPeter Avalos 93*664f4763Szrj /* Maximum number of signatures (XMSS) */ 94*664f4763Szrj static u_int maxsign = 0; 95*664f4763Szrj static u_int minleft = 0; 96*664f4763Szrj 97e9778795SPeter Avalos /* we keep a cache of one passphrase */ 9818de8d7fSPeter Avalos static char *pass = NULL; 9918de8d7fSPeter Avalos static void 10018de8d7fSPeter Avalos clear_pass(void) 10118de8d7fSPeter Avalos { 10218de8d7fSPeter Avalos if (pass) { 10336e94dc5SPeter Avalos explicit_bzero(pass, strlen(pass)); 10436e94dc5SPeter Avalos free(pass); 10518de8d7fSPeter Avalos pass = NULL; 10618de8d7fSPeter Avalos } 10718de8d7fSPeter Avalos } 10818de8d7fSPeter Avalos 10918de8d7fSPeter Avalos static int 110ce74bacaSMatthew Dillon delete_file(int agent_fd, const char *filename, int key_only, int qflag) 11118de8d7fSPeter Avalos { 112e9778795SPeter Avalos struct sshkey *public, *cert = NULL; 11336e94dc5SPeter Avalos char *certpath = NULL, *comment = NULL; 114e9778795SPeter Avalos int r, ret = -1; 11518de8d7fSPeter Avalos 116e9778795SPeter Avalos if ((r = sshkey_load_public(filename, &public, &comment)) != 0) { 117e9778795SPeter Avalos printf("Bad key file %s: %s\n", filename, ssh_err(r)); 11818de8d7fSPeter Avalos return -1; 11918de8d7fSPeter Avalos } 120e9778795SPeter Avalos if ((r = ssh_remove_identity(agent_fd, public)) == 0) { 121ce74bacaSMatthew Dillon if (!qflag) { 122ce74bacaSMatthew Dillon fprintf(stderr, "Identity removed: %s (%s)\n", 123ce74bacaSMatthew Dillon filename, comment); 124ce74bacaSMatthew Dillon } 12518de8d7fSPeter Avalos ret = 0; 12618de8d7fSPeter Avalos } else 127e9778795SPeter Avalos fprintf(stderr, "Could not remove identity \"%s\": %s\n", 128e9778795SPeter Avalos filename, ssh_err(r)); 12918de8d7fSPeter Avalos 13036e94dc5SPeter Avalos if (key_only) 13136e94dc5SPeter Avalos goto out; 13236e94dc5SPeter Avalos 13336e94dc5SPeter Avalos /* Now try to delete the corresponding certificate too */ 13436e94dc5SPeter Avalos free(comment); 13536e94dc5SPeter Avalos comment = NULL; 13636e94dc5SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename); 137e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) { 138e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 139e9778795SPeter Avalos error("Failed to load certificate \"%s\": %s", 140e9778795SPeter Avalos certpath, ssh_err(r)); 14136e94dc5SPeter Avalos goto out; 142e9778795SPeter Avalos } 143e9778795SPeter Avalos 144e9778795SPeter Avalos if (!sshkey_equal_public(cert, public)) 14536e94dc5SPeter Avalos fatal("Certificate %s does not match private key %s", 14636e94dc5SPeter Avalos certpath, filename); 14736e94dc5SPeter Avalos 148e9778795SPeter Avalos if ((r = ssh_remove_identity(agent_fd, cert)) == 0) { 149ce74bacaSMatthew Dillon if (!qflag) { 150ce74bacaSMatthew Dillon fprintf(stderr, "Identity removed: %s (%s)\n", 151ce74bacaSMatthew Dillon certpath, comment); 152ce74bacaSMatthew Dillon } 15336e94dc5SPeter Avalos ret = 0; 15436e94dc5SPeter Avalos } else 155e9778795SPeter Avalos fprintf(stderr, "Could not remove identity \"%s\": %s\n", 156e9778795SPeter Avalos certpath, ssh_err(r)); 15736e94dc5SPeter Avalos 15836e94dc5SPeter Avalos out: 159e9778795SPeter Avalos sshkey_free(cert); 160e9778795SPeter Avalos sshkey_free(public); 16136e94dc5SPeter Avalos free(certpath); 16236e94dc5SPeter Avalos free(comment); 16318de8d7fSPeter Avalos 16418de8d7fSPeter Avalos return ret; 16518de8d7fSPeter Avalos } 16618de8d7fSPeter Avalos 16718de8d7fSPeter Avalos /* Send a request to remove all identities. */ 16818de8d7fSPeter Avalos static int 169*664f4763Szrj delete_all(int agent_fd, int qflag) 17018de8d7fSPeter Avalos { 17118de8d7fSPeter Avalos int ret = -1; 17218de8d7fSPeter Avalos 173ce74bacaSMatthew Dillon /* 174ce74bacaSMatthew Dillon * Since the agent might be forwarded, old or non-OpenSSH, when asked 175ce74bacaSMatthew Dillon * to remove all keys, attempt to remove both protocol v.1 and v.2 176ce74bacaSMatthew Dillon * keys. 177ce74bacaSMatthew Dillon */ 178e9778795SPeter Avalos if (ssh_remove_all_identities(agent_fd, 2) == 0) 17918de8d7fSPeter Avalos ret = 0; 180e9778795SPeter Avalos /* ignore error-code for ssh1 */ 181e9778795SPeter Avalos ssh_remove_all_identities(agent_fd, 1); 18218de8d7fSPeter Avalos 183*664f4763Szrj if (ret != 0) 18418de8d7fSPeter Avalos fprintf(stderr, "Failed to remove all identities.\n"); 185*664f4763Szrj else if (!qflag) 186*664f4763Szrj fprintf(stderr, "All identities removed.\n"); 18718de8d7fSPeter Avalos 18818de8d7fSPeter Avalos return ret; 18918de8d7fSPeter Avalos } 19018de8d7fSPeter Avalos 19118de8d7fSPeter Avalos static int 192ce74bacaSMatthew Dillon add_file(int agent_fd, const char *filename, int key_only, int qflag) 19318de8d7fSPeter Avalos { 194e9778795SPeter Avalos struct sshkey *private, *cert; 19518de8d7fSPeter Avalos char *comment = NULL; 19699e85e0dSPeter Avalos char msg[1024], *certpath = NULL; 197e9778795SPeter Avalos int r, fd, ret = -1; 198*664f4763Szrj size_t i; 199*664f4763Szrj u_int32_t left; 200e9778795SPeter Avalos struct sshbuf *keyblob; 201*664f4763Szrj struct ssh_identitylist *idlist; 20218de8d7fSPeter Avalos 2031c188a7fSPeter Avalos if (strcmp(filename, "-") == 0) { 2041c188a7fSPeter Avalos fd = STDIN_FILENO; 2051c188a7fSPeter Avalos filename = "(stdin)"; 2061c188a7fSPeter Avalos } else if ((fd = open(filename, O_RDONLY)) < 0) { 20718de8d7fSPeter Avalos perror(filename); 20818de8d7fSPeter Avalos return -1; 20918de8d7fSPeter Avalos } 21018de8d7fSPeter Avalos 21118de8d7fSPeter Avalos /* 21218de8d7fSPeter Avalos * Since we'll try to load a keyfile multiple times, permission errors 21318de8d7fSPeter Avalos * will occur multiple times, so check perms first and bail if wrong. 21418de8d7fSPeter Avalos */ 2151c188a7fSPeter Avalos if (fd != STDIN_FILENO) { 216e9778795SPeter Avalos if (sshkey_perm_ok(fd, filename) != 0) { 21718de8d7fSPeter Avalos close(fd); 21818de8d7fSPeter Avalos return -1; 2191c188a7fSPeter Avalos } 2201c188a7fSPeter Avalos } 221e9778795SPeter Avalos if ((keyblob = sshbuf_new()) == NULL) 222e9778795SPeter Avalos fatal("%s: sshbuf_new failed", __func__); 223e9778795SPeter Avalos if ((r = sshkey_load_file(fd, keyblob)) != 0) { 224e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 225e9778795SPeter Avalos filename, ssh_err(r)); 226e9778795SPeter Avalos sshbuf_free(keyblob); 2271c188a7fSPeter Avalos close(fd); 2281c188a7fSPeter Avalos return -1; 2291c188a7fSPeter Avalos } 2301c188a7fSPeter Avalos close(fd); 23118de8d7fSPeter Avalos 23218de8d7fSPeter Avalos /* At first, try empty passphrase */ 233e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, "", &private, 234e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 235e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 236e9778795SPeter Avalos filename, ssh_err(r)); 237e9778795SPeter Avalos goto fail_load; 238e9778795SPeter Avalos } 23936e94dc5SPeter Avalos /* try last */ 24036e94dc5SPeter Avalos if (private == NULL && pass != NULL) { 241e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private, 242e9778795SPeter Avalos &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 243e9778795SPeter Avalos fprintf(stderr, "Error loading key \"%s\": %s\n", 244e9778795SPeter Avalos filename, ssh_err(r)); 245e9778795SPeter Avalos goto fail_load; 24636e94dc5SPeter Avalos } 247e9778795SPeter Avalos } 24818de8d7fSPeter Avalos if (private == NULL) { 24918de8d7fSPeter Avalos /* clear passphrase since it did not work */ 25018de8d7fSPeter Avalos clear_pass(); 251e9778795SPeter Avalos snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ", 252e9778795SPeter Avalos filename, confirm ? " (will confirm each use)" : ""); 25318de8d7fSPeter Avalos for (;;) { 25418de8d7fSPeter Avalos pass = read_passphrase(msg, RP_ALLOW_STDIN); 255e9778795SPeter Avalos if (strcmp(pass, "") == 0) 256e9778795SPeter Avalos goto fail_load; 257e9778795SPeter Avalos if ((r = sshkey_parse_private_fileblob(keyblob, pass, 258e9778795SPeter Avalos &private, &comment)) == 0) 259e9778795SPeter Avalos break; 260e9778795SPeter Avalos else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 261e9778795SPeter Avalos fprintf(stderr, 262e9778795SPeter Avalos "Error loading key \"%s\": %s\n", 263e9778795SPeter Avalos filename, ssh_err(r)); 264e9778795SPeter Avalos fail_load: 26518de8d7fSPeter Avalos clear_pass(); 266e9778795SPeter Avalos sshbuf_free(keyblob); 26718de8d7fSPeter Avalos return -1; 26818de8d7fSPeter Avalos } 26918de8d7fSPeter Avalos clear_pass(); 27018de8d7fSPeter Avalos snprintf(msg, sizeof msg, 271e9778795SPeter Avalos "Bad passphrase, try again for %s%s: ", filename, 272e9778795SPeter Avalos confirm ? " (will confirm each use)" : ""); 27318de8d7fSPeter Avalos } 27418de8d7fSPeter Avalos } 275e9778795SPeter Avalos if (comment == NULL || *comment == '\0') 276e9778795SPeter Avalos comment = xstrdup(filename); 277e9778795SPeter Avalos sshbuf_free(keyblob); 27818de8d7fSPeter Avalos 279*664f4763Szrj /* For XMSS */ 280*664f4763Szrj if ((r = sshkey_set_filename(private, filename)) != 0) { 281*664f4763Szrj fprintf(stderr, "Could not add filename to private key: %s (%s)\n", 282*664f4763Szrj filename, comment); 283*664f4763Szrj goto out; 284*664f4763Szrj } 285*664f4763Szrj if (maxsign && minleft && 286*664f4763Szrj (r = ssh_fetch_identitylist(agent_fd, &idlist)) == 0) { 287*664f4763Szrj for (i = 0; i < idlist->nkeys; i++) { 288*664f4763Szrj if (!sshkey_equal_public(idlist->keys[i], private)) 289*664f4763Szrj continue; 290*664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]); 291*664f4763Szrj if (left < minleft) { 292*664f4763Szrj fprintf(stderr, 293*664f4763Szrj "Only %d signatures left.\n", left); 294*664f4763Szrj break; 295*664f4763Szrj } 296*664f4763Szrj fprintf(stderr, "Skipping update: "); 297*664f4763Szrj if (left == minleft) { 298*664f4763Szrj fprintf(stderr, 299*664f4763Szrj "required signatures left (%d).\n", left); 300*664f4763Szrj } else { 301*664f4763Szrj fprintf(stderr, 302*664f4763Szrj "more signatures left (%d) than" 303*664f4763Szrj " required (%d).\n", left, minleft); 304*664f4763Szrj } 305*664f4763Szrj ssh_free_identitylist(idlist); 306*664f4763Szrj goto out; 307*664f4763Szrj } 308*664f4763Szrj ssh_free_identitylist(idlist); 309*664f4763Szrj } 310*664f4763Szrj 311e9778795SPeter Avalos if ((r = ssh_add_identity_constrained(agent_fd, private, comment, 312*664f4763Szrj lifetime, confirm, maxsign)) == 0) { 31318de8d7fSPeter Avalos ret = 0; 314*664f4763Szrj if (!qflag) { 315*664f4763Szrj fprintf(stderr, "Identity added: %s (%s)\n", 316*664f4763Szrj filename, comment); 317*664f4763Szrj if (lifetime != 0) { 31818de8d7fSPeter Avalos fprintf(stderr, 31918de8d7fSPeter Avalos "Lifetime set to %d seconds\n", lifetime); 320*664f4763Szrj } 321*664f4763Szrj if (confirm != 0) { 322*664f4763Szrj fprintf(stderr, "The user must confirm " 323*664f4763Szrj "each use of the key\n"); 324*664f4763Szrj } 325*664f4763Szrj } 32618de8d7fSPeter Avalos } else { 327e9778795SPeter Avalos fprintf(stderr, "Could not add identity \"%s\": %s\n", 328e9778795SPeter Avalos filename, ssh_err(r)); 32918de8d7fSPeter Avalos } 33018de8d7fSPeter Avalos 33199e85e0dSPeter Avalos /* Skip trying to load the cert if requested */ 33299e85e0dSPeter Avalos if (key_only) 33399e85e0dSPeter Avalos goto out; 334856ea928SPeter Avalos 335856ea928SPeter Avalos /* Now try to add the certificate flavour too */ 336856ea928SPeter Avalos xasprintf(&certpath, "%s-cert.pub", filename); 337e9778795SPeter Avalos if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) { 338e9778795SPeter Avalos if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 339e9778795SPeter Avalos error("Failed to load certificate \"%s\": %s", 340e9778795SPeter Avalos certpath, ssh_err(r)); 341856ea928SPeter Avalos goto out; 342e9778795SPeter Avalos } 343856ea928SPeter Avalos 344e9778795SPeter Avalos if (!sshkey_equal_public(cert, private)) { 345856ea928SPeter Avalos error("Certificate %s does not match private key %s", 346856ea928SPeter Avalos certpath, filename); 347e9778795SPeter Avalos sshkey_free(cert); 348856ea928SPeter Avalos goto out; 349856ea928SPeter Avalos } 350856ea928SPeter Avalos 351856ea928SPeter Avalos /* Graft with private bits */ 352e9778795SPeter Avalos if ((r = sshkey_to_certified(private)) != 0) { 353e9778795SPeter Avalos error("%s: sshkey_to_certified: %s", __func__, ssh_err(r)); 354e9778795SPeter Avalos sshkey_free(cert); 355856ea928SPeter Avalos goto out; 356856ea928SPeter Avalos } 357e9778795SPeter Avalos if ((r = sshkey_cert_copy(cert, private)) != 0) { 358ce74bacaSMatthew Dillon error("%s: sshkey_cert_copy: %s", __func__, ssh_err(r)); 359e9778795SPeter Avalos sshkey_free(cert); 360e9778795SPeter Avalos goto out; 361e9778795SPeter Avalos } 362e9778795SPeter Avalos sshkey_free(cert); 363856ea928SPeter Avalos 364e9778795SPeter Avalos if ((r = ssh_add_identity_constrained(agent_fd, private, comment, 365*664f4763Szrj lifetime, confirm, maxsign)) != 0) { 366e9778795SPeter Avalos error("Certificate %s (%s) add failed: %s", certpath, 367e9778795SPeter Avalos private->cert->key_id, ssh_err(r)); 368e9778795SPeter Avalos goto out; 369856ea928SPeter Avalos } 370*664f4763Szrj /* success */ 371*664f4763Szrj if (!qflag) { 372856ea928SPeter Avalos fprintf(stderr, "Certificate added: %s (%s)\n", certpath, 373856ea928SPeter Avalos private->cert->key_id); 374*664f4763Szrj if (lifetime != 0) { 375*664f4763Szrj fprintf(stderr, "Lifetime set to %d seconds\n", 376*664f4763Szrj lifetime); 377*664f4763Szrj } 378*664f4763Szrj if (confirm != 0) { 379*664f4763Szrj fprintf(stderr, "The user must confirm each use " 380*664f4763Szrj "of the key\n"); 381*664f4763Szrj } 382*664f4763Szrj } 383*664f4763Szrj 384856ea928SPeter Avalos out: 38536e94dc5SPeter Avalos free(certpath); 38636e94dc5SPeter Avalos free(comment); 387e9778795SPeter Avalos sshkey_free(private); 38818de8d7fSPeter Avalos 38918de8d7fSPeter Avalos return ret; 39018de8d7fSPeter Avalos } 39118de8d7fSPeter Avalos 39218de8d7fSPeter Avalos static int 393*664f4763Szrj update_card(int agent_fd, int add, const char *id, int qflag) 39418de8d7fSPeter Avalos { 39536e94dc5SPeter Avalos char *pin = NULL; 396e9778795SPeter Avalos int r, ret = -1; 39718de8d7fSPeter Avalos 39836e94dc5SPeter Avalos if (add) { 39936e94dc5SPeter Avalos if ((pin = read_passphrase("Enter passphrase for PKCS#11: ", 40036e94dc5SPeter Avalos RP_ALLOW_STDIN)) == NULL) 40118de8d7fSPeter Avalos return -1; 40236e94dc5SPeter Avalos } 40318de8d7fSPeter Avalos 404e9778795SPeter Avalos if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin, 405e9778795SPeter Avalos lifetime, confirm)) == 0) { 406*664f4763Szrj ret = 0; 407*664f4763Szrj if (!qflag) { 40818de8d7fSPeter Avalos fprintf(stderr, "Card %s: %s\n", 40918de8d7fSPeter Avalos add ? "added" : "removed", id); 410*664f4763Szrj } 41118de8d7fSPeter Avalos } else { 412e9778795SPeter Avalos fprintf(stderr, "Could not %s card \"%s\": %s\n", 413e9778795SPeter Avalos add ? "add" : "remove", id, ssh_err(r)); 41418de8d7fSPeter Avalos ret = -1; 41518de8d7fSPeter Avalos } 41636e94dc5SPeter Avalos free(pin); 41718de8d7fSPeter Avalos return ret; 41818de8d7fSPeter Avalos } 41918de8d7fSPeter Avalos 42018de8d7fSPeter Avalos static int 421*664f4763Szrj test_key(int agent_fd, const char *filename) 422*664f4763Szrj { 423*664f4763Szrj struct sshkey *key = NULL; 424*664f4763Szrj u_char *sig = NULL; 425*664f4763Szrj size_t slen = 0; 426*664f4763Szrj int r, ret = -1; 427*664f4763Szrj char data[1024]; 428*664f4763Szrj 429*664f4763Szrj if ((r = sshkey_load_public(filename, &key, NULL)) != 0) { 430*664f4763Szrj error("Couldn't read public key %s: %s", filename, ssh_err(r)); 431*664f4763Szrj return -1; 432*664f4763Szrj } 433*664f4763Szrj arc4random_buf(data, sizeof(data)); 434*664f4763Szrj if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data), 435*664f4763Szrj NULL, 0)) != 0) { 436*664f4763Szrj error("Agent signature failed for %s: %s", 437*664f4763Szrj filename, ssh_err(r)); 438*664f4763Szrj goto done; 439*664f4763Szrj } 440*664f4763Szrj if ((r = sshkey_verify(key, sig, slen, data, sizeof(data), 441*664f4763Szrj NULL, 0)) != 0) { 442*664f4763Szrj error("Signature verification failed for %s: %s", 443*664f4763Szrj filename, ssh_err(r)); 444*664f4763Szrj goto done; 445*664f4763Szrj } 446*664f4763Szrj /* success */ 447*664f4763Szrj ret = 0; 448*664f4763Szrj done: 449*664f4763Szrj free(sig); 450*664f4763Szrj sshkey_free(key); 451*664f4763Szrj return ret; 452*664f4763Szrj } 453*664f4763Szrj 454*664f4763Szrj static int 455e9778795SPeter Avalos list_identities(int agent_fd, int do_fp) 45618de8d7fSPeter Avalos { 457e9778795SPeter Avalos char *fp; 458ce74bacaSMatthew Dillon int r; 459e9778795SPeter Avalos struct ssh_identitylist *idlist; 460*664f4763Szrj u_int32_t left; 461e9778795SPeter Avalos size_t i; 46218de8d7fSPeter Avalos 463ce74bacaSMatthew Dillon if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) { 464e9778795SPeter Avalos if (r != SSH_ERR_AGENT_NO_IDENTITIES) 465ce74bacaSMatthew Dillon fprintf(stderr, "error fetching identities: %s\n", 466ce74bacaSMatthew Dillon ssh_err(r)); 467ce74bacaSMatthew Dillon else 468ce74bacaSMatthew Dillon printf("The agent has no identities.\n"); 469ce74bacaSMatthew Dillon return -1; 470e9778795SPeter Avalos } 471e9778795SPeter Avalos for (i = 0; i < idlist->nkeys; i++) { 47218de8d7fSPeter Avalos if (do_fp) { 473e9778795SPeter Avalos fp = sshkey_fingerprint(idlist->keys[i], 474e9778795SPeter Avalos fingerprint_hash, SSH_FP_DEFAULT); 475ce74bacaSMatthew Dillon printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]), 476ce74bacaSMatthew Dillon fp == NULL ? "(null)" : fp, idlist->comments[i], 477e9778795SPeter Avalos sshkey_type(idlist->keys[i])); 47836e94dc5SPeter Avalos free(fp); 47918de8d7fSPeter Avalos } else { 480ce74bacaSMatthew Dillon if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) { 481e9778795SPeter Avalos fprintf(stderr, "sshkey_write: %s\n", 482e9778795SPeter Avalos ssh_err(r)); 483e9778795SPeter Avalos continue; 48418de8d7fSPeter Avalos } 485*664f4763Szrj fprintf(stdout, " %s", idlist->comments[i]); 486*664f4763Szrj left = sshkey_signatures_left(idlist->keys[i]); 487*664f4763Szrj if (left > 0) 488*664f4763Szrj fprintf(stdout, 489*664f4763Szrj " [signatures left %d]", left); 490*664f4763Szrj fprintf(stdout, "\n"); 49118de8d7fSPeter Avalos } 49218de8d7fSPeter Avalos } 493e9778795SPeter Avalos ssh_free_identitylist(idlist); 49418de8d7fSPeter Avalos return 0; 49518de8d7fSPeter Avalos } 49618de8d7fSPeter Avalos 49718de8d7fSPeter Avalos static int 498e9778795SPeter Avalos lock_agent(int agent_fd, int lock) 49918de8d7fSPeter Avalos { 50018de8d7fSPeter Avalos char prompt[100], *p1, *p2; 501e9778795SPeter Avalos int r, passok = 1, ret = -1; 50218de8d7fSPeter Avalos 50318de8d7fSPeter Avalos strlcpy(prompt, "Enter lock password: ", sizeof(prompt)); 50418de8d7fSPeter Avalos p1 = read_passphrase(prompt, RP_ALLOW_STDIN); 50518de8d7fSPeter Avalos if (lock) { 50618de8d7fSPeter Avalos strlcpy(prompt, "Again: ", sizeof prompt); 50718de8d7fSPeter Avalos p2 = read_passphrase(prompt, RP_ALLOW_STDIN); 50818de8d7fSPeter Avalos if (strcmp(p1, p2) != 0) { 50918de8d7fSPeter Avalos fprintf(stderr, "Passwords do not match.\n"); 51018de8d7fSPeter Avalos passok = 0; 51118de8d7fSPeter Avalos } 51236e94dc5SPeter Avalos explicit_bzero(p2, strlen(p2)); 51336e94dc5SPeter Avalos free(p2); 51418de8d7fSPeter Avalos } 515e9778795SPeter Avalos if (passok) { 516e9778795SPeter Avalos if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) { 51718de8d7fSPeter Avalos fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un"); 51818de8d7fSPeter Avalos ret = 0; 519e9778795SPeter Avalos } else { 520e9778795SPeter Avalos fprintf(stderr, "Failed to %slock agent: %s\n", 521e9778795SPeter Avalos lock ? "" : "un", ssh_err(r)); 522e9778795SPeter Avalos } 523e9778795SPeter Avalos } 52436e94dc5SPeter Avalos explicit_bzero(p1, strlen(p1)); 52536e94dc5SPeter Avalos free(p1); 52618de8d7fSPeter Avalos return (ret); 52718de8d7fSPeter Avalos } 52818de8d7fSPeter Avalos 52918de8d7fSPeter Avalos static int 530ce74bacaSMatthew Dillon do_file(int agent_fd, int deleting, int key_only, char *file, int qflag) 53118de8d7fSPeter Avalos { 53218de8d7fSPeter Avalos if (deleting) { 533ce74bacaSMatthew Dillon if (delete_file(agent_fd, file, key_only, qflag) == -1) 53418de8d7fSPeter Avalos return -1; 53518de8d7fSPeter Avalos } else { 536ce74bacaSMatthew Dillon if (add_file(agent_fd, file, key_only, qflag) == -1) 53718de8d7fSPeter Avalos return -1; 53818de8d7fSPeter Avalos } 53918de8d7fSPeter Avalos return 0; 54018de8d7fSPeter Avalos } 54118de8d7fSPeter Avalos 54218de8d7fSPeter Avalos static void 54318de8d7fSPeter Avalos usage(void) 54418de8d7fSPeter Avalos { 54518de8d7fSPeter Avalos fprintf(stderr, "usage: %s [options] [file ...]\n", __progname); 54618de8d7fSPeter Avalos fprintf(stderr, "Options:\n"); 54718de8d7fSPeter Avalos fprintf(stderr, " -l List fingerprints of all identities.\n"); 548e9778795SPeter Avalos fprintf(stderr, " -E hash Specify hash algorithm used for fingerprints.\n"); 54918de8d7fSPeter Avalos fprintf(stderr, " -L List public key parameters of all identities.\n"); 55099e85e0dSPeter Avalos fprintf(stderr, " -k Load only keys and not certificates.\n"); 55199e85e0dSPeter Avalos fprintf(stderr, " -c Require confirmation to sign using identities\n"); 552*664f4763Szrj fprintf(stderr, " -m minleft Maxsign is only changed if less than minleft are left (for XMSS)\n"); 553*664f4763Szrj fprintf(stderr, " -M maxsign Maximum number of signatures allowed (for XMSS)\n"); 55499e85e0dSPeter Avalos fprintf(stderr, " -t life Set lifetime (in seconds) when adding identities.\n"); 55518de8d7fSPeter Avalos fprintf(stderr, " -d Delete identity.\n"); 55618de8d7fSPeter Avalos fprintf(stderr, " -D Delete all identities.\n"); 55718de8d7fSPeter Avalos fprintf(stderr, " -x Lock agent.\n"); 55818de8d7fSPeter Avalos fprintf(stderr, " -X Unlock agent.\n"); 559856ea928SPeter Avalos fprintf(stderr, " -s pkcs11 Add keys from PKCS#11 provider.\n"); 560856ea928SPeter Avalos fprintf(stderr, " -e pkcs11 Remove keys provided by PKCS#11 provider.\n"); 561*664f4763Szrj fprintf(stderr, " -T pubkey Test if ssh-agent can access matching private key.\n"); 562ce74bacaSMatthew Dillon fprintf(stderr, " -q Be quiet after a successful operation.\n"); 563*664f4763Szrj fprintf(stderr, " -v Be more verbose.\n"); 56418de8d7fSPeter Avalos } 56518de8d7fSPeter Avalos 56618de8d7fSPeter Avalos int 56718de8d7fSPeter Avalos main(int argc, char **argv) 56818de8d7fSPeter Avalos { 56918de8d7fSPeter Avalos extern char *optarg; 57018de8d7fSPeter Avalos extern int optind; 571e9778795SPeter Avalos int agent_fd; 572856ea928SPeter Avalos char *pkcs11provider = NULL; 573e9778795SPeter Avalos int r, i, ch, deleting = 0, ret = 0, key_only = 0; 574*664f4763Szrj int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0; 575*664f4763Szrj SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 576*664f4763Szrj LogLevel log_level = SYSLOG_LEVEL_INFO; 57718de8d7fSPeter Avalos 578e9778795SPeter Avalos ssh_malloc_init(); /* must be called before any mallocs */ 57918de8d7fSPeter Avalos /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 58018de8d7fSPeter Avalos sanitise_stdfd(); 58118de8d7fSPeter Avalos 58218de8d7fSPeter Avalos __progname = ssh_get_progname(argv[0]); 58318de8d7fSPeter Avalos seed_rng(); 58418de8d7fSPeter Avalos 585*664f4763Szrj log_init(__progname, log_level, log_facility, 1); 58618de8d7fSPeter Avalos 587e9778795SPeter Avalos setvbuf(stdout, NULL, _IOLBF, 0); 58836e94dc5SPeter Avalos 589e9778795SPeter Avalos /* First, get a connection to the authentication agent. */ 590e9778795SPeter Avalos switch (r = ssh_get_authentication_socket(&agent_fd)) { 591e9778795SPeter Avalos case 0: 592e9778795SPeter Avalos break; 593e9778795SPeter Avalos case SSH_ERR_AGENT_NOT_PRESENT: 594e9778795SPeter Avalos fprintf(stderr, "Could not open a connection to your " 595e9778795SPeter Avalos "authentication agent.\n"); 596e9778795SPeter Avalos exit(2); 597e9778795SPeter Avalos default: 598e9778795SPeter Avalos fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r)); 59918de8d7fSPeter Avalos exit(2); 60018de8d7fSPeter Avalos } 601e9778795SPeter Avalos 602*664f4763Szrj while ((ch = getopt(argc, argv, "vklLcdDTxXE:e:M:m:qs:t:")) != -1) { 60318de8d7fSPeter Avalos switch (ch) { 604*664f4763Szrj case 'v': 605*664f4763Szrj if (log_level == SYSLOG_LEVEL_INFO) 606*664f4763Szrj log_level = SYSLOG_LEVEL_DEBUG1; 607*664f4763Szrj else if (log_level < SYSLOG_LEVEL_DEBUG3) 608*664f4763Szrj log_level++; 609*664f4763Szrj break; 610e9778795SPeter Avalos case 'E': 611e9778795SPeter Avalos fingerprint_hash = ssh_digest_alg_by_name(optarg); 612e9778795SPeter Avalos if (fingerprint_hash == -1) 613e9778795SPeter Avalos fatal("Invalid hash algorithm \"%s\"", optarg); 614e9778795SPeter Avalos break; 61599e85e0dSPeter Avalos case 'k': 61699e85e0dSPeter Avalos key_only = 1; 61799e85e0dSPeter Avalos break; 61818de8d7fSPeter Avalos case 'l': 61918de8d7fSPeter Avalos case 'L': 620e9778795SPeter Avalos if (lflag != 0) 621e9778795SPeter Avalos fatal("-%c flag already specified", lflag); 622e9778795SPeter Avalos lflag = ch; 623e9778795SPeter Avalos break; 62418de8d7fSPeter Avalos case 'x': 62518de8d7fSPeter Avalos case 'X': 626e9778795SPeter Avalos if (xflag != 0) 627e9778795SPeter Avalos fatal("-%c flag already specified", xflag); 628e9778795SPeter Avalos xflag = ch; 629e9778795SPeter Avalos break; 63018de8d7fSPeter Avalos case 'c': 63118de8d7fSPeter Avalos confirm = 1; 63218de8d7fSPeter Avalos break; 633*664f4763Szrj case 'm': 634*664f4763Szrj minleft = (int)strtonum(optarg, 1, UINT_MAX, NULL); 635*664f4763Szrj if (minleft == 0) { 636*664f4763Szrj usage(); 637*664f4763Szrj ret = 1; 638*664f4763Szrj goto done; 639*664f4763Szrj } 640*664f4763Szrj break; 641*664f4763Szrj case 'M': 642*664f4763Szrj maxsign = (int)strtonum(optarg, 1, UINT_MAX, NULL); 643*664f4763Szrj if (maxsign == 0) { 644*664f4763Szrj usage(); 645*664f4763Szrj ret = 1; 646*664f4763Szrj goto done; 647*664f4763Szrj } 648*664f4763Szrj break; 64918de8d7fSPeter Avalos case 'd': 65018de8d7fSPeter Avalos deleting = 1; 65118de8d7fSPeter Avalos break; 65218de8d7fSPeter Avalos case 'D': 653e9778795SPeter Avalos Dflag = 1; 654e9778795SPeter Avalos break; 65518de8d7fSPeter Avalos case 's': 656856ea928SPeter Avalos pkcs11provider = optarg; 65718de8d7fSPeter Avalos break; 65818de8d7fSPeter Avalos case 'e': 65918de8d7fSPeter Avalos deleting = 1; 660856ea928SPeter Avalos pkcs11provider = optarg; 66118de8d7fSPeter Avalos break; 66218de8d7fSPeter Avalos case 't': 66318de8d7fSPeter Avalos if ((lifetime = convtime(optarg)) == -1) { 66418de8d7fSPeter Avalos fprintf(stderr, "Invalid lifetime\n"); 66518de8d7fSPeter Avalos ret = 1; 66618de8d7fSPeter Avalos goto done; 66718de8d7fSPeter Avalos } 66818de8d7fSPeter Avalos break; 669ce74bacaSMatthew Dillon case 'q': 670ce74bacaSMatthew Dillon qflag = 1; 671ce74bacaSMatthew Dillon break; 672*664f4763Szrj case 'T': 673*664f4763Szrj Tflag = 1; 674*664f4763Szrj break; 67518de8d7fSPeter Avalos default: 67618de8d7fSPeter Avalos usage(); 67718de8d7fSPeter Avalos ret = 1; 67818de8d7fSPeter Avalos goto done; 67918de8d7fSPeter Avalos } 68018de8d7fSPeter Avalos } 681*664f4763Szrj log_init(__progname, log_level, log_facility, 1); 682e9778795SPeter Avalos 683e9778795SPeter Avalos if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1) 684e9778795SPeter Avalos fatal("Invalid combination of actions"); 685e9778795SPeter Avalos else if (xflag) { 686e9778795SPeter Avalos if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1) 687e9778795SPeter Avalos ret = 1; 688e9778795SPeter Avalos goto done; 689e9778795SPeter Avalos } else if (lflag) { 690e9778795SPeter Avalos if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1) 691e9778795SPeter Avalos ret = 1; 692e9778795SPeter Avalos goto done; 693e9778795SPeter Avalos } else if (Dflag) { 694*664f4763Szrj if (delete_all(agent_fd, qflag) == -1) 695e9778795SPeter Avalos ret = 1; 696e9778795SPeter Avalos goto done; 697e9778795SPeter Avalos } 698e9778795SPeter Avalos 69918de8d7fSPeter Avalos argc -= optind; 70018de8d7fSPeter Avalos argv += optind; 701*664f4763Szrj if (Tflag) { 702*664f4763Szrj if (argc <= 0) 703*664f4763Szrj fatal("no keys to test"); 704*664f4763Szrj for (r = i = 0; i < argc; i++) 705*664f4763Szrj r |= test_key(agent_fd, argv[i]); 706*664f4763Szrj ret = r == 0 ? 0 : 1; 707*664f4763Szrj goto done; 708*664f4763Szrj } 709856ea928SPeter Avalos if (pkcs11provider != NULL) { 710*664f4763Szrj if (update_card(agent_fd, !deleting, pkcs11provider, 711*664f4763Szrj qflag) == -1) 71218de8d7fSPeter Avalos ret = 1; 71318de8d7fSPeter Avalos goto done; 71418de8d7fSPeter Avalos } 71518de8d7fSPeter Avalos if (argc == 0) { 716e9778795SPeter Avalos char buf[PATH_MAX]; 71718de8d7fSPeter Avalos struct passwd *pw; 71818de8d7fSPeter Avalos struct stat st; 71918de8d7fSPeter Avalos int count = 0; 72018de8d7fSPeter Avalos 72118de8d7fSPeter Avalos if ((pw = getpwuid(getuid())) == NULL) { 72218de8d7fSPeter Avalos fprintf(stderr, "No user found with uid %u\n", 72318de8d7fSPeter Avalos (u_int)getuid()); 72418de8d7fSPeter Avalos ret = 1; 72518de8d7fSPeter Avalos goto done; 72618de8d7fSPeter Avalos } 72718de8d7fSPeter Avalos 72818de8d7fSPeter Avalos for (i = 0; default_files[i]; i++) { 72918de8d7fSPeter Avalos snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, 73018de8d7fSPeter Avalos default_files[i]); 73118de8d7fSPeter Avalos if (stat(buf, &st) < 0) 73218de8d7fSPeter Avalos continue; 733ce74bacaSMatthew Dillon if (do_file(agent_fd, deleting, key_only, buf, 734ce74bacaSMatthew Dillon qflag) == -1) 73518de8d7fSPeter Avalos ret = 1; 73618de8d7fSPeter Avalos else 73718de8d7fSPeter Avalos count++; 73818de8d7fSPeter Avalos } 73918de8d7fSPeter Avalos if (count == 0) 74018de8d7fSPeter Avalos ret = 1; 74118de8d7fSPeter Avalos } else { 74218de8d7fSPeter Avalos for (i = 0; i < argc; i++) { 743e9778795SPeter Avalos if (do_file(agent_fd, deleting, key_only, 744ce74bacaSMatthew Dillon argv[i], qflag) == -1) 74518de8d7fSPeter Avalos ret = 1; 74618de8d7fSPeter Avalos } 74718de8d7fSPeter Avalos } 74818de8d7fSPeter Avalos clear_pass(); 74918de8d7fSPeter Avalos 75018de8d7fSPeter Avalos done: 751e9778795SPeter Avalos ssh_close_authentication_socket(agent_fd); 75218de8d7fSPeter Avalos return ret; 75318de8d7fSPeter Avalos } 754