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
This commit is contained in:
djm@openbsd.org 2023-12-18 14:46:56 +00:00 committed by Damien Miller
parent 881d9c6af9
commit 4448a2938a
No known key found for this signature in database
8 changed files with 299 additions and 70 deletions

View File

@ -81,4 +81,35 @@ the constraint is:
This option is only valid for XMSS keys. 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 $

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -504,9 +504,10 @@ encode_dest_constraint(struct sshbuf *m, const struct dest_constraint *dc)
} }
static int static int
encode_constraints(struct sshbuf *m, u_int life, u_int confirm, u_int maxsign, encode_constraints(struct sshbuf *m, u_int life, u_int confirm,
const char *provider, struct dest_constraint **dest_constraints, u_int maxsign, const char *provider,
size_t ndest_constraints) struct dest_constraint **dest_constraints, size_t ndest_constraints,
int cert_only, struct sshkey **certs, size_t ncerts)
{ {
int r; int r;
struct sshbuf *b = NULL; 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 || "restrict-destination-v00@openssh.com")) != 0 ||
(r = sshbuf_put_stringb(m, b)) != 0) (r = sshbuf_put_stringb(m, b)) != 0)
goto out; 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; r = 0;
out: out:
@ -607,7 +629,7 @@ ssh_add_identity_constrained(int sock, struct sshkey *key,
} }
if (constrained && if (constrained &&
(r = encode_constraints(msg, life, confirm, maxsign, (r = encode_constraints(msg, life, confirm, maxsign,
provider, dest_constraints, ndest_constraints)) != 0) provider, dest_constraints, ndest_constraints, 0, NULL, 0)) != 0)
goto out; goto out;
if ((r = ssh_request_reply_decode(sock, msg)) != 0) if ((r = ssh_request_reply_decode(sock, msg)) != 0)
goto out; goto out;
@ -662,10 +684,11 @@ ssh_remove_identity(int sock, const struct sshkey *key)
int int
ssh_update_card(int sock, int add, const char *reader_id, const char *pin, ssh_update_card(int sock, int add, const char *reader_id, const char *pin,
u_int life, u_int confirm, 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; struct sshbuf *msg;
int r, constrained = (life || confirm || dest_constraints); int r, constrained = (life || confirm || dest_constraints || certs);
u_char type; u_char type;
if (add) { if (add) {
@ -683,7 +706,8 @@ ssh_update_card(int sock, int add, const char *reader_id, const char *pin,
goto out; goto out;
if (constrained && if (constrained &&
(r = encode_constraints(msg, life, confirm, 0, NULL, (r = encode_constraints(msg, life, confirm, 0, NULL,
dest_constraints, ndest_constraints)) != 0) dest_constraints, ndest_constraints,
cert_only, certs, ncerts)) != 0)
goto out; goto out;
if ((r = ssh_request_reply_decode(sock, msg)) != 0) if ((r = ssh_request_reply_decode(sock, msg)) != 0)
goto out; goto out;

View File

@ -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> * 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, int ssh_update_card(int sock, int add, const char *reader_id,
const char *pin, u_int life, u_int confirm, const char *pin, u_int life, u_int confirm,
struct dest_constraint **dest_constraints, 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_remove_all_identities(int sock, int version);
int ssh_agent_sign(int sock, const struct sshkey *key, int ssh_agent_sign(int sock, const struct sshkey *key,

View File

@ -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> .\" Author: Tatu Ylonen <ylo@cs.hut.fi>
.\" Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland .\" 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 .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" 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 .Dt SSH-ADD 1
.Os .Os
.Sh NAME .Sh NAME
@ -43,7 +43,7 @@
.Nd adds private key identities to the OpenSSH authentication agent .Nd adds private key identities to the OpenSSH authentication agent
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm ssh-add .Nm ssh-add
.Op Fl cDdKkLlqvXx .Op Fl cCDdKkLlqvXx
.Op Fl E Ar fingerprint_hash .Op Fl E Ar fingerprint_hash
.Op Fl H Ar hostkey_file .Op Fl H Ar hostkey_file
.Op Fl h Ar destination_constraint .Op Fl h Ar destination_constraint
@ -52,6 +52,8 @@
.Op Ar .Op Ar
.Nm ssh-add .Nm ssh-add
.Fl s Ar pkcs11 .Fl s Ar pkcs11
.Op Fl vC
.Op Ar certificate ...
.Nm ssh-add .Nm ssh-add
.Fl e Ar pkcs11 .Fl e Ar pkcs11
.Nm ssh-add .Nm ssh-add
@ -100,6 +102,9 @@ Confirmation is performed by
Successful confirmation is signaled by a zero exit status from Successful confirmation is signaled by a zero exit status from
.Xr ssh-askpass 1 , .Xr ssh-askpass 1 ,
rather than text entered into the requester. 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 .It Fl D
Deletes all identities from the agent. Deletes all identities from the agent.
.It Fl d .It Fl d
@ -228,6 +233,9 @@ internal USB HID support.
.It Fl s Ar pkcs11 .It Fl s Ar pkcs11
Add keys provided by the PKCS#11 shared library Add keys provided by the PKCS#11 shared library
.Ar pkcs11 . .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 ... .It Fl T Ar pubkey ...
Tests whether the private keys that correspond to the specified Tests whether the private keys that correspond to the specified
.Ar pubkey .Ar pubkey

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -131,7 +131,7 @@ delete_one(int agent_fd, const struct sshkey *key, const char *comment,
} }
static int 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; char *line = NULL, *cp;
size_t linesize = 0; size_t linesize = 0;
@ -152,30 +152,40 @@ delete_stdin(int agent_fd, int qflag)
error_r(r, "(stdin):%d: invalid key", lnum); error_r(r, "(stdin):%d: invalid key", lnum);
continue; continue;
} }
if (delete_one(agent_fd, key, cp, "(stdin)", qflag) == 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; ret = 0;
} }
}
sshkey_free(key); sshkey_free(key);
free(line); free(line);
return ret; return ret;
} }
static int 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; struct sshkey *public, *cert = NULL;
char *certpath = NULL, *comment = NULL; char *certpath = NULL, *comment = NULL;
int r, ret = -1; int r, ret = -1;
if (strcmp(filename, "-") == 0) 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) { if ((r = sshkey_load_public(filename, &public, &comment)) != 0) {
printf("Bad key file %s: %s\n", filename, ssh_err(r)); printf("Bad key file %s: %s\n", filename, ssh_err(r));
return -1; return -1;
} }
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) if (delete_one(agent_fd, public, comment, filename, qflag) == 0)
ret = 0; ret = 0;
}
if (key_only) if (key_only)
goto out; goto out;
@ -231,8 +241,9 @@ delete_all(int agent_fd, int qflag)
} }
static int static int
add_file(int agent_fd, const char *filename, int key_only, int qflag, add_file(int agent_fd, const char *filename, int key_only, int cert_only,
const char *skprovider, struct dest_constraint **dest_constraints, int qflag, const char *skprovider,
struct dest_constraint **dest_constraints,
size_t ndest_constraints) size_t ndest_constraints)
{ {
struct sshkey *private, *cert; struct sshkey *private, *cert;
@ -361,7 +372,8 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
skprovider = NULL; 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, lifetime, confirm, maxsign, skprovider,
dest_constraints, ndest_constraints)) == 0) { dest_constraints, ndest_constraints)) == 0) {
ret = 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); xasprintf(&certpath, "%s-cert.pub", filename);
if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) { if ((r = sshkey_load_public(certpath, &cert, NULL)) != 0) {
if (r != SSH_ERR_SYSTEM_ERROR || errno != ENOENT) 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; goto out;
} }
@ -445,11 +458,16 @@ add_file(int agent_fd, const char *filename, int key_only, int qflag,
static int static int
update_card(int agent_fd, int add, const char *id, int qflag, 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; char *pin = NULL;
int r, ret = -1; int r, ret = -1;
if (key_only)
ncerts = 0;
if (add) { if (add) {
if ((pin = read_passphrase("Enter passphrase for PKCS#11: ", if ((pin = read_passphrase("Enter passphrase for PKCS#11: ",
RP_ALLOW_STDIN)) == NULL) 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, 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; ret = 0;
if (!qflag) { if (!qflag) {
fprintf(stderr, "Card %s: %s\n", fprintf(stderr, "Card %s: %s\n",
@ -633,16 +652,17 @@ load_resident_keys(int agent_fd, const char *skprovider, int qflag,
} }
static int static int
do_file(int agent_fd, int deleting, int key_only, char *file, int qflag, do_file(int agent_fd, int deleting, int key_only, int cert_only,
const char *skprovider, struct dest_constraint **dest_constraints, char *file, int qflag, const char *skprovider,
size_t ndest_constraints) struct dest_constraint **dest_constraints, size_t ndest_constraints)
{ {
if (deleting) { 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; return -1;
} else { } else {
if (add_file(agent_fd, file, key_only, qflag, skprovider, if (add_file(agent_fd, file, key_only, cert_only, qflag,
dest_constraints, ndest_constraints) == -1) skprovider, dest_constraints, ndest_constraints) == -1)
return -1; return -1;
} }
return 0; return 0;
@ -790,12 +810,14 @@ main(int argc, char **argv)
int agent_fd; int agent_fd;
char *pkcs11provider = NULL, *skprovider = NULL; char *pkcs11provider = NULL, *skprovider = NULL;
char **dest_constraint_strings = NULL, **hostkey_files = NULL; char **dest_constraint_strings = NULL, **hostkey_files = NULL;
int r, i, ch, deleting = 0, ret = 0, key_only = 0, do_download = 0; int r, i, ch, deleting = 0, ret = 0, key_only = 0, cert_only = 0;
int xflag = 0, lflag = 0, Dflag = 0, qflag = 0, Tflag = 0; int do_download = 0, xflag = 0, lflag = 0, Dflag = 0;
int qflag = 0, Tflag = 0;
SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
LogLevel log_level = SYSLOG_LEVEL_INFO; LogLevel log_level = SYSLOG_LEVEL_INFO;
struct sshkey *k, **certs = NULL;
struct dest_constraint **dest_constraints = 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 */ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */
sanitise_stdfd(); sanitise_stdfd();
@ -822,7 +844,7 @@ main(int argc, char **argv)
skprovider = getenv("SSH_SK_PROVIDER"); 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) { switch (ch) {
case 'v': case 'v':
if (log_level == SYSLOG_LEVEL_INFO) if (log_level == SYSLOG_LEVEL_INFO)
@ -844,6 +866,9 @@ main(int argc, char **argv)
case 'k': case 'k':
key_only = 1; key_only = 1;
break; break;
case 'C':
cert_only = 1;
break;
case 'K': case 'K':
do_download = 1; do_download = 1;
break; break;
@ -962,8 +987,19 @@ main(int argc, char **argv)
goto done; goto done;
} }
if (pkcs11provider != NULL) { 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, 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; ret = 1;
goto done; goto done;
} }
@ -993,8 +1029,8 @@ main(int argc, char **argv)
default_files[i]); default_files[i]);
if (stat(buf, &st) == -1) if (stat(buf, &st) == -1)
continue; continue;
if (do_file(agent_fd, deleting, key_only, buf, if (do_file(agent_fd, deleting, key_only, cert_only,
qflag, skprovider, buf, qflag, skprovider,
dest_constraints, ndest_constraints) == -1) dest_constraints, ndest_constraints) == -1)
ret = 1; ret = 1;
else else
@ -1004,7 +1040,7 @@ main(int argc, char **argv)
ret = 1; ret = 1;
} else { } else {
for (i = 0; i < argc; i++) { 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, argv[i], qflag, skprovider,
dest_constraints, ndest_constraints) == -1) dest_constraints, ndest_constraints) == -1)
ret = 1; ret = 1;

View File

@ -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> * Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@ -105,6 +105,8 @@
#define AGENT_MAX_SID_LEN 128 #define AGENT_MAX_SID_LEN 128
/* Maximum number of destination constraints to accept on a key */ /* Maximum number of destination constraints to accept on a key */
#define AGENT_MAX_DEST_CONSTRAINTS 1024 #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 */ /* XXX store hostkey_sid in a refcounted tree */
@ -1158,11 +1160,14 @@ parse_dest_constraint(struct sshbuf *m, struct dest_constraint *dc)
static int static int
parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp, 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; char *ext_name = NULL;
int r; int r;
struct sshbuf *b = NULL; struct sshbuf *b = NULL;
u_char v;
struct sshkey *k;
if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) { if ((r = sshbuf_get_cstring(m, &ext_name, NULL)) != 0) {
error_fr(r, "parse constraint extension"); error_fr(r, "parse constraint extension");
@ -1205,6 +1210,36 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp,
*dcsp + (*ndcsp)++)) != 0) *dcsp + (*ndcsp)++)) != 0)
goto out; /* error already logged */ 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 { } else {
error_f("unsupported constraint \"%s\"", ext_name); error_f("unsupported constraint \"%s\"", ext_name);
r = SSH_ERR_FEATURE_UNSUPPORTED; r = SSH_ERR_FEATURE_UNSUPPORTED;
@ -1221,7 +1256,8 @@ parse_key_constraint_extension(struct sshbuf *m, char **sk_providerp,
static int static int
parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp, parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp,
u_int *secondsp, int *confirmp, char **sk_providerp, 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; u_char ctype;
int r; int r;
@ -1276,7 +1312,8 @@ parse_key_constraints(struct sshbuf *m, struct sshkey *k, time_t *deathp,
break; break;
case SSH_AGENT_CONSTRAIN_EXTENSION: case SSH_AGENT_CONSTRAIN_EXTENSION:
if ((r = parse_key_constraint_extension(m, 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 */ goto out; /* error already logged */
break; break;
default: default:
@ -1313,7 +1350,8 @@ process_add_identity(SocketEntry *e)
goto out; goto out;
} }
if (parse_key_constraints(e->request, k, &death, &seconds, &confirm, 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"); error_f("failed to parse constraints");
sshbuf_reset(e->request); sshbuf_reset(e->request);
goto out; goto out;
@ -1473,6 +1511,32 @@ no_identities(SocketEntry *e)
sshbuf_free(msg); 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 #ifdef ENABLE_PKCS11
static void static void
process_add_smartcard_key(SocketEntry *e) process_add_smartcard_key(SocketEntry *e)
@ -1483,9 +1547,10 @@ process_add_smartcard_key(SocketEntry *e)
u_int seconds = 0; u_int seconds = 0;
time_t death = 0; time_t death = 0;
struct sshkey **keys = NULL, *k; struct sshkey **keys = NULL, *k;
Identity *id;
struct dest_constraint *dest_constraints = NULL; 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"); debug2_f("entering");
if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 || if ((r = sshbuf_get_cstring(e->request, &provider, NULL)) != 0 ||
@ -1494,7 +1559,8 @@ process_add_smartcard_key(SocketEntry *e)
goto send; goto send;
} }
if (parse_key_constraints(e->request, NULL, &death, &seconds, &confirm, 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"); error_f("failed to parse constraints");
goto send; goto send;
} }
@ -1520,25 +1586,28 @@ process_add_smartcard_key(SocketEntry *e)
count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments); count = pkcs11_add_provider(canonical_provider, pin, &keys, &comments);
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
k = keys[i]; if (comments[i] == NULL || comments[i][0] == '\0') {
if (lookup_identity(k) == NULL) { free(comments[i]);
id = xcalloc(1, sizeof(Identity)); comments[i] = xstrdup(canonical_provider);
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; for (j = 0; j < ncerts; j++) {
id->confirm = confirm; if (!sshkey_is_cert(certs[j]))
id->dest_constraints = dup_dest_constraints( 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); dest_constraints, ndest_constraints);
id->ndest_constraints = ndest_constraints; success = 1;
TAILQ_INSERT_TAIL(&idtab->idlist, id, next); }
idtab->nentries++; 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; success = 1;
} }
/* XXX update constraints for existing keys */ /* XXX update constraints for existing keys */
@ -1551,6 +1620,9 @@ send:
free(keys); free(keys);
free(comments); free(comments);
free_dest_constraints(dest_constraints, ndest_constraints); free_dest_constraints(dest_constraints, ndest_constraints);
for (j = 0; j < ncerts; j++)
sshkey_free(certs[j]);
free(certs);
send_status(e, success); send_status(e, success);
} }

View File

@ -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) 2010 Markus Friedl. All rights reserved.
* Copyright (c) 2014 Pedro Martelletto. 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); 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 static int
pkcs11_start_helper_methods(struct helper *helper) pkcs11_start_helper_methods(struct helper *helper)
{ {

View File

@ -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. * Copyright (c) 2010 Markus Friedl. All rights reserved.
* *
@ -35,6 +35,9 @@ struct sshkey *
u_int32_t *); u_int32_t *);
#endif #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) #if !defined(WITH_OPENSSL) && defined(ENABLE_PKCS11)
#undef ENABLE_PKCS11 #undef ENABLE_PKCS11
#endif #endif