summaryrefslogtreecommitdiff
path: root/ssh-keygen.c
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2019-09-03 08:34:19 +0000
committerDamien Miller <djm@mindrot.org>2019-09-03 18:40:23 +1000
commit2a9c9f7272c1e8665155118fe6536bebdafb6166 (patch)
tree177a8c032d9396249708e4a5cb65321d9250fdee /ssh-keygen.c
parent5485f8d50a5bc46aeed829075ebf5d9c617027ea (diff)
upstream: sshsig: lightweight signature and verification ability
for OpenSSH This adds a simple manual signature scheme to OpenSSH. Signatures can be made and verified using ssh-keygen -Y sign|verify Signatures embed the key used to make them. At verification time, this is matched via principal name against an authorized_keys-like list of allowed signers. Mostly by Sebastian Kinne w/ some tweaks by me ok markus@ OpenBSD-Commit-ID: 2ab568e7114c933346616392579d72be65a4b8fb
Diffstat (limited to 'ssh-keygen.c')
-rw-r--r--ssh-keygen.c325
1 files changed, 321 insertions, 4 deletions
diff --git a/ssh-keygen.c b/ssh-keygen.c
index 3c32513bf..76bc41b2f 100644
--- a/ssh-keygen.c
+++ b/ssh-keygen.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: ssh-keygen.c,v 1.343 2019/09/03 08:27:52 djm Exp $ */ 1/* $OpenBSD: ssh-keygen.c,v 1.344 2019/09/03 08:34:19 djm Exp $ */
2/* 2/*
3 * Author: Tatu Ylonen <ylo@cs.hut.fi> 3 * Author: Tatu Ylonen <ylo@cs.hut.fi>
4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland 4 * Copyright (c) 1994 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -59,6 +59,7 @@
59#include "digest.h" 59#include "digest.h"
60#include "utf8.h" 60#include "utf8.h"
61#include "authfd.h" 61#include "authfd.h"
62#include "sshsig.h"
62 63
63#ifdef WITH_OPENSSL 64#ifdef WITH_OPENSSL
64# define DEFAULT_KEY_TYPE_NAME "rsa" 65# define DEFAULT_KEY_TYPE_NAME "rsa"
@@ -2422,6 +2423,279 @@ do_check_krl(struct passwd *pw, int argc, char **argv)
2422 exit(ret); 2423 exit(ret);
2423} 2424}
2424 2425
2426static struct sshkey *
2427load_sign_key(const char *keypath, const struct sshkey *pubkey)
2428{
2429 size_t i, slen, plen = strlen(keypath);
2430 char *privpath = xstrdup(keypath);
2431 const char *suffixes[] = { "-cert.pub", ".pub", NULL };
2432 struct sshkey *ret = NULL, *privkey = NULL;
2433 int r;
2434
2435 /*
2436 * If passed a public key filename, then try to locate the correponding
2437 * private key. This lets us specify certificates on the command-line
2438 * and have ssh-keygen find the appropriate private key.
2439 */
2440 for (i = 0; suffixes[i]; i++) {
2441 slen = strlen(suffixes[i]);
2442 if (plen <= slen ||
2443 strcmp(privpath + plen - slen, suffixes[i]) != 0)
2444 continue;
2445 privpath[plen - slen] = '\0';
2446 debug("%s: %s looks like a public key, using private key "
2447 "path %s instead", __func__, keypath, privpath);
2448 }
2449 if ((privkey = load_identity(privpath, NULL)) == NULL) {
2450 error("Couldn't load identity %s", keypath);
2451 goto done;
2452 }
2453 if (!sshkey_equal_public(pubkey, privkey)) {
2454 error("Public key %s doesn't match private %s",
2455 keypath, privpath);
2456 goto done;
2457 }
2458 if (sshkey_is_cert(pubkey) && !sshkey_is_cert(privkey)) {
2459 /*
2460 * Graft the certificate onto the private key to make
2461 * it capable of signing.
2462 */
2463 if ((r = sshkey_to_certified(privkey)) != 0) {
2464 error("%s: sshkey_to_certified: %s", __func__,
2465 ssh_err(r));
2466 goto done;
2467 }
2468 if ((r = sshkey_cert_copy(pubkey, privkey)) != 0) {
2469 error("%s: sshkey_cert_copy: %s", __func__, ssh_err(r));
2470 goto done;
2471 }
2472 }
2473 /* success */
2474 ret = privkey;
2475 privkey = NULL;
2476 done:
2477 sshkey_free(privkey);
2478 free(privpath);
2479 return ret;
2480}
2481
2482static int
2483sign_one(struct sshkey *signkey, const char *filename, int fd,
2484 const char *sig_namespace, sshsig_signer *signer, void *signer_ctx)
2485{
2486 struct sshbuf *sigbuf = NULL, *abuf = NULL;
2487 int r = SSH_ERR_INTERNAL_ERROR, wfd = -1, oerrno;
2488 char *wfile = NULL;
2489 char *asig = NULL;
2490
2491 if (!quiet) {
2492 if (fd == STDIN_FILENO)
2493 fprintf(stderr, "Signing data on standard input\n");
2494 else
2495 fprintf(stderr, "Signing file %s\n", filename);
2496 }
2497 if ((r = sshsig_sign_fd(signkey, NULL, fd, sig_namespace,
2498 &sigbuf, signer, signer_ctx)) != 0) {
2499 error("Signing %s failed: %s", filename, ssh_err(r));
2500 goto out;
2501 }
2502 if ((r = sshsig_armor(sigbuf, &abuf)) != 0) {
2503 error("%s: sshsig_armor: %s", __func__, ssh_err(r));
2504 goto out;
2505 }
2506 if ((asig = sshbuf_dup_string(abuf)) == NULL) {
2507 error("%s: buffer error", __func__);
2508 r = SSH_ERR_ALLOC_FAIL;
2509 goto out;
2510 }
2511
2512 if (fd == STDIN_FILENO) {
2513 fputs(asig, stdout);
2514 fflush(stdout);
2515 } else {
2516 xasprintf(&wfile, "%s.sig", filename);
2517 if (confirm_overwrite(wfile)) {
2518 if ((wfd = open(wfile, O_WRONLY|O_CREAT|O_TRUNC,
2519 0666)) == -1) {
2520 oerrno = errno;
2521 error("Cannot open %s: %s",
2522 wfile, strerror(errno));
2523 errno = oerrno;
2524 r = SSH_ERR_SYSTEM_ERROR;
2525 goto out;
2526 }
2527 if (atomicio(vwrite, wfd, asig,
2528 strlen(asig)) != strlen(asig)) {
2529 oerrno = errno;
2530 error("Cannot write to %s: %s",
2531 wfile, strerror(errno));
2532 errno = oerrno;
2533 r = SSH_ERR_SYSTEM_ERROR;
2534 goto out;
2535 }
2536 if (!quiet) {
2537 fprintf(stderr, "Write signature to %s\n",
2538 wfile);
2539 }
2540 }
2541 }
2542 /* success */
2543 r = 0;
2544 out:
2545 free(wfile);
2546 free(asig);
2547 sshbuf_free(abuf);
2548 sshbuf_free(sigbuf);
2549 if (wfd != -1)
2550 close(wfd);
2551 return r;
2552}
2553
2554static int
2555sign(const char *keypath, const char *sig_namespace, int argc, char **argv)
2556{
2557 int i, fd = -1, r, ret = -1;
2558 int agent_fd = -1;
2559 struct sshkey *pubkey = NULL, *privkey = NULL, *signkey = NULL;
2560 sshsig_signer *signer = NULL;
2561
2562 /* Check file arguments. */
2563 for (i = 0; i < argc; i++) {
2564 if (strcmp(argv[i], "-") != 0)
2565 continue;
2566 if (i > 0 || argc > 1)
2567 fatal("Cannot sign mix of paths and standard input");
2568 }
2569
2570 if ((r = sshkey_load_public(keypath, &pubkey, NULL)) != 0) {
2571 error("Couldn't load public key %s: %s", keypath, ssh_err(r));
2572 goto done;
2573 }
2574
2575 if ((r = ssh_get_authentication_socket(&agent_fd)) != 0)
2576 debug("Couldn't get agent socket: %s", ssh_err(r));
2577 else {
2578 if ((r = ssh_agent_has_key(agent_fd, pubkey)) == 0)
2579 signer = agent_signer;
2580 else
2581 debug("Couldn't find key in agent: %s", ssh_err(r));
2582 }
2583
2584 if (signer == NULL) {
2585 /* Not using agent - try to load private key */
2586 if ((privkey = load_sign_key(keypath, pubkey)) == NULL)
2587 goto done;
2588 signkey = privkey;
2589 } else {
2590 /* Will use key in agent */
2591 signkey = pubkey;
2592 }
2593
2594 if (argc == 0) {
2595 if ((r = sign_one(signkey, "(stdin)", STDIN_FILENO,
2596 sig_namespace, signer, &agent_fd)) != 0)
2597 goto done;
2598 } else {
2599 for (i = 0; i < argc; i++) {
2600 if (strcmp(argv[i], "-") == 0)
2601 fd = STDIN_FILENO;
2602 else if ((fd = open(argv[i], O_RDONLY)) == -1) {
2603 error("Cannot open %s for signing: %s",
2604 argv[i], strerror(errno));
2605 goto done;
2606 }
2607 if ((r = sign_one(signkey, argv[i], fd, sig_namespace,
2608 signer, &agent_fd)) != 0)
2609 goto done;
2610 if (fd != STDIN_FILENO)
2611 close(fd);
2612 fd = -1;
2613 }
2614 }
2615
2616 ret = 0;
2617done:
2618 if (fd != -1 && fd != STDIN_FILENO)
2619 close(fd);
2620 sshkey_free(pubkey);
2621 sshkey_free(privkey);
2622 return ret;
2623}
2624
2625static int
2626verify(const char *signature, const char *sig_namespace, const char *principal,
2627 const char *allowed_keys, const char *revoked_keys)
2628{
2629 int r, ret = -1, sigfd = -1;
2630 struct sshbuf *sigbuf = NULL, *abuf = NULL;
2631 struct sshkey *sign_key = NULL;
2632 char *fp = NULL;
2633
2634 if ((abuf = sshbuf_new()) == NULL)
2635 fatal("%s: sshbuf_new() failed", __func__);
2636
2637 if ((sigfd = open(signature, O_RDONLY)) < 0) {
2638 error("Couldn't open signature file %s", signature);
2639 goto done;
2640 }
2641
2642 if ((r = sshkey_load_file(sigfd, abuf)) != 0) {
2643 error("Couldn't read signature file: %s", ssh_err(r));
2644 goto done;
2645 }
2646 if ((r = sshsig_dearmor(abuf, &sigbuf)) != 0) {
2647 error("%s: sshsig_armor: %s", __func__, ssh_err(r));
2648 return r;
2649 }
2650 if ((r = sshsig_verify_fd(sigbuf, STDIN_FILENO, sig_namespace,
2651 &sign_key)) != 0)
2652 goto done; /* sshsig_verify() prints error */
2653
2654 if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
2655 SSH_FP_DEFAULT)) == NULL)
2656 fatal("%s: sshkey_fingerprint failed", __func__);
2657 debug("Valid (unverified) signature from key %s", fp);
2658 free(fp);
2659 fp = NULL;
2660
2661 if (revoked_keys != NULL) {
2662 if ((r = sshkey_check_revoked(sign_key, revoked_keys)) != 0) {
2663 debug3("sshkey_check_revoked failed: %s", ssh_err(r));
2664 goto done;
2665 }
2666 }
2667
2668 if ((r = sshsig_check_allowed_keys(allowed_keys, sign_key,
2669 principal, sig_namespace)) != 0) {
2670 debug3("sshsig_check_allowed_keys failed: %s", ssh_err(r));
2671 goto done;
2672 }
2673 /* success */
2674 ret = 0;
2675done:
2676 if (!quiet) {
2677 if (ret == 0) {
2678 if ((fp = sshkey_fingerprint(sign_key, fingerprint_hash,
2679 SSH_FP_DEFAULT)) == NULL) {
2680 fatal("%s: sshkey_fingerprint failed",
2681 __func__);
2682 }
2683 printf("Good \"%s\" signature for %s with %s key %s\n",
2684 sig_namespace, principal,
2685 sshkey_type(sign_key), fp);
2686 } else {
2687 printf("Could not verify signature.\n");
2688 }
2689 }
2690 if (sigfd != -1)
2691 close(sigfd);
2692 sshbuf_free(sigbuf);
2693 sshbuf_free(abuf);
2694 sshkey_free(sign_key);
2695 free(fp);
2696 return ret;
2697}
2698
2425static void 2699static void
2426usage(void) 2700usage(void)
2427{ 2701{
@@ -2457,7 +2731,10 @@ usage(void)
2457 " ssh-keygen -A\n" 2731 " ssh-keygen -A\n"
2458 " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n" 2732 " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n"
2459 " file ...\n" 2733 " file ...\n"
2460 " ssh-keygen -Q -f krl_file file ...\n"); 2734 " ssh-keygen -Q -f le file ...\n"
2735 " ssh-keygen -Y sign -f sign_key -n namespace\n"
2736 " ssh-keygen -Y verify -I signer_identity -s signature_file\n"
2737 " -n namespace -f allowed_keys [-r revoked_keys]\n");
2461 exit(1); 2738 exit(1);
2462} 2739}
2463 2740
@@ -2484,6 +2761,7 @@ main(int argc, char **argv)
2484 FILE *f; 2761 FILE *f;
2485 const char *errstr; 2762 const char *errstr;
2486 int log_level = SYSLOG_LEVEL_INFO; 2763 int log_level = SYSLOG_LEVEL_INFO;
2764 char *sign_op = NULL;
2487#ifdef WITH_OPENSSL 2765#ifdef WITH_OPENSSL
2488 /* Moduli generation/screening */ 2766 /* Moduli generation/screening */
2489 char out_file[PATH_MAX], *checkpoint = NULL; 2767 char out_file[PATH_MAX], *checkpoint = NULL;
@@ -2514,9 +2792,9 @@ main(int argc, char **argv)
2514 if (gethostname(hostname, sizeof(hostname)) == -1) 2792 if (gethostname(hostname, sizeof(hostname)) == -1)
2515 fatal("gethostname: %s", strerror(errno)); 2793 fatal("gethostname: %s", strerror(errno));
2516 2794
2517 /* Remaining characters: Ydw */ 2795 /* Remaining characters: dw */
2518 while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy" 2796 while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy"
2519 "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Z:" 2797 "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Y:Z:"
2520 "a:b:f:g:j:m:n:r:s:t:z:")) != -1) { 2798 "a:b:f:g:j:m:n:r:s:t:z:")) != -1) {
2521 switch (opt) { 2799 switch (opt) {
2522 case 'A': 2800 case 'A':
@@ -2672,6 +2950,9 @@ main(int argc, char **argv)
2672 case 'V': 2950 case 'V':
2673 parse_cert_times(optarg); 2951 parse_cert_times(optarg);
2674 break; 2952 break;
2953 case 'Y':
2954 sign_op = optarg;
2955 break;
2675 case 'z': 2956 case 'z':
2676 errno = 0; 2957 errno = 0;
2677 if (*optarg == '+') { 2958 if (*optarg == '+') {
@@ -2739,6 +3020,42 @@ main(int argc, char **argv)
2739 argv += optind; 3020 argv += optind;
2740 argc -= optind; 3021 argc -= optind;
2741 3022
3023 if (sign_op != NULL) {
3024 if (cert_principals == NULL) {
3025 error("Too few arguments for sign/verify: "
3026 "missing namespace");
3027 exit(1);
3028 }
3029 if (strncmp(sign_op, "sign", 4) == 0) {
3030 if (!have_identity) {
3031 error("Too few arguments for sign: "
3032 "missing key");
3033 exit(1);
3034 }
3035 return sign(identity_file, cert_principals, argc, argv);
3036 } else if (strncmp(sign_op, "verify", 6) == 0) {
3037 if (ca_key_path == NULL) {
3038 error("Too few arguments for verify: "
3039 "missing signature file");
3040 exit(1);
3041 }
3042 if (!have_identity) {
3043 error("Too few arguments for sign: "
3044 "missing allowed keys file");
3045 exit(1);
3046 }
3047 if (cert_key_id == NULL) {
3048 error("Too few arguments for verify: "
3049 "missing principal ID");
3050 exit(1);
3051 }
3052 return verify(ca_key_path, cert_principals,
3053 cert_key_id, identity_file, rr_hostname);
3054 }
3055 usage();
3056 /* NOTREACHED */
3057 }
3058
2742 if (ca_key_path != NULL) { 3059 if (ca_key_path != NULL) {
2743 if (argc < 1 && !gen_krl) { 3060 if (argc < 1 && !gen_krl) {
2744 error("Too few arguments."); 3061 error("Too few arguments.");