=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/ssh-keygen.c,v retrieving revision 1.280 retrieving revision 1.313 diff -u -r1.280 -r1.313 --- src/usr.bin/ssh/ssh-keygen.c 2015/11/18 08:37:28 1.280 +++ src/usr.bin/ssh/ssh-keygen.c 2018/02/23 15:58:38 1.313 @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.280 2015/11/18 08:37:28 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.313 2018/02/23 15:58:38 markus Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -28,10 +28,10 @@ #include #include #include +#include #include "xmalloc.h" #include "sshkey.h" -#include "rsa.h" #include "authfile.h" #include "uuencode.h" #include "sshbuf.h" @@ -47,6 +47,8 @@ #include "atomicio.h" #include "krl.h" #include "digest.h" +#include "utf8.h" +#include "authfd.h" #ifdef ENABLE_PKCS11 #include "ssh-pkcs11.h" @@ -113,6 +115,9 @@ /* Path to CA key when certifying keys. */ char *ca_key_path = NULL; +/* Prefer to use agent keys for CA signing */ +int prefer_agent = 0; + /* Certificate serial number */ unsigned long long cert_serial = 0; @@ -141,6 +146,15 @@ char *certflags_command = NULL; char *certflags_src_addr = NULL; +/* Arbitrary extensions specified by user */ +struct cert_userext { + char *key; + char *val; + int crit; +}; +struct cert_userext *cert_userext; +size_t ncert_userext; + /* Conversion to/from various formats */ int convert_to = 0; int convert_from = 0; @@ -200,8 +214,7 @@ *bitsp = sshkey_curve_nid_to_bits(nid); if (*bitsp == 0) *bitsp = DEFAULT_BITS_ECDSA; - } - else + } else #endif *bitsp = DEFAULT_BITS; } @@ -210,13 +223,21 @@ OPENSSL_DSA_MAX_MODULUS_BITS : OPENSSL_RSA_MAX_MODULUS_BITS; if (*bitsp > maxbits) fatal("key bits exceeds maximum %d", maxbits); - if (type == KEY_DSA && *bitsp != 1024) - fatal("DSA keys must be 1024 bits"); - else if (type != KEY_ECDSA && type != KEY_ED25519 && *bitsp < 1024) - fatal("Key must at least be 1024 bits"); - else if (type == KEY_ECDSA && sshkey_ecdsa_bits_to_nid(*bitsp) == -1) - fatal("Invalid ECDSA key length - valid lengths are " - "256, 384 or 521 bits"); + switch (type) { + case KEY_DSA: + if (*bitsp != 1024) + fatal("Invalid DSA key length: must be 1024 bits"); + break; + case KEY_RSA: + if (*bitsp < SSH_RSA_MINIMUM_MODULUS_SIZE) + fatal("Invalid RSA key length: minimum is %d bits", + SSH_RSA_MINIMUM_MODULUS_SIZE); + break; + case KEY_ECDSA: + if (sshkey_ecdsa_bits_to_nid(*bitsp) == -1) + fatal("Invalid ECDSA key length: valid lengths are " + "256, 384 or 521 bits"); + } #endif } @@ -230,9 +251,6 @@ name = _PATH_SSH_CLIENT_ID_RSA; else { switch (sshkey_type_from_name(key_type_name)) { - case KEY_RSA1: - name = _PATH_SSH_CLIENT_IDENTITY; - break; case KEY_DSA_CERT: case KEY_DSA: name = _PATH_SSH_CLIENT_ID_DSA; @@ -249,6 +267,10 @@ case KEY_ED25519_CERT: name = _PATH_SSH_CLIENT_ID_ED25519; break; + case KEY_XMSS: + case KEY_XMSS_CERT: + name = _PATH_SSH_CLIENT_ID_XMSS; + break; default: fatal("bad key type"); } @@ -302,8 +324,6 @@ char comment[61]; int r; - if (k->type == KEY_RSA1) - fatal("version 1 keys are not supported"); if ((r = sshkey_to_blob(k, &blob, &len)) != 0) fatal("key_to_blob failed: %s", ssh_err(r)); /* Comment + surrounds must fit into 72 chars (RFC 4716 sec 3.3) */ @@ -325,7 +345,6 @@ do_convert_to_pkcs8(struct sshkey *k) { switch (sshkey_type_plain(k->type)) { - case KEY_RSA1: case KEY_RSA: if (!PEM_write_RSA_PUBKEY(stdout, k->rsa)) fatal("PEM_write_RSA_PUBKEY failed"); @@ -348,18 +367,10 @@ do_convert_to_pem(struct sshkey *k) { switch (sshkey_type_plain(k->type)) { - case KEY_RSA1: case KEY_RSA: if (!PEM_write_RSAPublicKey(stdout, k->rsa)) fatal("PEM_write_RSAPublicKey failed"); break; -#if notyet /* OpenSSH 0.9.8 lacks this function */ - case KEY_DSA: - if (!PEM_write_DSAPublicKey(stdout, k->dsa)) - fatal("PEM_write_DSAPublicKey failed"); - break; -#endif - /* XXX ECDSA? */ default: fatal("%s: unsupported key type %s", __func__, sshkey_type(k)); } @@ -467,7 +478,7 @@ return NULL; } if ((key = sshkey_new_private(ktype)) == NULL) - fatal("key_new_private failed"); + fatal("sshkey_new_private failed"); free(type); switch (key->type) { @@ -503,7 +514,7 @@ buffer_get_bignum_bits(b, key->rsa->iqmp); buffer_get_bignum_bits(b, key->rsa->q); buffer_get_bignum_bits(b, key->rsa->p); - if ((r = rsa_generate_additional_parameters(key->rsa)) != 0) + if ((r = ssh_rsa_generate_additional_parameters(key)) != 0) fatal("generate RSA parameters failed: %s", ssh_err(r)); break; } @@ -514,8 +525,8 @@ sshbuf_free(b); /* try the key */ - if (sshkey_sign(key, &sig, &slen, data, sizeof(data), 0) != 0 || - sshkey_verify(key, sig, slen, data, sizeof(data), 0) != 0) { + if (sshkey_sign(key, &sig, &slen, data, sizeof(data), NULL, 0) != 0 || + sshkey_verify(key, sig, slen, data, sizeof(data), NULL, 0) != 0) { sshkey_free(key); free(sig); return NULL; @@ -645,9 +656,6 @@ { FILE *fp; RSA *rsa; -#ifdef notyet - DSA *dsa; -#endif if ((fp = fopen(identity_file, "r")) == NULL) fatal("%s: %s: %s", __progname, identity_file, strerror(errno)); @@ -659,18 +667,6 @@ fclose(fp); return; } -#if notyet /* OpenSSH 0.9.8 lacks this function */ - rewind(fp); - if ((dsa = PEM_read_DSAPublicKey(fp, NULL, NULL, NULL)) != NULL) { - if ((*k = sshkey_new(KEY_UNSPEC)) == NULL) - fatal("sshkey_new failed"); - (*k)->type = KEY_DSA; - (*k)->dsa = dsa; - fclose(fp); - return; - } - /* XXX ECDSA */ -#endif fatal("%s: unrecognised raw private key format", __func__); } @@ -745,7 +741,7 @@ fatal("%s: %s", identity_file, strerror(errno)); prv = load_identity(identity_file); if ((r = sshkey_write(prv, stdout)) != 0) - error("key_write failed: %s", ssh_err(r)); + error("sshkey_write failed: %s", ssh_err(r)); sshkey_free(prv); fprintf(stdout, "\n"); exit(0); @@ -801,13 +797,6 @@ struct sshkey *ret; int r; - if ((ret = sshkey_new(KEY_RSA1)) == NULL) - fatal("sshkey_new failed"); - /* Try RSA1 */ - if ((r = sshkey_read(ret, cpp)) == 0) - return ret; - /* Try modern */ - sshkey_free(ret); if ((ret = sshkey_new(KEY_UNSPEC)) == NULL) fatal("sshkey_new failed"); if ((r = sshkey_read(ret, cpp)) == 0) @@ -830,7 +819,7 @@ ra = sshkey_fingerprint(public, fingerprint_hash, SSH_FP_RANDOMART); if (fp == NULL || ra == NULL) fatal("%s: sshkey_fingerprint failed", __func__); - printf("%u %s %s (%s)\n", sshkey_size(public), fp, + mprintf("%u %s %s (%s)\n", sshkey_size(public), fp, comment ? comment : "no comment", sshkey_type(public)); if (log_level >= SYSLOG_LEVEL_VERBOSE) printf("%s\n", ra); @@ -867,10 +856,10 @@ { FILE *f; struct sshkey *public = NULL; - char *comment = NULL, *cp, *ep, line[16*1024]; + char *comment = NULL, *cp, *ep, line[SSH_MAX_PUBKEY_BYTES]; int i, invalid = 1; const char *path; - long int lnum = 0; + u_long lnum = 0; if (!have_identity) ask_filename(pw, "Enter file in which the key is"); @@ -933,7 +922,7 @@ } /* Retry after parsing leading hostname/key options */ if (public == NULL && (public = try_read_key(&cp)) == NULL) { - debug("%s:%ld: not a public key", path, lnum); + debug("%s:%lu: not a public key", path, lnum); continue; } @@ -963,34 +952,52 @@ char *path; } key_types[] = { #ifdef WITH_OPENSSL -#ifdef WITH_SSH1 - { "rsa1", "RSA1", _PATH_HOST_KEY_FILE }, -#endif /* WITH_SSH1 */ { "rsa", "RSA" ,_PATH_HOST_RSA_KEY_FILE }, { "dsa", "DSA", _PATH_HOST_DSA_KEY_FILE }, { "ecdsa", "ECDSA",_PATH_HOST_ECDSA_KEY_FILE }, #endif /* WITH_OPENSSL */ { "ed25519", "ED25519",_PATH_HOST_ED25519_KEY_FILE }, +#ifdef WITH_XMSS + { "xmss", "XMSS",_PATH_HOST_XMSS_KEY_FILE }, +#endif /* WITH_XMSS */ { NULL, NULL, NULL } }; int first = 0; struct stat st; struct sshkey *private, *public; - char comment[1024]; + char comment[1024], *prv_tmp, *pub_tmp, *prv_file, *pub_file; int i, type, fd, r; FILE *f; for (i = 0; key_types[i].key_type; i++) { - if (stat(key_types[i].path, &st) == 0) - continue; - if (errno != ENOENT) { + public = private = NULL; + prv_tmp = pub_tmp = prv_file = pub_file = NULL; + + xasprintf(&prv_file, "%s%s", + identity_file, key_types[i].path); + + /* Check whether private key exists and is not zero-length */ + if (stat(prv_file, &st) == 0) { + if (st.st_size != 0) + goto next; + } else if (errno != ENOENT) { error("Could not stat %s: %s", key_types[i].path, strerror(errno)); - first = 0; - continue; + goto failnext; } + /* + * Private key doesn't exist or is invalid; proceed with + * key generation. + */ + xasprintf(&prv_tmp, "%s%s.XXXXXXXXXX", + identity_file, key_types[i].path); + xasprintf(&pub_tmp, "%s%s.pub.XXXXXXXXXX", + identity_file, key_types[i].path); + xasprintf(&pub_file, "%s%s.pub", + identity_file, key_types[i].path); + if (first == 0) { first = 1; printf("%s: generating new host keys: ", __progname); @@ -998,56 +1005,76 @@ printf("%s ", key_types[i].key_type_display); fflush(stdout); type = sshkey_type_from_name(key_types[i].key_type); - strlcpy(identity_file, key_types[i].path, sizeof(identity_file)); + if ((fd = mkstemp(prv_tmp)) == -1) { + error("Could not save your public key in %s: %s", + prv_tmp, strerror(errno)); + goto failnext; + } + close(fd); /* just using mkstemp() to generate/reserve a name */ bits = 0; type_bits_valid(type, NULL, &bits); if ((r = sshkey_generate(type, bits, &private)) != 0) { - error("key_generate failed: %s", ssh_err(r)); - first = 0; - continue; + error("sshkey_generate failed: %s", ssh_err(r)); + goto failnext; } if ((r = sshkey_from_private(private, &public)) != 0) fatal("sshkey_from_private failed: %s", ssh_err(r)); snprintf(comment, sizeof comment, "%s@%s", pw->pw_name, hostname); - if ((r = sshkey_save_private(private, identity_file, "", + if ((r = sshkey_save_private(private, prv_tmp, "", comment, use_new_format, new_format_cipher, rounds)) != 0) { error("Saving key \"%s\" failed: %s", - identity_file, ssh_err(r)); - sshkey_free(private); - sshkey_free(public); - first = 0; - continue; + prv_tmp, ssh_err(r)); + goto failnext; } - sshkey_free(private); - strlcat(identity_file, ".pub", sizeof(identity_file)); - fd = open(identity_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd == -1) { - error("Could not save your public key in %s", - identity_file); - sshkey_free(public); - first = 0; - continue; + if ((fd = mkstemp(pub_tmp)) == -1) { + error("Could not save your public key in %s: %s", + pub_tmp, strerror(errno)); + goto failnext; } + (void)fchmod(fd, 0644); f = fdopen(fd, "w"); if (f == NULL) { - error("fdopen %s failed", identity_file); + error("fdopen %s failed: %s", pub_tmp, strerror(errno)); close(fd); - sshkey_free(public); - first = 0; - continue; + goto failnext; } if ((r = sshkey_write(public, f)) != 0) { error("write key failed: %s", ssh_err(r)); fclose(f); - sshkey_free(public); - first = 0; - continue; + goto failnext; } fprintf(f, " %s\n", comment); - fclose(f); - sshkey_free(public); + if (ferror(f) != 0) { + error("write key failed: %s", strerror(errno)); + fclose(f); + goto failnext; + } + if (fclose(f) != 0) { + error("key close failed: %s", strerror(errno)); + goto failnext; + } + /* Rename temporary files to their permanent locations. */ + if (rename(pub_tmp, pub_file) != 0) { + error("Unable to move %s into position: %s", + pub_file, strerror(errno)); + goto failnext; + } + if (rename(prv_tmp, prv_file) != 0) { + error("Unable to move %s into position: %s", + key_types[i].path, strerror(errno)); + failnext: + first = 0; + goto next; + } + next: + sshkey_free(private); + sshkey_free(public); + free(prv_tmp); + free(pub_tmp); + free(prv_file); + free(pub_file); } if (first != 0) printf("\n"); @@ -1067,6 +1094,7 @@ struct known_hosts_ctx *ctx = (struct known_hosts_ctx *)_ctx; char *hashed, *cp, *hosts, *ohosts; int has_wild = l->hosts && strcspn(l->hosts, "*?!") != strlen(l->hosts); + int was_hashed = l->hosts && l->hosts[0] == HASH_DELIM; switch (l->status) { case HKF_STATUS_OK: @@ -1075,11 +1103,10 @@ * Don't hash hosts already already hashed, with wildcard * characters or a CA/revocation marker. */ - if ((l->match & HKF_MATCH_HOST_HASHED) != 0 || - has_wild || l->marker != MRK_NONE) { + if (was_hashed || has_wild || l->marker != MRK_NONE) { fprintf(ctx->out, "%s\n", l->line); if (has_wild && !find_host) { - logit("%s:%ld: ignoring host name " + logit("%s:%lu: ignoring host name " "with wildcard: %.64s", l->path, l->linenum, l->hosts); } @@ -1091,6 +1118,7 @@ */ ohosts = hosts = xstrdup(l->hosts); while ((cp = strsep(&hosts, ",")) != NULL && *cp != '\0') { + lowercase(cp); if ((hashed = host_hash(cp, NULL, 0)) == NULL) fatal("hash_host failed"); fprintf(ctx->out, "%s %s\n", hashed, l->rawkey); @@ -1101,7 +1129,7 @@ case HKF_STATUS_INVALID: /* Retain invalid lines, but mark file as invalid. */ ctx->invalid = 1; - logit("%s:%ld: invalid line", l->path, l->linenum); + logit("%s:%lu: invalid line", l->path, l->linenum); /* FALLTHROUGH */ default: fprintf(ctx->out, "%s\n", l->line); @@ -1135,14 +1163,14 @@ */ ctx->found_key = 1; if (!quiet) - printf("# Host %s found: line %ld\n", + printf("# Host %s found: line %lu\n", ctx->host, l->linenum); } return 0; } else if (find_host) { ctx->found_key = 1; if (!quiet) { - printf("# Host %s found: line %ld %s\n", + printf("# Host %s found: line %lu %s\n", ctx->host, l->linenum, l->marker == MRK_CA ? "CA" : (l->marker == MRK_REVOKE ? "REVOKED" : "")); @@ -1151,7 +1179,7 @@ known_hosts_hash(l, ctx); else if (print_fingerprint) { fp = sshkey_fingerprint(l->key, fptype, rep); - printf("%s %s %s %s\n", ctx->host, + mprintf("%s %s %s %s\n", ctx->host, sshkey_type(l->key), fp, l->comment); free(fp); } else @@ -1162,7 +1190,7 @@ /* Retain non-matching hosts when deleting */ if (l->status == HKF_STATUS_INVALID) { ctx->invalid = 1; - logit("%s:%ld: invalid line", l->path, l->linenum); + logit("%s:%lu: invalid line", l->path, l->linenum); } fprintf(ctx->out, "%s\n", l->line); } @@ -1216,8 +1244,11 @@ foreach_options |= print_fingerprint ? HKF_WANT_PARSE_KEY : 0; if ((r = hostkeys_foreach(identity_file, hash_hosts ? known_hosts_hash : known_hosts_find_delete, &ctx, - name, NULL, foreach_options)) != 0) + name, NULL, foreach_options)) != 0) { + if (inplace) + unlink(tmp); fatal("%s: hostkeys_foreach failed: %s", __func__, ssh_err(r)); + } if (inplace) fclose(ctx.out); @@ -1299,7 +1330,7 @@ fatal("Failed to load key %s: %s", identity_file, ssh_err(r)); } if (comment) - printf("Key has comment '%s'\n", comment); + mprintf("Key has comment '%s'\n", comment); /* Ask the new passphrase (twice). */ if (identity_new_passphrase) { @@ -1414,14 +1445,19 @@ identity_file, ssh_err(r)); } } - /* XXX what about new-format keys? */ - if (private->type != KEY_RSA1) { - error("Comments are only supported for RSA1 keys."); + + if (private->type != KEY_ED25519 && private->type != KEY_XMSS && + !use_new_format) { + error("Comments are only supported for keys stored in " + "the new format (-o)."); explicit_bzero(passphrase, strlen(passphrase)); sshkey_free(private); exit(1); } - printf("Key now has comment '%s'\n", comment); + if (comment) + printf("Key now has comment '%s'\n", comment); + else + printf("Key now has no comment\n"); if (identity_comment) { strlcpy(new_comment, identity_comment, sizeof(new_comment)); @@ -1450,7 +1486,7 @@ explicit_bzero(passphrase, strlen(passphrase)); free(passphrase); if ((r = sshkey_from_private(private, &public)) != 0) - fatal("key_from_private failed: %s", ssh_err(r)); + fatal("sshkey_from_private failed: %s", ssh_err(r)); sshkey_free(private); strlcat(identity_file, ".pub", sizeof(identity_file)); @@ -1472,44 +1508,6 @@ exit(0); } -static const char * -fmt_validity(u_int64_t valid_from, u_int64_t valid_to) -{ - char from[32], to[32]; - static char ret[64]; - time_t tt; - struct tm *tm; - - *from = *to = '\0'; - if (valid_from == 0 && valid_to == 0xffffffffffffffffULL) - return "forever"; - - if (valid_from != 0) { - /* XXX revisit INT_MAX in 2038 :) */ - tt = valid_from > INT_MAX ? INT_MAX : valid_from; - tm = localtime(&tt); - strftime(from, sizeof(from), "%Y-%m-%dT%H:%M:%S", tm); - } - if (valid_to != 0xffffffffffffffffULL) { - /* XXX revisit INT_MAX in 2038 :) */ - tt = valid_to > INT_MAX ? INT_MAX : valid_to; - tm = localtime(&tt); - strftime(to, sizeof(to), "%Y-%m-%dT%H:%M:%S", tm); - } - - if (valid_from == 0) { - snprintf(ret, sizeof(ret), "before %s", to); - return ret; - } - if (valid_to == 0xffffffffffffffffULL) { - snprintf(ret, sizeof(ret), "after %s", from); - return ret; - } - - snprintf(ret, sizeof(ret), "from %s to %s", from, to); - return ret; -} - static void add_flag_option(struct sshbuf *c, const char *name) { @@ -1543,6 +1541,8 @@ static void prepare_options_buf(struct sshbuf *c, int which) { + size_t i; + sshbuf_reset(c); if ((which & OPTIONS_CRITICAL) != 0 && certflags_command != NULL) @@ -1565,6 +1565,17 @@ if ((which & OPTIONS_CRITICAL) != 0 && certflags_src_addr != NULL) add_string_option(c, "source-address", certflags_src_addr); + for (i = 0; i < ncert_userext; i++) { + if ((cert_userext[i].crit && (which & OPTIONS_EXTENSIONS)) || + (!cert_userext[i].crit && (which & OPTIONS_CRITICAL))) + continue; + if (cert_userext[i].val == NULL) + add_flag_option(c, cert_userext[i].key); + else { + add_string_option(c, cert_userext[i].key, + cert_userext[i].val); + } + } } static struct sshkey * @@ -1597,26 +1608,74 @@ #endif /* ENABLE_PKCS11 */ } +/* Signer for sshkey_certify_custom that uses the agent */ +static int +agent_signer(const struct sshkey *key, u_char **sigp, size_t *lenp, + const u_char *data, size_t datalen, + const char *alg, u_int compat, void *ctx) +{ + int *agent_fdp = (int *)ctx; + + return ssh_agent_sign(*agent_fdp, key, sigp, lenp, + data, datalen, alg, compat); +} + static void do_ca_sign(struct passwd *pw, int argc, char **argv) { - int r, i, fd; + int r, i, fd, found, agent_fd = -1; u_int n; struct sshkey *ca, *public; - char *otmp, *tmp, *cp, *out, *comment, **plist = NULL; + char valid[64], *otmp, *tmp, *cp, *out, *comment, **plist = NULL; FILE *f; + struct ssh_identitylist *agent_ids; + size_t j; #ifdef ENABLE_PKCS11 pkcs11_init(1); #endif tmp = tilde_expand_filename(ca_key_path, pw->pw_uid); if (pkcs11provider != NULL) { + /* If a PKCS#11 token was specified then try to use it */ if ((ca = load_pkcs11_key(tmp)) == NULL) fatal("No PKCS#11 key matching %s found", ca_key_path); - } else + } else if (prefer_agent) { + /* + * Agent signature requested. Try to use agent after making + * sure the public key specified is actually present in the + * agent. + */ + if ((r = sshkey_load_public(tmp, &ca, NULL)) != 0) + fatal("Cannot load CA public key %s: %s", + tmp, ssh_err(r)); + if ((r = ssh_get_authentication_socket(&agent_fd)) != 0) + fatal("Cannot use public key for CA signature: %s", + ssh_err(r)); + if ((r = ssh_fetch_identitylist(agent_fd, &agent_ids)) != 0) + fatal("Retrieve agent key list: %s", ssh_err(r)); + found = 0; + for (j = 0; j < agent_ids->nkeys; j++) { + if (sshkey_equal(ca, agent_ids->keys[j])) { + found = 1; + break; + } + } + if (!found) + fatal("CA key %s not found in agent", tmp); + ssh_free_identitylist(agent_ids); + ca->flags |= SSHKEY_FLAG_EXT; + } else { + /* CA key is assumed to be a private key on the filesystem */ ca = load_identity(tmp); + } free(tmp); + if (key_type_name != NULL && + sshkey_type_from_name(key_type_name) != ca->type) { + fatal("CA key type %s doesn't match specified %s", + sshkey_ssh_name(ca), key_type_name); + } + for (i = 0; i < argc; i++) { /* Split list of principals */ n = 0; @@ -1630,13 +1689,16 @@ } free(otmp); } + if (n > SSHKEY_CERT_MAX_PRINCIPALS) + fatal("Too many certificate principals specified"); tmp = tilde_expand_filename(argv[i], pw->pw_uid); if ((r = sshkey_load_public(tmp, &public, &comment)) != 0) fatal("%s: unable to open \"%s\": %s", __func__, tmp, ssh_err(r)); if (public->type != KEY_RSA && public->type != KEY_DSA && - public->type != KEY_ECDSA && public->type != KEY_ED25519) + public->type != KEY_ECDSA && public->type != KEY_ED25519 && + public->type != KEY_XMSS) fatal("%s: key \"%s\" type %s cannot be certified", __func__, tmp, sshkey_type(public)); @@ -1656,10 +1718,18 @@ OPTIONS_EXTENSIONS); if ((r = sshkey_from_private(ca, &public->cert->signature_key)) != 0) - fatal("key_from_private (ca key): %s", ssh_err(r)); + fatal("sshkey_from_private (ca key): %s", ssh_err(r)); - if (sshkey_certify(public, ca) != 0) - fatal("Couldn't not certify key %s", tmp); + if (agent_fd != -1 && (ca->flags & SSHKEY_FLAG_EXT) != 0) { + if ((r = sshkey_certify_custom(public, ca, + key_type_name, agent_signer, &agent_fd)) != 0) + fatal("Couldn't certify key %s via agent: %s", + tmp, ssh_err(r)); + } else { + if ((sshkey_certify(public, ca, key_type_name)) != 0) + fatal("Couldn't certify key %s: %s", + tmp, ssh_err(r)); + } if ((cp = strrchr(tmp, '.')) != NULL && strcmp(cp, ".pub") == 0) *cp = '\0'; @@ -1678,13 +1748,15 @@ fclose(f); if (!quiet) { + sshkey_format_cert_validity(public->cert, + valid, sizeof(valid)); logit("Signed %s key %s: id \"%s\" serial %llu%s%s " - "valid %s", sshkey_cert_type(public), + "valid %s", sshkey_cert_type(public), out, public->cert->key_id, (unsigned long long)public->cert->serial, cert_principals != NULL ? " for " : "", cert_principals != NULL ? cert_principals : "", - fmt_validity(cert_valid_from, cert_valid_to)); + valid); } sshkey_free(public); @@ -1718,7 +1790,7 @@ char buf[32], *fmt; /* - * POSIX strptime says "The application shall ensure that there + * POSIX strptime says "The application shall ensure that there * is white-space or other non-alphanumeric characters between * any two conversion specifications" so arrange things this way. */ @@ -1733,7 +1805,7 @@ s, s + 4, s + 6, s + 8, s + 10, s + 12); break; default: - fatal("Invalid certificate time format %s", s); + fatal("Invalid certificate time format \"%s\"", s); } memset(&tm, 0, sizeof(tm)); @@ -1766,8 +1838,8 @@ /* * from:to, where - * from := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS - * to := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS + * from := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | "always" + * to := [+-]timespec | YYYYMMDD | YYYYMMDDHHMMSS | "forever" */ from = xstrdup(timespec); to = strchr(from, ':'); @@ -1777,11 +1849,15 @@ if (*from == '-' || *from == '+') cert_valid_from = parse_relative_time(from, now); + else if (strcmp(from, "always") == 0) + cert_valid_from = 0; else cert_valid_from = parse_absolute_time(from); if (*to == '-' || *to == '+') cert_valid_to = parse_relative_time(to, now); + else if (strcmp(to, "forever") == 0) + cert_valid_to = ~(u_int64_t)0; else cert_valid_to = parse_absolute_time(to); @@ -1793,7 +1869,8 @@ static void add_cert_option(char *opt) { - char *val; + char *val, *cp; + int iscrit = 0; if (strcasecmp(opt, "clear") == 0) certflags_flags = 0; @@ -1833,6 +1910,18 @@ if (addr_match_cidr_list(NULL, val) != 0) fatal("Invalid source-address list"); certflags_src_addr = xstrdup(val); + } else if (strncasecmp(opt, "extension:", 10) == 0 || + (iscrit = (strncasecmp(opt, "critical:", 9) == 0))) { + val = xstrdup(strchr(opt, ':') + 1); + if ((cp = strchr(val, '=')) != NULL) + *cp++ = '\0'; + cert_userext = xreallocarray(cert_userext, ncert_userext + 1, + sizeof(*cert_userext)); + cert_userext[ncert_userext].key = val; + cert_userext[ncert_userext].val = cp == NULL ? + NULL : xstrdup(cp); + cert_userext[ncert_userext].crit = iscrit; + ncert_userext++; } else fatal("Unsupported certificate option \"%s\"", opt); } @@ -1884,7 +1973,7 @@ static void print_cert(struct sshkey *key) { - char *key_fp, *ca_fp; + char valid[64], *key_fp, *ca_fp; u_int i; key_fp = sshkey_fingerprint(key, fingerprint_hash, SSH_FP_DEFAULT); @@ -1892,6 +1981,7 @@ fingerprint_hash, SSH_FP_DEFAULT); if (key_fp == NULL || ca_fp == NULL) fatal("%s: sshkey_fingerprint fail", __func__); + sshkey_format_cert_validity(key->cert, valid, sizeof(valid)); printf(" Type: %s %s certificate\n", sshkey_ssh_name(key), sshkey_cert_type(key)); @@ -1900,8 +1990,7 @@ sshkey_type(key->cert->signature_key), ca_fp); printf(" Key ID: \"%s\"\n", key->cert->key_id); printf(" Serial: %llu\n", (unsigned long long)key->cert->serial); - printf(" Valid: %s\n", - fmt_validity(key->cert->valid_after, key->cert->valid_before)); + printf(" Valid: %s\n", valid); printf(" Principals: "); if (key->cert->nprincipals == 0) printf("(none)\n"); @@ -1934,9 +2023,9 @@ struct stat st; int r, is_stdin = 0, ok = 0; FILE *f; - char *cp, line[2048]; + char *cp, line[SSH_MAX_PUBKEY_BYTES]; const char *path; - long int lnum = 0; + u_long lnum = 0; if (!have_identity) ask_filename(pw, "Enter file in which the key is"); @@ -1959,7 +2048,7 @@ if (*cp == '#' || *cp == '\0') continue; if ((key = sshkey_new(KEY_UNSPEC)) == NULL) - fatal("key_new"); + fatal("sshkey_new"); if ((r = sshkey_read(key, &cp)) != 0) { error("%s:%lu: invalid key: %s", path, lnum, ssh_err(r)); @@ -2106,7 +2195,7 @@ */ } if ((key = sshkey_new(KEY_UNSPEC)) == NULL) - fatal("key_new"); + fatal("sshkey_new"); if ((r = sshkey_read(key, &cp)) != 0) fatal("%s:%lu: invalid key: %s", path, lnum, ssh_err(r)); @@ -2183,8 +2272,7 @@ close(fd); sshbuf_free(kbuf); ssh_krl_free(krl); - if (ca != NULL) - sshkey_free(ca); + sshkey_free(ca); } static void @@ -2220,7 +2308,7 @@ usage(void) { fprintf(stderr, - "usage: ssh-keygen [-q] [-b bits] [-t dsa | ecdsa | ed25519 | rsa | rsa1]\n" + "usage: ssh-keygen [-q] [-b bits] [-t dsa | ecdsa | ed25519 | rsa]\n" " [-N new_passphrase] [-C comment] [-f output_keyfile]\n" " ssh-keygen -p [-P old_passphrase] [-N new_passphrase] [-f keyfile]\n" " ssh-keygen -i [-m key_format] [-f input_keyfile]\n" @@ -2243,8 +2331,9 @@ " ssh-keygen -T output_file -f input_file [-v] [-a rounds] [-J num_lines]\n" " [-j start_line] [-K checkpt] [-W generator]\n" #endif - " ssh-keygen -s ca_key -I certificate_identity [-h] [-n principals]\n" - " [-O option] [-V validity_interval] [-z serial_number] file ...\n" + " ssh-keygen -s ca_key -I certificate_identity [-h] [-U]\n" + " [-D pkcs11_provider] [-n principals] [-O option]\n" + " [-V validity_interval] [-z serial_number] file ...\n" " ssh-keygen -L [-f input_keyfile]\n" " ssh-keygen -A\n" " ssh-keygen -k -f krl_file [-u] [-s ca_public] [-z version_number]\n" @@ -2280,12 +2369,15 @@ extern int optind; extern char *optarg; + ssh_malloc_init(); /* must be called before any mallocs */ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); OpenSSL_add_all_algorithms(); log_init(argv[0], SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_USER, 1); + setlocale(LC_CTYPE, ""); + /* we need this for the home * directory. */ pw = getpwuid(getuid()); if (!pw) @@ -2293,8 +2385,8 @@ if (gethostname(hostname, sizeof(hostname)) < 0) fatal("gethostname: %s", strerror(errno)); - /* Remaining characters: UYdw */ - while ((opt = getopt(argc, argv, "ABHLQXceghiklopquvxy" + /* Remaining characters: Ydw */ + while ((opt = getopt(argc, argv, "ABHLQUXceghiklopquvxy" "C:D:E:F:G:I:J:K:M:N:O:P:R:S:T:V:W:Z:" "a:b:f:g:j:m:n:r:s:t:z:")) != -1) { switch (opt) { @@ -2302,7 +2394,7 @@ gen_all_hostkeys = 1; break; case 'b': - bits = (u_int32_t)strtonum(optarg, 256, 32768, &errstr); + bits = (u_int32_t)strtonum(optarg, 10, 32768, &errstr); if (errstr) fatal("Bits has bad value %s (%s)", optarg, errstr); @@ -2421,6 +2513,9 @@ case 'D': pkcs11provider = optarg; break; + case 'U': + prefer_agent = 1; + break; case 'u': update_krl = 1; break; @@ -2462,10 +2557,10 @@ break; case 'J': lines_to_process = strtoul(optarg, NULL, 10); - break; + break; case 'j': start_lineno = strtoul(optarg, NULL, 10); - break; + break; case 'K': if (strlen(optarg) >= PATH_MAX) fatal("Checkpoint filename too long"); @@ -2579,6 +2674,8 @@ _PATH_HOST_ECDSA_KEY_FILE, rr_hostname); n += do_print_resource_record(pw, _PATH_HOST_ED25519_KEY_FILE, rr_hostname); + n += do_print_resource_record(pw, + _PATH_HOST_XMSS_KEY_FILE, rr_hostname); if (n == 0) fatal("no keys found."); exit(0); @@ -2642,9 +2739,9 @@ printf("Generating public/private %s key pair.\n", key_type_name); if ((r = sshkey_generate(type, bits, &private)) != 0) - fatal("key_generate failed"); + fatal("sshkey_generate failed"); if ((r = sshkey_from_private(private, &public)) != 0) - fatal("key_from_private failed: %s\n", ssh_err(r)); + fatal("sshkey_from_private failed: %s\n", ssh_err(r)); if (!have_identity) ask_filename(pw, "Enter file in which to save the key"); @@ -2739,7 +2836,8 @@ if ((r = sshkey_write(public, f)) != 0) error("write key failed: %s", ssh_err(r)); fprintf(f, " %s\n", comment); - fclose(f); + if (ferror(f) || fclose(f) != 0) + fatal("write public failed: %s", strerror(errno)); if (!quiet) { fp = sshkey_fingerprint(public, fingerprint_hash,