[openssh-commits] [openssh] 04/12: upstream: Make it possible to load certs from PKCS#11 tokens

git+noreply at mindrot.org git+noreply at mindrot.org
Tue Dec 19 02:08:04 AEDT 2023


This is an automated email from the git hooks/post-receive script.

djm pushed a commit to branch V_9_6
in repository openssh.

commit 4448a2938abc76e6bd33ba09b2ec17a216dfb491
Author: djm at openbsd.org <djm at openbsd.org>
Date:   Mon Dec 18 14:46:56 2023 +0000

    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 1c484114..e4a6b74c 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 at 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 at 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 25a36366..e04ad0cf 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 at 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 at 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 7a1c0ddf..c1e4b405 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 <ylo at cs.hut.fi>
@@ -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 4601f598..f0186cd5 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 <ylo at cs.hut.fi>
 .\" Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 775a9a8e..99ba23b5 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 1d4c321e..12ec66cf 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 <ylo at cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo at cs.hut.fi>, 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 at 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 061b0681..82e86a51 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 81f1d7c5..52602231 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

-- 
To stop receiving notification emails like this one, please contact
djm at mindrot.org.


More information about the openssh-commits mailing list