jcs's openbsd hax
openbsd
at jcs 1028 lines 27 kB view raw
1/* $OpenBSD: ssh-add.c,v 1.185 2026/02/11 17:01:34 dtucker Exp $ */ 2/* 3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 4 * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 5 * All rights reserved 6 * Adds an identity to the authentication server, or removes an identity. 7 * 8 * As far as I am concerned, the code I have written for this software 9 * can be used freely for any purpose. Any derived versions of this 10 * software must be clearly marked as such, and if the derived work is 11 * incompatible with the protocol description in the RFC file, it must be 12 * called by a name other than "ssh" or "Secure Shell". 13 * 14 * SSH2 implementation, 15 * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. 16 * 17 * Redistribution and use in source and binary forms, with or without 18 * modification, are permitted provided that the following conditions 19 * are met: 20 * 1. Redistributions of source code must retain the above copyright 21 * notice, this list of conditions and the following disclaimer. 22 * 2. Redistributions in binary form must reproduce the above copyright 23 * notice, this list of conditions and the following disclaimer in the 24 * documentation and/or other materials provided with the distribution. 25 * 26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 27 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 29 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 30 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 35 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 */ 37 38#include <sys/types.h> 39#include <sys/stat.h> 40 41#ifdef WITH_OPENSSL 42#include <openssl/evp.h> 43#endif 44 45#include <errno.h> 46#include <fcntl.h> 47#include <pwd.h> 48#include <stdio.h> 49#include <stdlib.h> 50#include <stdarg.h> 51#include <string.h> 52#include <unistd.h> 53#include <limits.h> 54#include <time.h> 55 56#include "xmalloc.h" 57#include "ssh.h" 58#include "log.h" 59#include "sshkey.h" 60#include "sshbuf.h" 61#include "authfd.h" 62#include "authfile.h" 63#include "pathnames.h" 64#include "misc.h" 65#include "ssherr.h" 66#include "digest.h" 67#include "ssh-sk.h" 68#include "sk-api.h" 69#include "hostfile.h" 70 71#define CERT_EXPIRY_GRACE (5*60) 72 73/* argv0 */ 74extern char *__progname; 75 76/* Default files to add */ 77static char *default_files[] = { 78 _PATH_SSH_CLIENT_ID_RSA, 79 _PATH_SSH_CLIENT_ID_ECDSA, 80 _PATH_SSH_CLIENT_ID_ECDSA_SK, 81 _PATH_SSH_CLIENT_ID_ED25519, 82 _PATH_SSH_CLIENT_ID_ED25519_SK, 83 NULL 84}; 85 86static int fingerprint_hash = SSH_FP_HASH_DEFAULT; 87 88/* Default lifetime (0 == forever) */ 89static int lifetime = 0; 90 91/* User has to confirm key use */ 92static int confirm = 0; 93 94/* we keep a cache of one passphrase */ 95static char *pass = NULL; 96static void 97clear_pass(void) 98{ 99 if (pass) { 100 freezero(pass, strlen(pass)); 101 pass = NULL; 102 } 103} 104 105static int 106delete_one(int agent_fd, const struct sshkey *key, const char *comment, 107 const char *path, int qflag) 108{ 109 int r; 110 111 if ((r = ssh_remove_identity(agent_fd, key)) != 0) { 112 fprintf(stderr, "Could not remove identity \"%s\": %s\n", 113 path, ssh_err(r)); 114 return r; 115 } 116 if (!qflag) { 117 fprintf(stderr, "Identity removed: %s %s (%s)\n", path, 118 sshkey_type(key), comment ? comment : "no comment"); 119 } 120 return 0; 121} 122 123static int 124delete_stdin(int agent_fd, int qflag, int key_only, int cert_only) 125{ 126 char *line = NULL, *cp; 127 size_t linesize = 0; 128 struct sshkey *key = NULL; 129 int lnum = 0, r, ret = -1; 130 131 while (getline(&line, &linesize, stdin) != -1) { 132 lnum++; 133 sshkey_free(key); 134 key = NULL; 135 line[strcspn(line, "\n")] = '\0'; 136 cp = line + strspn(line, " \t"); 137 if (*cp == '#' || *cp == '\0') 138 continue; 139 if ((key = sshkey_new(KEY_UNSPEC)) == NULL) 140 fatal_f("sshkey_new"); 141 if ((r = sshkey_read(key, &cp)) != 0) { 142 error_r(r, "(stdin):%d: invalid key", lnum); 143 continue; 144 } 145 if ((!key_only && !cert_only) || 146 (key_only && !sshkey_is_cert(key)) || 147 (cert_only && sshkey_is_cert(key))) { 148 if (delete_one(agent_fd, key, cp, 149 "(stdin)", qflag) == 0) 150 ret = 0; 151 } 152 } 153 sshkey_free(key); 154 free(line); 155 return ret; 156} 157 158static int 159delete_file(int agent_fd, const char *filename, int key_only, 160 int cert_only, int qflag) 161{ 162 struct sshkey *public, *cert = NULL; 163 char *certpath = NULL, *comment = NULL; 164 int r, ret = -1; 165 166 if (strcmp(filename, "-") == 0) 167 return delete_stdin(agent_fd, qflag, key_only, cert_only); 168 169 if ((r = sshkey_load_public(filename, &public, &comment)) != 0) { 170 printf("Bad key file %s: %s\n", filename, ssh_err(r)); 171 return -1; 172 } 173 if ((!key_only && !cert_only) || 174 (key_only && !sshkey_is_cert(public)) || 175 (cert_only && sshkey_is_cert(public))) { 176 if (delete_one(agent_fd, public, comment, filename, qflag) == 0) 177 ret = 0; 178 } 179 180 if (key_only) 181 goto out; 182 183 /* Now try to delete the corresponding certificate too */ 184 free(comment); 185 comment = NULL; 186 xasprintf(&certpath, "%s-cert.pub", filename); 187 if ((r = sshkey_load_public(certpath, &cert, &comment)) != 0) { 188 if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 189 error_r(r, "Failed to load certificate \"%s\"", certpath); 190 goto out; 191 } 192 193 if (!sshkey_equal_public(cert, public)) 194 fatal("Certificate %s does not match private key %s", 195 certpath, filename); 196 197 if (delete_one(agent_fd, cert, comment, certpath, qflag) == 0) 198 ret = 0; 199 200 out: 201 sshkey_free(cert); 202 sshkey_free(public); 203 free(certpath); 204 free(comment); 205 206 return ret; 207} 208 209/* Send a request to remove all identities. */ 210static int 211delete_all(int agent_fd, int qflag) 212{ 213 int ret = -1; 214 215 /* 216 * Since the agent might be forwarded, old or non-OpenSSH, when asked 217 * to remove all keys, attempt to remove both protocol v.1 and v.2 218 * keys. 219 */ 220 if (ssh_remove_all_identities(agent_fd, 2) == 0) 221 ret = 0; 222 /* ignore error-code for ssh1 */ 223 ssh_remove_all_identities(agent_fd, 1); 224 225 if (ret != 0) 226 fprintf(stderr, "Failed to remove all identities.\n"); 227 else if (!qflag) 228 fprintf(stderr, "All identities removed.\n"); 229 230 return ret; 231} 232 233static int 234check_cert_lifetime(const struct sshkey *cert, int cert_lifetime) 235{ 236 time_t now; 237 uint64_t n; 238 239 if (cert == NULL || cert->cert == NULL || !sshkey_is_cert(cert) || 240 cert->cert->valid_before == 0xFFFFFFFFFFFFFFFFULL) 241 return cert_lifetime; 242 if ((now = time(NULL)) <= 0) 243 fatal_f("system time is at/before epoch"); 244 if ((uint64_t)now > (cert->cert->valid_before + CERT_EXPIRY_GRACE)) 245 return -1; /* certificate already expired */ 246 n = (CERT_EXPIRY_GRACE + cert->cert->valid_before) - (uint64_t)now; 247 n = MINIMUM(n, INT_MAX); 248 if (cert_lifetime <= 0) 249 return (int)n; 250 return MINIMUM(cert_lifetime, (int)n); 251} 252 253static int 254add_file(int agent_fd, const char *filename, int key_only, int cert_only, 255 int qflag, int Nflag, const char *skprovider, 256 struct dest_constraint **dest_constraints, 257 size_t ndest_constraints) 258{ 259 struct sshkey *private = NULL, *cert = NULL; 260 char *comment = NULL; 261 char msg[1024], *certpath = NULL; 262 int cert_lifetime, r, fd, ret = -1; 263 struct sshbuf *keyblob; 264 265 if (strcmp(filename, "-") == 0) { 266 fd = STDIN_FILENO; 267 filename = "(stdin)"; 268 } else if ((fd = open(filename, O_RDONLY)) == -1) { 269 perror(filename); 270 return -1; 271 } 272 273 /* 274 * Since we'll try to load a keyfile multiple times, permission errors 275 * will occur multiple times, so check perms first and bail if wrong. 276 */ 277 if (fd != STDIN_FILENO) { 278 if (sshkey_perm_ok(fd, filename) != 0) { 279 close(fd); 280 return -1; 281 } 282 } 283 if ((r = sshbuf_load_fd(fd, &keyblob)) != 0) { 284 fprintf(stderr, "Error loading key \"%s\": %s\n", 285 filename, ssh_err(r)); 286 sshbuf_free(keyblob); 287 close(fd); 288 return -1; 289 } 290 close(fd); 291 292 /* At first, try empty passphrase */ 293 if ((r = sshkey_parse_private_fileblob(keyblob, "", &private, 294 &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 295 fprintf(stderr, "Error loading key \"%s\": %s\n", 296 filename, ssh_err(r)); 297 goto fail_load; 298 } 299 /* try last */ 300 if (private == NULL && pass != NULL) { 301 if ((r = sshkey_parse_private_fileblob(keyblob, pass, &private, 302 &comment)) != 0 && r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 303 fprintf(stderr, "Error loading key \"%s\": %s\n", 304 filename, ssh_err(r)); 305 goto fail_load; 306 } 307 } 308 if (private == NULL) { 309 /* clear passphrase since it did not work */ 310 clear_pass(); 311 snprintf(msg, sizeof msg, "Enter passphrase for %s%s: ", 312 filename, confirm ? " (will confirm each use)" : ""); 313 for (;;) { 314 pass = read_passphrase(msg, RP_ALLOW_STDIN); 315 if (strcmp(pass, "") == 0) 316 goto fail_load; 317 if ((r = sshkey_parse_private_fileblob(keyblob, pass, 318 &private, &comment)) == 0) 319 break; 320 else if (r != SSH_ERR_KEY_WRONG_PASSPHRASE) { 321 fprintf(stderr, 322 "Error loading key \"%s\": %s\n", 323 filename, ssh_err(r)); 324 fail_load: 325 clear_pass(); 326 sshbuf_free(keyblob); 327 return -1; 328 } 329 clear_pass(); 330 snprintf(msg, sizeof msg, 331 "Bad passphrase, try again for %s%s: ", filename, 332 confirm ? " (will confirm each use)" : ""); 333 } 334 } 335 if (comment == NULL || *comment == '\0') 336 comment = xstrdup(filename); 337 sshbuf_free(keyblob); 338 339 if (sshkey_is_sk(private)) { 340 if (skprovider == NULL) { 341 fprintf(stderr, "Cannot load FIDO key %s " 342 "without provider\n", filename); 343 goto out; 344 } 345 } else { 346 /* Don't send provider constraint for other keys */ 347 skprovider = NULL; 348 } 349 350 if (!cert_only) { 351 if ((r = ssh_add_identity_constrained(agent_fd, private, 352 comment, lifetime, confirm, skprovider, 353 dest_constraints, ndest_constraints)) == 0) { 354 ret = 0; 355 if (!qflag) { 356 fprintf(stderr, "Identity added: %s (%s)\n", 357 filename, comment); 358 if (lifetime != 0) { 359 fprintf(stderr, "Lifetime set to %s\n", 360 fmt_timeframe((time_t)lifetime)); 361 } 362 if (confirm != 0) { 363 fprintf(stderr, "The user must confirm " 364 "each use of the key\n"); 365 } 366 } 367 } else { 368 fprintf(stderr, "Could not add identity \"%s\": %s\n", 369 filename, ssh_err(r)); 370 } 371 } 372 373 /* Skip trying to load the cert if requested */ 374 if (key_only) 375 goto out; 376 377 /* Now try to add the certificate flavour too */ 378 xasprintf(&certpath, "%s-cert.pub", filename); 379 if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) { 380 if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 381 error_r(r, "Failed to load certificate \"%s\"", 382 certpath); 383 goto out; 384 } 385 386 if (!sshkey_equal_public(cert, private)) { 387 error("Certificate %s does not match private key %s", 388 certpath, filename); 389 goto out; 390 } 391 392 cert_lifetime = lifetime; 393 if (!Nflag && 394 (cert_lifetime = check_cert_lifetime(cert, cert_lifetime)) == -1) { 395 logit("Certificate %s has already expired; ignored", certpath); 396 goto out; 397 } 398 399 /* Graft with private bits */ 400 if ((r = sshkey_to_certified(private)) != 0) { 401 error_fr(r, "sshkey_to_certified"); 402 goto out; 403 } 404 if ((r = sshkey_cert_copy(cert, private)) != 0) { 405 error_fr(r, "sshkey_cert_copy"); 406 goto out; 407 } 408 /* send to agent */ 409 if ((r = ssh_add_identity_constrained(agent_fd, private, comment, 410 cert_lifetime, confirm, skprovider, 411 dest_constraints, ndest_constraints)) != 0) { 412 error_r(r, "Certificate %s (%s) add failed", certpath, 413 private->cert->key_id); 414 goto out; 415 } 416 /* success */ 417 if (!qflag) { 418 fprintf(stderr, "Certificate added: %s (%s)\n", certpath, 419 private->cert->key_id); 420 if (cert_lifetime != 0) { 421 fprintf(stderr, "Lifetime set to %s\n", 422 fmt_timeframe((time_t)cert_lifetime)); 423 } 424 if (confirm != 0) { 425 fprintf(stderr, "The user must confirm each use " 426 "of the key\n"); 427 } 428 } 429 430 out: 431 free(certpath); 432 free(comment); 433 sshkey_free(cert); 434 sshkey_free(private); 435 436 return ret; 437} 438 439static int 440update_card(int agent_fd, int add, const char *id, int qflag, 441 int key_only, int cert_only, 442 struct dest_constraint **dest_constraints, size_t ndest_constraints, 443 struct sshkey **certs, size_t ncerts) 444{ 445 char *pin = NULL; 446 int r, ret = -1; 447 448 if (key_only) 449 ncerts = 0; 450 451 if (add) { 452 if ((pin = read_passphrase("Enter passphrase for PKCS#11: ", 453 RP_ALLOW_STDIN)) == NULL) 454 return -1; 455 } 456 457 if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin, 458 lifetime, confirm, dest_constraints, ndest_constraints, 459 cert_only, certs, ncerts)) == 0) { 460 ret = 0; 461 if (!qflag) { 462 fprintf(stderr, "Card %s: %s\n", 463 add ? "added" : "removed", id); 464 } 465 } else { 466 fprintf(stderr, "Could not %s card \"%s\": %s\n", 467 add ? "add" : "remove", id, ssh_err(r)); 468 ret = -1; 469 } 470 free(pin); 471 return ret; 472} 473 474static int 475test_key(int agent_fd, const char *filename) 476{ 477 struct sshkey *key = NULL; 478 u_char *sig = NULL; 479 const char *alg = NULL; 480 size_t slen = 0; 481 int r, ret = -1; 482 char data[1024]; 483 484 if ((r = sshkey_load_public(filename, &key, NULL)) != 0) { 485 error_r(r, "Couldn't read public key %s", filename); 486 return -1; 487 } 488 if (sshkey_type_plain(key->type) == KEY_RSA) 489 alg = "rsa-sha2-256"; 490 arc4random_buf(data, sizeof(data)); 491 if ((r = ssh_agent_sign(agent_fd, key, &sig, &slen, data, sizeof(data), 492 alg, 0)) != 0) { 493 error_r(r, "Agent signature failed for %s", filename); 494 goto done; 495 } 496 if ((r = sshkey_verify(key, sig, slen, data, sizeof(data), 497 alg, 0, NULL)) != 0) { 498 error_r(r, "Signature verification failed for %s", filename); 499 goto done; 500 } 501 /* success */ 502 ret = 0; 503 done: 504 free(sig); 505 sshkey_free(key); 506 return ret; 507} 508 509static int 510list_identities(int agent_fd, int do_fp) 511{ 512 char *fp; 513 int r; 514 struct ssh_identitylist *idlist; 515 size_t i; 516 517 if ((r = ssh_fetch_identitylist(agent_fd, &idlist)) != 0) { 518 if (r != SSH_ERR_AGENT_NO_IDENTITIES) 519 fprintf(stderr, "error fetching identities: %s\n", 520 ssh_err(r)); 521 else 522 printf("The agent has no identities.\n"); 523 return -1; 524 } 525 for (i = 0; i < idlist->nkeys; i++) { 526 if (do_fp) { 527 fp = sshkey_fingerprint(idlist->keys[i], 528 fingerprint_hash, SSH_FP_DEFAULT); 529 printf("%u %s %s (%s)\n", sshkey_size(idlist->keys[i]), 530 fp == NULL ? "(null)" : fp, idlist->comments[i], 531 sshkey_type(idlist->keys[i])); 532 free(fp); 533 } else { 534 if ((r = sshkey_write(idlist->keys[i], stdout)) != 0) { 535 fprintf(stderr, "sshkey_write: %s\n", 536 ssh_err(r)); 537 continue; 538 } 539 fprintf(stdout, " %s\n", idlist->comments[i]); 540 } 541 } 542 ssh_free_identitylist(idlist); 543 return 0; 544} 545 546static int 547lock_agent(int agent_fd, int lock) 548{ 549 char prompt[100], *p1, *p2; 550 int r, passok = 1, ret = -1; 551 552 strlcpy(prompt, "Enter lock password: ", sizeof(prompt)); 553 p1 = read_passphrase(prompt, RP_ALLOW_STDIN); 554 if (lock) { 555 strlcpy(prompt, "Again: ", sizeof prompt); 556 p2 = read_passphrase(prompt, RP_ALLOW_STDIN); 557 if (strcmp(p1, p2) != 0) { 558 fprintf(stderr, "Passwords do not match.\n"); 559 passok = 0; 560 } 561 freezero(p2, strlen(p2)); 562 } 563 if (passok) { 564 if ((r = ssh_lock_agent(agent_fd, lock, p1)) == 0) { 565 fprintf(stderr, "Agent %slocked.\n", lock ? "" : "un"); 566 ret = 0; 567 } else { 568 fprintf(stderr, "Failed to %slock agent: %s\n", 569 lock ? "" : "un", ssh_err(r)); 570 } 571 } 572 freezero(p1, strlen(p1)); 573 return (ret); 574} 575 576static int 577load_resident_keys(int agent_fd, const char *skprovider, int qflag, 578 struct dest_constraint **dest_constraints, size_t ndest_constraints) 579{ 580 struct sshsk_resident_key **srks; 581 size_t nsrks, i; 582 struct sshkey *key; 583 int r, ok = 0; 584 char *fp; 585 586 pass = read_passphrase("Enter PIN for authenticator: ", RP_ALLOW_STDIN); 587 if ((r = sshsk_load_resident(skprovider, NULL, pass, 0, 588 &srks, &nsrks)) != 0) { 589 error_r(r, "Unable to load resident keys"); 590 return r; 591 } 592 for (i = 0; i < nsrks; i++) { 593 key = srks[i]->key; 594 if ((fp = sshkey_fingerprint(key, 595 fingerprint_hash, SSH_FP_DEFAULT)) == NULL) 596 fatal_f("sshkey_fingerprint failed"); 597 if ((r = ssh_add_identity_constrained(agent_fd, key, 598 key->sk_application, lifetime, confirm, skprovider, 599 dest_constraints, ndest_constraints)) != 0) { 600 error("Unable to add key %s %s", 601 sshkey_type(key), fp); 602 free(fp); 603 ok = r; 604 continue; 605 } 606 if (ok == 0) 607 ok = 1; 608 if (!qflag) { 609 fprintf(stderr, "Resident identity added: %s %s\n", 610 sshkey_type(key), fp); 611 if (lifetime != 0) { 612 fprintf(stderr, 613 "Lifetime set to %d seconds\n", lifetime); 614 } 615 if (confirm != 0) { 616 fprintf(stderr, "The user must confirm " 617 "each use of the key\n"); 618 } 619 } 620 free(fp); 621 } 622 sshsk_free_resident_keys(srks, nsrks); 623 if (nsrks == 0) 624 return SSH_ERR_KEY_NOT_FOUND; 625 return ok == 1 ? 0 : ok; 626} 627 628static int 629do_file(int agent_fd, int deleting, int key_only, int cert_only, 630 char *file, int qflag, int Nflag, const char *skprovider, 631 struct dest_constraint **dest_constraints, size_t ndest_constraints) 632{ 633 if (deleting) { 634 if (delete_file(agent_fd, file, key_only, 635 cert_only, qflag) == -1) 636 return -1; 637 } else { 638 if (add_file(agent_fd, file, key_only, cert_only, qflag, Nflag, 639 skprovider, dest_constraints, ndest_constraints) == -1) 640 return -1; 641 } 642 return 0; 643} 644 645static void 646free_dest_constraint_hop(struct dest_constraint_hop *dch) 647{ 648 u_int i; 649 650 if (dch == NULL) 651 return; 652 free(dch->user); 653 free(dch->hostname); 654 for (i = 0; i < dch->nkeys; i++) 655 sshkey_free(dch->keys[i]); 656 free(dch->keys); 657 free(dch->key_is_ca); 658} 659 660static void 661free_dest_constraints(struct dest_constraint **dcs, size_t ndcs) 662{ 663 size_t i; 664 665 for (i = 0; i < ndcs; i++) { 666 free_dest_constraint_hop(&dcs[i]->from); 667 free_dest_constraint_hop(&dcs[i]->to); 668 free(dcs[i]); 669 } 670 free(dcs); 671} 672 673 674static void 675parse_dest_constraint_hop(const char *s, struct dest_constraint_hop *dch, 676 char **hostkey_files) 677{ 678 char *user = NULL, *host, *os, *path; 679 size_t i; 680 struct hostkeys *hostkeys; 681 const struct hostkey_entry *hke; 682 int r, want_ca; 683 684 memset(dch, '\0', sizeof(*dch)); 685 os = xstrdup(s); 686 if ((host = strrchr(os, '@')) == NULL) 687 host = os; 688 else { 689 *host++ = '\0'; 690 user = os; 691 } 692 cleanhostname(host); 693 /* Trivial case: username@ (all hosts) */ 694 if (*host == '\0') { 695 if (user == NULL) { 696 fatal("Invalid key destination constraint \"%s\": " 697 "does not specify user or host", s); 698 } 699 dch->user = xstrdup(user); 700 /* other fields left blank */ 701 free(os); 702 return; 703 } 704 if (hostkey_files == NULL) 705 fatal_f("no hostkey files"); 706 /* Otherwise we need to look up the keys for this hostname */ 707 hostkeys = init_hostkeys(); 708 for (i = 0; hostkey_files[i]; i++) { 709 path = tilde_expand_filename(hostkey_files[i], getuid()); 710 debug2_f("looking up host keys for \"%s\" in %s", host, path); 711 load_hostkeys(hostkeys, host, path, 0); 712 free(path); 713 } 714 dch->user = user == NULL ? NULL : xstrdup(user); 715 dch->hostname = xstrdup(host); 716 for (i = 0; i < hostkeys->num_entries; i++) { 717 hke = hostkeys->entries + i; 718 want_ca = hke->marker == MRK_CA; 719 if (hke->marker != MRK_NONE && !want_ca) 720 continue; 721 debug3_f("%s%s%s: adding %s %skey from %s:%lu as key %u", 722 user == NULL ? "": user, user == NULL ? "" : "@", 723 host, sshkey_type(hke->key), want_ca ? "CA " : "", 724 hke->file, hke->line, dch->nkeys); 725 dch->keys = xrecallocarray(dch->keys, dch->nkeys, 726 dch->nkeys + 1, sizeof(*dch->keys)); 727 dch->key_is_ca = xrecallocarray(dch->key_is_ca, dch->nkeys, 728 dch->nkeys + 1, sizeof(*dch->key_is_ca)); 729 if ((r = sshkey_from_private(hke->key, 730 &(dch->keys[dch->nkeys]))) != 0) 731 fatal_fr(r, "sshkey_from_private"); 732 dch->key_is_ca[dch->nkeys] = want_ca; 733 dch->nkeys++; 734 } 735 if (dch->nkeys == 0) 736 fatal("No host keys found for destination \"%s\"", host); 737 free_hostkeys(hostkeys); 738 free(os); 739 return; 740} 741 742static void 743parse_dest_constraint(const char *s, struct dest_constraint ***dcp, 744 size_t *ndcp, char **hostkey_files) 745{ 746 struct dest_constraint *dc; 747 char *os, *cp; 748 749 dc = xcalloc(1, sizeof(*dc)); 750 os = xstrdup(s); 751 if ((cp = strchr(os, '>')) == NULL) { 752 /* initial hop; no 'from' hop specified */ 753 parse_dest_constraint_hop(os, &dc->to, hostkey_files); 754 } else { 755 /* two hops specified */ 756 *(cp++) = '\0'; 757 parse_dest_constraint_hop(os, &dc->from, hostkey_files); 758 parse_dest_constraint_hop(cp, &dc->to, hostkey_files); 759 if (dc->from.user != NULL) { 760 fatal("Invalid key constraint %s: cannot specify " 761 "user on 'from' host", os); 762 } 763 } 764 /* XXX eliminate or error on duplicates */ 765 debug2_f("constraint %zu: %s%s%s (%u keys) > %s%s%s (%u keys)", *ndcp, 766 dc->from.user ? dc->from.user : "", dc->from.user ? "@" : "", 767 dc->from.hostname ? dc->from.hostname : "(ORIGIN)", dc->from.nkeys, 768 dc->to.user ? dc->to.user : "", dc->to.user ? "@" : "", 769 dc->to.hostname ? dc->to.hostname : "(ANY)", dc->to.nkeys); 770 *dcp = xrecallocarray(*dcp, *ndcp, *ndcp + 1, sizeof(**dcp)); 771 (*dcp)[(*ndcp)++] = dc; 772 free(os); 773} 774 775 776static void 777usage(void) 778{ 779 fprintf(stderr, 780"usage: ssh-add [-CcDdKkLlqvXx] [-E fingerprint_hash] [-H hostkey_file]\n" 781" [-h destination_constraint] [-S provider] [-t life]\n" 782" [file ...]\n" 783" ssh-add -s pkcs11 [-Cv] [certificate ...]\n" 784" ssh-add -e pkcs11\n" 785" ssh-add -T pubkey ...\n" 786 ); 787} 788 789int 790main(int argc, char **argv) 791{ 792 extern char *optarg; 793 extern int optind; 794 int agent_fd; 795 char *pkcs11provider = NULL, *skprovider = NULL; 796 char **dest_constraint_strings = NULL, **hostkey_files = NULL; 797 int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0; 798 int do_download = 0, xflag = 0, lflag = 0, Dflag = 0; 799 int qflag = 0, Tflag = 0, Nflag = 0; 800 SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; 801 LogLevel log_level = SYSLOG_LEVEL_INFO; 802 struct sshkey *k, **certs = NULL; 803 struct dest_constraint **dest_constraints = NULL; 804 size_t n, ndest_constraints = 0, ncerts = 0; 805 806 /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ 807 sanitise_stdfd(); 808 809 log_init(__progname, log_level, log_facility, 1); 810 811 setvbuf(stdout, NULL, _IOLBF, 0); 812 813 /* First, get a connection to the authentication agent. */ 814 switch (r = ssh_get_authentication_socket(&agent_fd)) { 815 case 0: 816 break; 817 case SSH_ERR_AGENT_NOT_PRESENT: 818 fprintf(stderr, "Could not open a connection to your " 819 "authentication agent.\n"); 820 exit(2); 821 default: 822 fprintf(stderr, "Error connecting to agent: %s\n", ssh_err(r)); 823 exit(2); 824 } 825 826 skprovider = getenv("SSH_SK_PROVIDER"); 827 828 while ((ch = getopt(argc, argv, "vkKlLNCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { 829 switch (ch) { 830 case 'v': 831 if (log_level == SYSLOG_LEVEL_INFO) 832 log_level = SYSLOG_LEVEL_DEBUG1; 833 else if (log_level < SYSLOG_LEVEL_DEBUG3) 834 log_level++; 835 break; 836 case 'N': 837 Nflag = 1; 838 break; 839 case 'E': 840 fingerprint_hash = ssh_digest_alg_by_name(optarg); 841 if (fingerprint_hash == -1) 842 fatal("Invalid hash algorithm \"%s\"", optarg); 843 break; 844 case 'H': 845 stringlist_append(&hostkey_files, optarg); 846 break; 847 case 'h': 848 stringlist_append(&dest_constraint_strings, optarg); 849 break; 850 case 'k': 851 key_only = 1; 852 break; 853 case 'C': 854 cert_only = 1; 855 break; 856 case 'K': 857 do_download = 1; 858 break; 859 case 'l': 860 case 'L': 861 if (lflag != 0) 862 fatal("-%c flag already specified", lflag); 863 lflag = ch; 864 break; 865 case 'x': 866 case 'X': 867 if (xflag != 0) 868 fatal("-%c flag already specified", xflag); 869 xflag = ch; 870 break; 871 case 'c': 872 confirm = 1; 873 break; 874 case 'm': 875 case 'M': 876 /* deprecated */ 877 break; 878 case 'd': 879 deleting = 1; 880 break; 881 case 'D': 882 Dflag = 1; 883 break; 884 case 's': 885 pkcs11provider = optarg; 886 break; 887 case 'S': 888 skprovider = optarg; 889 break; 890 case 'e': 891 deleting = 1; 892 pkcs11provider = optarg; 893 break; 894 case 't': 895 if ((lifetime = convtime(optarg)) == -1 || 896 lifetime < 0 || (u_long)lifetime > UINT32_MAX) { 897 fprintf(stderr, "Invalid lifetime\n"); 898 ret = 1; 899 goto done; 900 } 901 break; 902 case 'q': 903 qflag = 1; 904 break; 905 case 'T': 906 Tflag = 1; 907 break; 908 default: 909 usage(); 910 ret = 1; 911 goto done; 912 } 913 } 914 log_init(__progname, log_level, log_facility, 1); 915 916 if ((xflag != 0) + (lflag != 0) + (Dflag != 0) > 1) 917 fatal("Invalid combination of actions"); 918 else if (xflag) { 919 if (lock_agent(agent_fd, xflag == 'x' ? 1 : 0) == -1) 920 ret = 1; 921 goto done; 922 } else if (lflag) { 923 if (list_identities(agent_fd, lflag == 'l' ? 1 : 0) == -1) 924 ret = 1; 925 goto done; 926 } else if (Dflag) { 927 if (delete_all(agent_fd, qflag) == -1) 928 ret = 1; 929 goto done; 930 } 931 932 if (skprovider == NULL) 933 skprovider = "internal"; 934 if (hostkey_files == NULL) { 935 /* use defaults from readconf.c */ 936 stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE); 937 stringlist_append(&hostkey_files, _PATH_SSH_USER_HOSTFILE2); 938 stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE); 939 stringlist_append(&hostkey_files, _PATH_SSH_SYSTEM_HOSTFILE2); 940 } 941 if (dest_constraint_strings != NULL) { 942 for (i = 0; dest_constraint_strings[i] != NULL; i++) { 943 parse_dest_constraint(dest_constraint_strings[i], 944 &dest_constraints, &ndest_constraints, hostkey_files); 945 } 946 } 947 948 argc -= optind; 949 argv += optind; 950 if (Tflag) { 951 if (argc <= 0) 952 fatal("no keys to test"); 953 for (r = i = 0; i < argc; i++) 954 r |= test_key(agent_fd, argv[i]); 955 ret = r == 0 ? 0 : 1; 956 goto done; 957 } 958 if (pkcs11provider != NULL) { 959 for (i = 0; i < argc; i++) { 960 if ((r = sshkey_load_public(argv[i], &k, NULL)) != 0) 961 fatal_fr(r, "load certificate %s", argv[i]); 962 certs = xrecallocarray(certs, ncerts, ncerts + 1, 963 sizeof(*certs)); 964 debug2("%s: %s", argv[i], sshkey_ssh_name(k)); 965 certs[ncerts++] = k; 966 } 967 debug2_f("loaded %zu certificates", ncerts); 968 if (update_card(agent_fd, !deleting, pkcs11provider, 969 qflag, key_only, cert_only, 970 dest_constraints, ndest_constraints, 971 certs, ncerts) == -1) 972 ret = 1; 973 for (n = 0; n < ncerts; n++) 974 sshkey_free(certs[n]); 975 free(certs); 976 goto done; 977 } 978 if (do_download) { 979 if (skprovider == NULL) 980 fatal("Cannot download keys without provider"); 981 if (load_resident_keys(agent_fd, skprovider, qflag, 982 dest_constraints, ndest_constraints) != 0) 983 ret = 1; 984 goto done; 985 } 986 if (argc == 0) { 987 char buf[PATH_MAX]; 988 struct passwd *pw; 989 struct stat st; 990 int count = 0; 991 992 if ((pw = getpwuid(getuid())) == NULL) { 993 fprintf(stderr, "No user found with uid %u\n", 994 (u_int)getuid()); 995 ret = 1; 996 goto done; 997 } 998 999 for (i = 0; default_files[i]; i++) { 1000 snprintf(buf, sizeof(buf), "%s/%s", pw->pw_dir, 1001 default_files[i]); 1002 if (stat(buf, &st) == -1) 1003 continue; 1004 if (do_file(agent_fd, deleting, key_only, cert_only, 1005 buf, qflag, Nflag, skprovider, 1006 dest_constraints, ndest_constraints) == -1) 1007 ret = 1; 1008 else 1009 count++; 1010 } 1011 if (count == 0) 1012 ret = 1; 1013 } else { 1014 for (i = 0; i < argc; i++) { 1015 if (do_file(agent_fd, deleting, key_only, cert_only, 1016 argv[i], qflag, Nflag, skprovider, 1017 dest_constraints, ndest_constraints) == -1) 1018 ret = 1; 1019 } 1020 } 1021done: 1022 clear_pass(); 1023 stringlist_free(hostkey_files); 1024 stringlist_free(dest_constraint_strings); 1025 free_dest_constraints(dest_constraints, ndest_constraints); 1026 ssh_close_authentication_socket(agent_fd); 1027 return ret; 1028}