From 4448a2938abc76e6bd33ba09b2ec17a216dfb491 Mon Sep 17 00:00:00 2001 From: "djm@openbsd.org" Date: Mon, 18 Dec 2023 14:46:56 +0000 Subject: [PATCH] upstream: Make it possible to load certs from PKCS#11 tokens Adds a protocol extension to allow grafting certificates supplied by ssh-add to keys loaded from PKCS#11 tokens in the agent. feedback/ok markus@ OpenBSD-Commit-ID: bb5433cd28ede2bc910996eb3c0b53e20f86037f --- PROTOCOL.agent | 33 +++++++++++- authfd.c | 40 +++++++++++--- authfd.h | 5 +- ssh-add.1 | 14 +++-- ssh-add.c | 92 ++++++++++++++++++++++---------- ssh-agent.c | 124 ++++++++++++++++++++++++++++++++++---------- ssh-pkcs11-client.c | 56 +++++++++++++++++++- ssh-pkcs11.h | 5 +- 8 files changed, 299 insertions(+), 70 deletions(-) diff --git a/PROTOCOL.agent b/PROTOCOL.agent index 1c4841147..e4a6b74c5 100644 --- a/PROTOCOL.agent +++ b/PROTOCOL.agent @@ -81,4 +81,35 @@ the constraint is: This option is only valid for XMSS keys. -$OpenBSD: PROTOCOL.agent,v 1.20 2023/10/03 23:56:10 djm Exp $ +3. associated-certs-v00@openssh.com key constraint extension + +The key constraint extension allows certificates to be associated +with private keys as they are loaded from a PKCS#11 token. + + byte SSH_AGENT_CONSTRAIN_EXTENSION (0xff) + string associated-certs-v00@openssh.com + bool certs_only + string certsblob + +Where "certsblob" constists of one or more certificates encoded as public +key blobs: + + string[] certificates + +This extension is only valid for SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED +requests. When an agent receives this extension, it will attempt to match +each certificate in the request with a corresponding private key loaded +from the requested PKCS#11 token. When a matching key is found, the +agent will graft the certificate contents to the token-hosted private key +and store the result for subsequent use by regular agent operations. + +If the "certs_only" flag is set, then this extension will cause ONLY +the resultant certificates to be loaded to the agent. The default +behaviour is to load the PKCS#11-hosted private key as well as the +resultant certificate. + +A SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED will return SSH_AGENT_SUCCESS +if any key (plain private or certificate) was successfully loaded, or +SSH_AGENT_FAILURE if no key was loaded. + +$OpenBSD: PROTOCOL.agent,v 1.21 2023/12/18 14:46:56 djm Exp $ diff --git a/authfd.c b/authfd.c index 25a363664..e04ad0cf2 100644 --- a/authfd.c +++ b/authfd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.c,v 1.133 2023/03/09 21:06:24 jcs Exp $ */ +/* $OpenBSD: authfd.c,v 1.134 2023/12/18 14:46:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -504,9 +504,10 @@ encode_dest_constraint(struct sshbuf *m, const struct dest_constraint *dc) } static int -encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign, - const char *provider, struct dest_constraint **dest_constraints, - size_t ndest_constraints) +encode_constraints(struct sshbuf *m, u_int life, u_int confirm, + u_int maxsign, const char *provider, + struct dest_constraint **dest_constraints, size_t ndest_constraints, + int cert_only, struct sshkey **certs, size_t ncerts) { int r; struct sshbuf *b = NULL; @@ -550,6 +551,27 @@ encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign, "restrict-destination-v00@openssh.com")) != 0 || (r = sshbuf_put_stringb(m, b)) != 0) goto out; + sshbuf_free(b); + b = NULL; + } + if (ncerts != 0) { + if ((b = sshbuf_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + for (i = 0; i < ncerts; i++) { + if ((r = sshkey_puts(certs[i], b)) != 0) + goto out; + } + if ((r = sshbuf_put_u8(m, + SSH_AGENT_CONSTRAIN_EXTENSION)) != 0 || + (r = sshbuf_put_cstring(m, + "associated-certs-v00@openssh.com")) != 0 || + (r = sshbuf_put_u8(m, cert_only != 0)) != 0 || + (r = sshbuf_put_stringb(m, b)) != 0) + goto out; + sshbuf_free(b); + b = NULL; } r = 0; out: @@ -607,7 +629,7 @@ ssh_add_identity_constrained(int sock, struct sshkey *key, } if (constrained && (r = encode_constraints(msg, life, confirm, maxsign, - provider, dest_constraints, ndest_constraints)) != 0) + provider, dest_constraints, ndest_constraints, 0, NULL, 0)) != 0) goto out; if ((r = ssh_request_reply_decode(sock, msg)) != 0) goto out; @@ -662,10 +684,11 @@ ssh_remove_identity(int sock, const struct sshkey *key) int ssh_update_card(int sock, int add, const char *reader_id, const char *pin, u_int life, u_int confirm, - struct dest_constraint **dest_constraints, size_t ndest_constraints) + struct dest_constraint **dest_constraints, size_t ndest_constraints, + int cert_only, struct sshkey **certs, size_t ncerts) { struct sshbuf *msg; - int r, constrained = (life || confirm || dest_constraints); + int r, constrained = (life || confirm || dest_constraints || certs); u_char type; if (add) { @@ -683,7 +706,8 @@ ssh_update_card(int sock, int add, const char *reader_id, const char *pin, goto out; if (constrained && (r = encode_constraints(msg, life, confirm, 0, NULL, - dest_constraints, ndest_constraints)) != 0) + dest_constraints, ndest_constraints, + cert_only, certs, ncerts)) != 0) goto out; if ((r = ssh_request_reply_decode(sock, msg)) != 0) goto out; diff --git a/authfd.h b/authfd.h index 7a1c0ddff..c1e4b405c 100644 --- a/authfd.h +++ b/authfd.h @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.h,v 1.51 2021/12/19 22:10:24 djm Exp $ */ +/* $OpenBSD: authfd.h,v 1.52 2023/12/18 14:46:56 djm Exp $ */ /* * Author: Tatu Ylonen @@ -56,7 +56,8 @@ int ssh_remove_identity(int sock, const struct sshkey *key); int ssh_update_card(int sock, int add, const char *reader_id, const char *pin, u_int life, u_int confirm, struct dest_constraint **dest_constraints, - size_t ndest_constraints); + size_t ndest_constraints, + int cert_only, struct sshkey **certs, size_t ncerts); int ssh_remove_all_identities(int sock, int version); int ssh_agent_sign(int sock, const struct sshkey *key, diff --git a/ssh-add.1 b/ssh-add.1 index 4601f5981..f0186cd5f 100644 --- a/ssh-add.1 +++ b/ssh-add.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-add.1,v 1.84 2022/02/04 02:49:17 dtucker Exp $ +.\" $OpenBSD: ssh-add.1,v 1.85 2023/12/18 14:46:56 djm Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: February 4 2022 $ +.Dd $Mdocdate: December 18 2023 $ .Dt SSH-ADD 1 .Os .Sh NAME @@ -43,7 +43,7 @@ .Nd adds private key identities to the OpenSSH authentication agent .Sh SYNOPSIS .Nm ssh-add -.Op Fl cDdKkLlqvXx +.Op Fl cCDdKkLlqvXx .Op Fl E Ar fingerprint_hash .Op Fl H Ar hostkey_file .Op Fl h Ar destination_constraint @@ -52,6 +52,8 @@ .Op Ar .Nm ssh-add .Fl s Ar pkcs11 +.Op Fl vC +.Op Ar certificate ... .Nm ssh-add .Fl e Ar pkcs11 .Nm ssh-add @@ -100,6 +102,9 @@ Confirmation is performed by Successful confirmation is signaled by a zero exit status from .Xr ssh-askpass 1 , rather than text entered into the requester. +.It Fl C +When loading keys into or deleting keys from the agent, process +certificates only and skip plain keys. .It Fl D Deletes all identities from the agent. .It Fl d @@ -228,6 +233,9 @@ internal USB HID support. .It Fl s Ar pkcs11 Add keys provided by the PKCS#11 shared library .Ar pkcs11 . +Certificate files may optionally be listed as command-line arguments. +If these are present, then they will be loaded into the agent using any +corresponding private keys loaded from the PKCS#11 token. .It Fl T Ar pubkey ... Tests whether the private keys that correspond to the specified .Ar pubkey diff --git a/ssh-add.c b/ssh-add.c index 775a9a8ef..99ba23b52 100644 --- a/ssh-add.c +++ b/ssh-add.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-add.c,v 1.168 2023/07/06 22:17:59 dtucker Exp $ */ +/* $OpenBSD: ssh-add.c,v 1.169 2023/12/18 14:46:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -131,7 +131,7 @@ delete_one(int agent_fd, const struct sshkey *key, const char *comment, } static int -delete_stdin(int agent_fd, int qflag) +delete_stdin(int agent_fd, int qflag, int key_only, int cert_only) { char *line = NULL, *cp; size_t linesize = 0; @@ -152,8 +152,13 @@ delete_stdin(int agent_fd, int qflag) error_r(r, "(stdin):%d: invalid key", lnum); continue; } - if (delete_one(agent_fd, key, cp, "(stdin)", qflag) == 0) - ret = 0; + if ((!key_only && !cert_only) || + (key_only && !sshkey_is_cert(key)) || + (cert_only && sshkey_is_cert(key))) { + if (delete_one(agent_fd, key, cp, + "(stdin)", qflag) == 0) + ret = 0; + } } sshkey_free(key); free(line); @@ -161,21 +166,26 @@ delete_stdin(int agent_fd, int qflag) } static int -delete_file(int agent_fd, const char *filename, int key_only, int qflag) +delete_file(int agent_fd, const char *filename, int key_only, + int cert_only, int qflag) { struct sshkey *public, *cert = NULL; char *certpath = NULL, *comment = NULL; int r, ret = -1; if (strcmp(filename, "-") == 0) - return delete_stdin(agent_fd, qflag); + return delete_stdin(agent_fd, qflag, key_only, cert_only); if ((r = sshkey_load_public(filename, &public, &comment)) != 0) { printf("Bad key file %s: %s\n", filename, ssh_err(r)); return -1; } - if (delete_one(agent_fd, public, comment, filename, qflag) == 0) - ret = 0; + if ((!key_only && !cert_only) || + (key_only && !sshkey_is_cert(public)) || + (cert_only && sshkey_is_cert(public))) { + if (delete_one(agent_fd, public, comment, filename, qflag) == 0) + ret = 0; + } if (key_only) goto out; @@ -231,8 +241,9 @@ delete_all(int agent_fd, int qflag) } static int -add_file(int agent_fd, const char *filename, int key_only, int qflag, - const char *skprovider, struct dest_constraint **dest_constraints, +add_file(int agent_fd, const char *filename, int key_only, int cert_only, + int qflag, const char *skprovider, + struct dest_constraint **dest_constraints, size_t ndest_constraints) { struct sshkey *private, *cert; @@ -361,7 +372,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag, skprovider = NULL; } - if ((r = ssh_add_identity_constrained(agent_fd, private, comment, + if (!cert_only && + (r = ssh_add_identity_constrained(agent_fd, private, comment, lifetime, confirm, maxsign, skprovider, dest_constraints, ndest_constraints)) == 0) { ret = 0; @@ -390,7 +402,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag, xasprintf(&certpath, "%s-cert.pub", filename); if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) { if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) - error_r(r, "Failed to load certificate \"%s\"", certpath); + error_r(r, "Failed to load certificate \"%s\"", + certpath); goto out; } @@ -445,11 +458,16 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag, static int update_card(int agent_fd, int add, const char *id, int qflag, - struct dest_constraint **dest_constraints, size_t ndest_constraints) + int key_only, int cert_only, + struct dest_constraint **dest_constraints, size_t ndest_constraints, + struct sshkey **certs, size_t ncerts) { char *pin = NULL; int r, ret = -1; + if (key_only) + ncerts = 0; + if (add) { if ((pin = read_passphrase("Enter passphrase for PKCS#11: ", RP_ALLOW_STDIN)) == NULL) @@ -457,7 +475,8 @@ update_card(int agent_fd, int add, const char *id, int qflag, } if ((r = ssh_update_card(agent_fd, add, id, pin == NULL ? "" : pin, - lifetime, confirm, dest_constraints, ndest_constraints)) == 0) { + lifetime, confirm, dest_constraints, ndest_constraints, + cert_only, certs, ncerts)) == 0) { ret = 0; if (!qflag) { fprintf(stderr, "Card %s: %s\n", @@ -633,16 +652,17 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag, } static int -do_file(int agent_fd, int deleting, int key_only, char *file, int qflag, - const char *skprovider, struct dest_constraint **dest_constraints, - size_t ndest_constraints) +do_file(int agent_fd, int deleting, int key_only, int cert_only, + char *file, int qflag, const char *skprovider, + struct dest_constraint **dest_constraints, size_t ndest_constraints) { if (deleting) { - if (delete_file(agent_fd, file, key_only, qflag) == -1) + if (delete_file(agent_fd, file, key_only, + cert_only, qflag) == -1) return -1; } else { - if (add_file(agent_fd, file, key_only, qflag, skprovider, - dest_constraints, ndest_constraints) == -1) + if (add_file(agent_fd, file, key_only, cert_only, qflag, + skprovider, dest_constraints, ndest_constraints) == -1) return -1; } return 0; @@ -790,12 +810,14 @@ main(int argc, char **argv) int agent_fd; char *pkcs11provider = NULL, *skprovider = NULL; char **dest_constraint_strings = NULL, **hostkey_files = NULL; - int r, i, ch, deleting = 0, ret = 0, key_only = 0, do_download = 0; - int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0; + int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0; + int do_download = 0, xflag = 0, lflag = 0, Dflag = 0; + int qflag = 0, Tflag = 0; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; LogLevel log_level = SYSLOG_LEVEL_INFO; + struct sshkey *k, **certs = NULL; struct dest_constraint **dest_constraints = NULL; - size_t ndest_constraints = 0; + size_t ndest_constraints = 0i, ncerts = 0; /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ sanitise_stdfd(); @@ -822,7 +844,7 @@ main(int argc, char **argv) skprovider = getenv("SSH_SK_PROVIDER"); - while ((ch = getopt(argc, argv, "vkKlLcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { + while ((ch = getopt(argc, argv, "vkKlLCcdDTxXE:e:h:H:M:m:qs:S:t:")) != -1) { switch (ch) { case 'v': if (log_level == SYSLOG_LEVEL_INFO) @@ -844,6 +866,9 @@ main(int argc, char **argv) case 'k': key_only = 1; break; + case 'C': + cert_only = 1; + break; case 'K': do_download = 1; break; @@ -962,8 +987,19 @@ main(int argc, char **argv) goto done; } if (pkcs11provider != NULL) { + for (i = 0; i < argc; i++) { + if ((r = sshkey_load_public(argv[i], &k, NULL)) != 0) + fatal_fr(r, "load certificate %s", argv[i]); + certs = xrecallocarray(certs, ncerts, ncerts + 1, + sizeof(*certs)); + debug2("%s: %s", argv[i], sshkey_ssh_name(k)); + certs[ncerts++] = k; + } + debug2_f("loaded %zu certificates", ncerts); if (update_card(agent_fd, !deleting, pkcs11provider, - qflag, dest_constraints, ndest_constraints) == -1) + qflag, key_only, cert_only, + dest_constraints, ndest_constraints, + certs, ncerts) == -1) ret = 1; goto done; } @@ -993,8 +1029,8 @@ main(int argc, char **argv) default_files[i]); if (stat(buf, &st) == -1) continue; - if (do_file(agent_fd, deleting, key_only, buf, - qflag, skprovider, + if (do_file(agent_fd, deleting, key_only, cert_only, + buf, qflag, skprovider, dest_constraints, ndest_constraints) == -1) ret = 1; else @@ -1004,7 +1040,7 @@ main(int argc, char **argv) ret = 1; } else { for (i = 0; i < argc; i++) { - if (do_file(agent_fd, deleting, key_only, + if (do_file(agent_fd, deleting, key_only, cert_only, argv[i], qflag, skprovider, dest_constraints, ndest_constraints) == -1) ret = 1; diff --git a/ssh-agent.c b/ssh-agent.c index 1d4c321eb..12ec66cf8 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-agent.c,v 1.301 2023/12/18 14:46:12 djm Exp $ */ +/* $OpenBSD: ssh-agent.c,v 1.302 2023/12/18 14:46:56 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -105,6 +105,8 @@ #define AGENT_MAX_SID_LEN 128 /* Maximum number of destination constraints to accept on a key */ #define AGENT_MAX_DEST_CONSTRAINTS 1024 +/* Maximum number of associated certificate constraints to accept on a key */ +#define AGENT_MAX_EXT_CERTS 1024 /* XXX store hostkey_sid in a refcounted tree */ @@ -1158,11 +1160,14 @@ parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc) static int parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, - struct dest_constraint **dcsp, size_t *ndcsp) + struct dest_constraint **dcsp, size_t *ndcsp, int *cert_onlyp, + struct sshkey ***certs, size_t *ncerts) { char *ext_name = NULL; int r; struct sshbuf *b = NULL; + u_char v; + struct sshkey *k; if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) { error_fr(r, "parse constraint extension"); @@ -1205,6 +1210,36 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, *dcsp + (*ndcsp)++)) != 0) goto out; /* error already logged */ } + } else if (strcmp(ext_name, + "associated-certs-v00@openssh.com") == 0) { + if (certs == NULL || ncerts == NULL || cert_onlyp == NULL) { + error_f("%s not valid here", ext_name); + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + if (*certs != NULL) { + error_f("%s already set", ext_name); + goto out; + } + if ((r = sshbuf_get_u8(m, &v)) != 0 || + (r = sshbuf_froms(m, &b)) != 0) { + error_fr(r, "parse %s", ext_name); + goto out; + } + *cert_onlyp = v != 0; + while (sshbuf_len(b) != 0) { + if (*ncerts >= AGENT_MAX_EXT_CERTS) { + error_f("too many %s constraints", ext_name); + goto out; + } + *certs = xrecallocarray(*certs, *ncerts, *ncerts + 1, + sizeof(**certs)); + if ((r = sshkey_froms(b, &k)) != 0) { + error_fr(r, "parse key"); + goto out; + } + (*certs)[(*ncerts)++] = k; + } } else { error_f("unsupported constraint \"%s\"", ext_name); r = SSH_ERR_FEATURE_UNSUPPORTED; @@ -1221,7 +1256,8 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, static int parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, u_int *secondsp, int *confirmp, char **sk_providerp, - struct dest_constraint **dcsp, size_t *ndcsp) + struct dest_constraint **dcsp, size_t *ndcsp, + int *cert_onlyp, size_t *ncerts, struct sshkey ***certs) { u_char ctype; int r; @@ -1276,7 +1312,8 @@ parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, break; case SSH_AGENT_CONSTRAIN_EXTENSION: if ((r = parse_key_constraint_extension(m, - sk_providerp, dcsp, ndcsp)) != 0) + sk_providerp, dcsp, ndcsp, + cert_onlyp, certs, ncerts)) != 0) goto out; /* error already logged */ break; default: @@ -1313,7 +1350,8 @@ process_add_identity(SocketEntry *e) goto out; } if (parse_key_constraints(e->request, k, &death, &seconds, &confirm, - &sk_provider, &dest_constraints, &ndest_constraints) != 0) { + &sk_provider, &dest_constraints, &ndest_constraints, + NULL, NULL, NULL) != 0) { error_f("failed to parse constraints"); sshbuf_reset(e->request); goto out; @@ -1473,6 +1511,32 @@ no_identities(SocketEntry *e) sshbuf_free(msg); } +/* Add an identity to idlist; takes ownership of 'key' and 'comment' */ +static void +add_p11_identity(struct sshkey *key, char *comment, const char *provider, + time_t death, int confirm, struct dest_constraint *dest_constraints, + size_t ndest_constraints) +{ + Identity *id; + + if (lookup_identity(key) != NULL) { + sshkey_free(key); + free(comment); + return; + } + id = xcalloc(1, sizeof(Identity)); + id->key = key; + id->comment = comment; + id->provider = xstrdup(provider); + id->death = death; + id->confirm = confirm; + id->dest_constraints = dup_dest_constraints(dest_constraints, + ndest_constraints); + id->ndest_constraints = ndest_constraints; + TAILQ_INSERT_TAIL(&idtab->idlist, id, next); + idtab->nentries++; +} + #ifdef ENABLE_PKCS11 static void process_add_smartcard_key(SocketEntry *e) @@ -1483,9 +1547,10 @@ process_add_smartcard_key(SocketEntry *e) u_int seconds = 0; time_t death = 0; struct sshkey **keys = NULL, *k; - Identity *id; struct dest_constraint *dest_constraints = NULL; - size_t ndest_constraints = 0; + size_t j, ndest_constraints = 0, ncerts = 0; + struct sshkey **certs = NULL; + int cert_only = 0; debug2_f("entering"); if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || @@ -1494,7 +1559,8 @@ process_add_smartcard_key(SocketEntry *e) goto send; } if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm, - NULL, &dest_constraints, &ndest_constraints) != 0) { + NULL, &dest_constraints, &ndest_constraints, &cert_only, + &ncerts, &certs) != 0) { error_f("failed to parse constraints"); goto send; } @@ -1520,25 +1586,28 @@ process_add_smartcard_key(SocketEntry *e) count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments); for (i = 0; i < count; i++) { - k = keys[i]; - if (lookup_identity(k) == NULL) { - id = xcalloc(1, sizeof(Identity)); - id->key = k; - keys[i] = NULL; /* transferred */ - id->provider = xstrdup(canonical_provider); - if (*comments[i] != '\0') { - id->comment = comments[i]; - comments[i] = NULL; /* transferred */ - } else { - id->comment = xstrdup(canonical_provider); - } - id->death = death; - id->confirm = confirm; - id->dest_constraints = dup_dest_constraints( + if (comments[i] == NULL || comments[i][0] == '\0') { + free(comments[i]); + comments[i] = xstrdup(canonical_provider); + } + for (j = 0; j < ncerts; j++) { + if (!sshkey_is_cert(certs[j])) + continue; + if (!sshkey_equal_public(keys[i], certs[j])) + continue; + if (pkcs11_make_cert(keys[i], certs[j], &k) != 0) + continue; + add_p11_identity(k, xstrdup(comments[i]), + canonical_provider, death, confirm, dest_constraints, ndest_constraints); - id->ndest_constraints = ndest_constraints; - TAILQ_INSERT_TAIL(&idtab->idlist, id, next); - idtab->nentries++; + success = 1; + } + if (!cert_only && lookup_identity(keys[i]) == NULL) { + add_p11_identity(keys[i], comments[i], + canonical_provider, death, confirm, + dest_constraints, ndest_constraints); + keys[i] = NULL; /* transferred */ + comments[i] = NULL; /* transferred */ success = 1; } /* XXX update constraints for existing keys */ @@ -1551,6 +1620,9 @@ send: free(keys); free(comments); free_dest_constraints(dest_constraints, ndest_constraints); + for (j = 0; j < ncerts; j++) + sshkey_free(certs[j]); + free(certs); send_status(e, success); } diff --git a/ssh-pkcs11-client.c b/ssh-pkcs11-client.c index 061b0681e..82e86a518 100644 --- a/ssh-pkcs11-client.c +++ b/ssh-pkcs11-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-pkcs11-client.c,v 1.18 2023/07/19 14:03:45 djm Exp $ */ +/* $OpenBSD: ssh-pkcs11-client.c,v 1.19 2023/12/18 14:46:56 djm Exp $ */ /* * Copyright (c) 2010 Markus Friedl. All rights reserved. * Copyright (c) 2014 Pedro Martelletto. All rights reserved. @@ -426,6 +426,60 @@ wrap_key(struct helper *helper, struct sshkey *k) helper->path, helper->nrsa, helper->nec); } +/* + * Make a private PKCS#11-backed certificate by grafting a previously-loaded + * PKCS#11 private key and a public certificate key. + */ +int +pkcs11_make_cert(const struct sshkey *priv, + const struct sshkey *certpub, struct sshkey **certprivp) +{ + struct helper *helper = NULL; + struct sshkey *ret; + int r; + + debug3_f("private key type %s cert type %s", sshkey_type(priv), + sshkey_type(certpub)); + *certprivp = NULL; + if (!sshkey_is_cert(certpub) || sshkey_is_cert(priv) || + !sshkey_equal_public(priv, certpub)) { + error_f("private key %s doesn't match cert %s", + sshkey_type(priv), sshkey_type(certpub)); + return SSH_ERR_INVALID_ARGUMENT; + } + *certprivp = NULL; + if (priv->type == KEY_RSA) { + if ((helper = helper_by_rsa(priv->rsa)) == NULL || + helper->fd == -1) + fatal_f("no helper for PKCS11 RSA key"); + if ((r = sshkey_from_private(priv, &ret)) != 0) + fatal_fr(r, "copy key"); + RSA_set_method(ret->rsa, helper->rsa_meth); + if (helper->nrsa++ >= INT_MAX) + fatal_f("RSA refcount error"); + } else if (priv->type == KEY_ECDSA) { + if ((helper = helper_by_ec(priv->ecdsa)) == NULL || + helper->fd == -1) + fatal_f("no helper for PKCS11 EC key"); + if ((r = sshkey_from_private(priv, &ret)) != 0) + fatal_fr(r, "copy key"); + EC_KEY_set_method(ret->ecdsa, helper->ec_meth); + if (helper->nec++ >= INT_MAX) + fatal_f("EC refcount error"); + } else + fatal_f("unknown key type %s", sshkey_type(priv)); + + ret->flags |= SSHKEY_FLAG_EXT; + if ((r = sshkey_to_certified(ret)) != 0 || + (r = sshkey_cert_copy(certpub, ret)) != 0) + fatal_fr(r, "graft certificate"); + debug3_f("provider %s remaining keys: %zu RSA %zu ECDSA", + helper->path, helper->nrsa, helper->nec); + /* success */ + *certprivp = ret; + return 0; +} + static int pkcs11_start_helper_methods(struct helper *helper) { diff --git a/ssh-pkcs11.h b/ssh-pkcs11.h index 81f1d7c5d..526022319 100644 --- a/ssh-pkcs11.h +++ b/ssh-pkcs11.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-pkcs11.h,v 1.6 2020/01/25 00:03:36 djm Exp $ */ +/* $OpenBSD: ssh-pkcs11.h,v 1.7 2023/12/18 14:46:56 djm Exp $ */ /* * Copyright (c) 2010 Markus Friedl. All rights reserved. * @@ -35,6 +35,9 @@ struct sshkey * u_int32_t *); #endif +/* Only available in ssh-pkcs11-client.c so far */ +int pkcs11_make_cert(const struct sshkey *, + const struct sshkey *, struct sshkey **); #if !defined(WITH_OPENSSL) && defined(ENABLE_PKCS11) #undef ENABLE_PKCS11 #endif