summaryrefslogtreecommitdiff
path: root/usr.bin/ssh
diff options
context:
space:
mode:
authorDamien Miller <djm@cvs.openbsd.org>2023-12-18 14:46:57 +0000
committerDamien Miller <djm@cvs.openbsd.org>2023-12-18 14:46:57 +0000
commit6195a66f324e9f1cb5ff4ee6aa00b12e2e23aa96 (patch)
treed41e9d7db99c9a687232992fc6818af04dc96b0f /usr.bin/ssh
parent8caaaa7b86765da8a5b740d5f9c708eab34186be (diff)
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@
Diffstat (limited to 'usr.bin/ssh')
-rw-r--r--usr.bin/ssh/PROTOCOL.agent33
-rw-r--r--usr.bin/ssh/authfd.c40
-rw-r--r--usr.bin/ssh/authfd.h5
-rw-r--r--usr.bin/ssh/ssh-add.114
-rw-r--r--usr.bin/ssh/ssh-add.c92
-rw-r--r--usr.bin/ssh/ssh-agent.c124
-rw-r--r--usr.bin/ssh/ssh-pkcs11-client.c56
-rw-r--r--usr.bin/ssh/ssh-pkcs11.h5
8 files changed, 299 insertions, 70 deletions
diff --git a/usr.bin/ssh/PROTOCOL.agent b/usr.bin/ssh/PROTOCOL.agent
index 1c4841147a2..e4a6b74c50b 100644
--- a/usr.bin/ssh/PROTOCOL.agent
+++ b/usr.bin/ssh/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/usr.bin/ssh/authfd.c b/usr.bin/ssh/authfd.c
index 4e676312634..20b461e89a5 100644
--- a/usr.bin/ssh/authfd.c
+++ b/usr.bin/ssh/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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -503,9 +503,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;
@@ -549,6 +550,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:
@@ -606,7 +628,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;
@@ -661,10 +683,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) {
@@ -682,7 +705,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/usr.bin/ssh/authfd.h b/usr.bin/ssh/authfd.h
index 7a1c0ddff98..c1e4b405ce2 100644
--- a/usr.bin/ssh/authfd.h
+++ b/usr.bin/ssh/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@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/usr.bin/ssh/ssh-add.1 b/usr.bin/ssh/ssh-add.1
index 4601f5981cd..f0186cd5fd7 100644
--- a/usr.bin/ssh/ssh-add.1
+++ b/usr.bin/ssh/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@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@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/usr.bin/ssh/ssh-add.c b/usr.bin/ssh/ssh-add.c
index ab92a540fe9..ef321ea95de 100644
--- a/usr.bin/ssh/ssh-add.c
+++ b/usr.bin/ssh/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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -124,7 +124,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;
@@ -145,8 +145,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);
@@ -154,21 +159,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;
@@ -224,8 +234,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;
@@ -354,7 +365,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;
@@ -383,7 +395,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;
}
@@ -438,11 +451,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)
@@ -450,7 +468,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",
@@ -626,16 +645,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;
@@ -783,12 +803,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();
@@ -815,7 +837,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)
@@ -837,6 +859,9 @@ main(int argc, char **argv)
case 'k':
key_only = 1;
break;
+ case 'C':
+ cert_only = 1;
+ break;
case 'K':
do_download = 1;
break;
@@ -952,8 +977,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;
}
@@ -983,8 +1019,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
@@ -994,7 +1030,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/usr.bin/ssh/ssh-agent.c b/usr.bin/ssh/ssh-agent.c
index 9e7b6b77616..8012ec2cf80 100644
--- a/usr.bin/ssh/ssh-agent.c
+++ b/usr.bin/ssh/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@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -92,6 +92,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 */
@@ -1145,11 +1147,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");
@@ -1192,6 +1197,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;
@@ -1208,7 +1243,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;
@@ -1263,7 +1299,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:
@@ -1300,7 +1337,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;
@@ -1460,6 +1498,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)
@@ -1470,9 +1534,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 ||
@@ -1481,7 +1546,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;
}
@@ -1507,25 +1573,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);
+ 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);
- id->ndest_constraints = ndest_constraints;
- TAILQ_INSERT_TAIL(&idtab->idlist, id, next);
- idtab->nentries++;
+ keys[i] = NULL; /* transferred */
+ comments[i] = NULL; /* transferred */
success = 1;
}
/* XXX update constraints for existing keys */
@@ -1538,6 +1607,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/usr.bin/ssh/ssh-pkcs11-client.c b/usr.bin/ssh/ssh-pkcs11-client.c
index d4221644ca9..af9ad1b887f 100644
--- a/usr.bin/ssh/ssh-pkcs11-client.c
+++ b/usr.bin/ssh/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.
@@ -405,6 +405,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/usr.bin/ssh/ssh-pkcs11.h b/usr.bin/ssh/ssh-pkcs11.h
index 81f1d7c5d39..526022319b4 100644
--- a/usr.bin/ssh/ssh-pkcs11.h
+++ b/usr.bin/ssh/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