upstream: resident keys support in SK API
Adds a sk_load_resident_keys() function to the security key API that accepts a security key provider and a PIN and returns a list of keys. Implement support for this in the usbhid middleware. feedback and ok markus@ OpenBSD-Commit-ID: 67e984e4e87f4999ce447a6178c4249a9174eff0
This commit is contained in:
parent
2fe05fcb4a
commit
14cea36df3
13
sk-api.h
13
sk-api.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: sk-api.h,v 1.3 2019/12/30 09:19:52 djm Exp $ */
|
||||
/* $OpenBSD: sk-api.h,v 1.4 2019/12/30 09:21:16 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Google LLC
|
||||
*
|
||||
|
@ -52,6 +52,13 @@ struct sk_sign_response {
|
|||
size_t sig_s_len;
|
||||
};
|
||||
|
||||
struct sk_resident_key {
|
||||
uint8_t alg;
|
||||
size_t slot;
|
||||
char *application;
|
||||
struct sk_enroll_response key;
|
||||
};
|
||||
|
||||
#define SSH_SK_VERSION_MAJOR 0x00020000 /* current API version */
|
||||
#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000
|
||||
|
||||
|
@ -68,4 +75,8 @@ int sk_sign(int alg, const uint8_t *message, size_t message_len,
|
|||
const char *application, const uint8_t *key_handle, size_t key_handle_len,
|
||||
uint8_t flags, struct sk_sign_response **sign_response);
|
||||
|
||||
/* Enumerate all resident keys */
|
||||
int sk_load_resident_keys(const char *pin,
|
||||
struct sk_resident_key ***rks, size_t *nrks);
|
||||
|
||||
#endif /* _SK_API_H */
|
||||
|
|
238
sk-usbhid.c
238
sk-usbhid.c
|
@ -34,6 +34,7 @@
|
|||
#endif /* WITH_OPENSSL */
|
||||
|
||||
#include <fido.h>
|
||||
#include <fido/credman.h>
|
||||
|
||||
#ifndef SK_STANDALONE
|
||||
#include "log.h"
|
||||
|
@ -84,11 +85,19 @@ struct sk_sign_response {
|
|||
size_t sig_s_len;
|
||||
};
|
||||
|
||||
struct sk_resident_key {
|
||||
uint8_t alg;
|
||||
size_t slot;
|
||||
char *application;
|
||||
struct sk_enroll_response key;
|
||||
};
|
||||
|
||||
/* If building as part of OpenSSH, then rename exported functions */
|
||||
#if !defined(SK_STANDALONE)
|
||||
#define sk_api_version ssh_sk_api_version
|
||||
#define sk_enroll ssh_sk_enroll
|
||||
#define sk_sign ssh_sk_sign
|
||||
#define sk_api_version ssh_sk_api_version
|
||||
#define sk_enroll ssh_sk_enroll
|
||||
#define sk_sign ssh_sk_sign
|
||||
#define sk_load_resident_keys ssh_sk_load_resident_keys
|
||||
#endif
|
||||
|
||||
/* Return the version of the middleware API */
|
||||
|
@ -104,6 +113,10 @@ int sk_sign(int alg, const uint8_t *message, size_t message_len,
|
|||
const char *application, const uint8_t *key_handle, size_t key_handle_len,
|
||||
uint8_t flags, struct sk_sign_response **sign_response);
|
||||
|
||||
/* Load resident keys */
|
||||
int sk_load_resident_keys(const char *pin,
|
||||
struct sk_resident_key ***rks, size_t *nrks);
|
||||
|
||||
static void skdebug(const char *func, const char *fmt, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)));
|
||||
|
||||
|
@ -281,7 +294,8 @@ find_device(const uint8_t *message, size_t message_len, const char *application,
|
|||
* but the API expects a SEC1 octet string.
|
||||
*/
|
||||
static int
|
||||
pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response)
|
||||
pack_public_key_ecdsa(const fido_cred_t *cred,
|
||||
struct sk_enroll_response *response)
|
||||
{
|
||||
const uint8_t *ptr;
|
||||
BIGNUM *x = NULL, *y = NULL;
|
||||
|
@ -351,7 +365,8 @@ pack_public_key_ecdsa(fido_cred_t *cred, struct sk_enroll_response *response)
|
|||
#endif /* WITH_OPENSSL */
|
||||
|
||||
static int
|
||||
pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response)
|
||||
pack_public_key_ed25519(const fido_cred_t *cred,
|
||||
struct sk_enroll_response *response)
|
||||
{
|
||||
const uint8_t *ptr;
|
||||
size_t len;
|
||||
|
@ -382,7 +397,8 @@ pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response)
|
|||
}
|
||||
|
||||
static int
|
||||
pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response)
|
||||
pack_public_key(int alg, const fido_cred_t *cred,
|
||||
struct sk_enroll_response *response)
|
||||
{
|
||||
switch(alg) {
|
||||
#ifdef WITH_OPENSSL
|
||||
|
@ -715,4 +731,214 @@ sk_sign(int alg, const uint8_t *message, size_t message_len,
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
read_rks(const char *devpath, const char *pin,
|
||||
struct sk_resident_key ***rksp, size_t *nrksp)
|
||||
{
|
||||
int r = -1;
|
||||
fido_dev_t *dev = NULL;
|
||||
fido_credman_metadata_t *metadata = NULL;
|
||||
fido_credman_rp_t *rp = NULL;
|
||||
fido_credman_rk_t *rk = NULL;
|
||||
size_t i, j, nrp, nrk;
|
||||
const fido_cred_t *cred;
|
||||
struct sk_resident_key *srk = NULL, **tmp;
|
||||
|
||||
if ((dev = fido_dev_new()) == NULL) {
|
||||
skdebug(__func__, "fido_dev_new failed");
|
||||
return -1;
|
||||
}
|
||||
if ((r = fido_dev_open(dev, devpath)) != FIDO_OK) {
|
||||
skdebug(__func__, "fido_dev_open %s failed: %s",
|
||||
devpath, fido_strerr(r));
|
||||
fido_dev_free(&dev);
|
||||
return -1;
|
||||
}
|
||||
if ((metadata = fido_credman_metadata_new()) == NULL) {
|
||||
r = -1;
|
||||
skdebug(__func__, "alloc failed");
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((r = fido_credman_get_dev_metadata(dev, metadata, pin)) != 0) {
|
||||
if (r == FIDO_ERR_INVALID_COMMAND) {
|
||||
skdebug(__func__, "device %s does not support "
|
||||
"resident keys", devpath);
|
||||
r = 0;
|
||||
goto out;
|
||||
}
|
||||
skdebug(__func__, "get metadata for %s failed: %s",
|
||||
devpath, fido_strerr(r));
|
||||
goto out;
|
||||
}
|
||||
skdebug(__func__, "existing %llu, remaining %llu",
|
||||
(unsigned long long)fido_credman_rk_existing(metadata),
|
||||
(unsigned long long)fido_credman_rk_remaining(metadata));
|
||||
if ((rp = fido_credman_rp_new()) == NULL) {
|
||||
r = -1;
|
||||
skdebug(__func__, "alloc rp failed");
|
||||
goto out;
|
||||
}
|
||||
if ((r = fido_credman_get_dev_rp(dev, rp, pin)) != 0) {
|
||||
skdebug(__func__, "get RPs for %s failed: %s",
|
||||
devpath, fido_strerr(r));
|
||||
goto out;
|
||||
}
|
||||
nrp = fido_credman_rp_count(rp);
|
||||
skdebug(__func__, "Device %s has resident keys for %zu RPs",
|
||||
devpath, nrp);
|
||||
|
||||
/* Iterate over RP IDs that have resident keys */
|
||||
for (i = 0; i < nrp; i++) {
|
||||
skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
|
||||
i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i),
|
||||
fido_credman_rp_id_hash_len(rp, i));
|
||||
|
||||
/* Skip non-SSH RP IDs */
|
||||
if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
|
||||
continue;
|
||||
|
||||
fido_credman_rk_free(&rk);
|
||||
if ((rk = fido_credman_rk_new()) == NULL) {
|
||||
r = -1;
|
||||
skdebug(__func__, "alloc rk failed");
|
||||
goto out;
|
||||
}
|
||||
if ((r = fido_credman_get_dev_rk(dev, fido_credman_rp_id(rp, i),
|
||||
rk, pin)) != 0) {
|
||||
skdebug(__func__, "get RKs for %s slot %zu failed: %s",
|
||||
devpath, i, fido_strerr(r));
|
||||
goto out;
|
||||
}
|
||||
nrk = fido_credman_rk_count(rk);
|
||||
skdebug(__func__, "RP \"%s\" has %zu resident keys",
|
||||
fido_credman_rp_id(rp, i), nrk);
|
||||
|
||||
/* Iterate over resident keys for this RP ID */
|
||||
for (j = 0; j < nrk; j++) {
|
||||
if ((cred = fido_credman_rk(rk, j)) == NULL) {
|
||||
skdebug(__func__, "no RK in slot %zu", j);
|
||||
continue;
|
||||
}
|
||||
skdebug(__func__, "Device %s RP \"%s\" slot %zu: "
|
||||
"type %d", devpath, fido_credman_rp_id(rp, i), j,
|
||||
fido_cred_type(cred));
|
||||
|
||||
/* build response entry */
|
||||
if ((srk = calloc(1, sizeof(*srk))) == NULL ||
|
||||
(srk->key.key_handle = calloc(1,
|
||||
fido_cred_id_len(cred))) == NULL ||
|
||||
(srk->application = strdup(fido_credman_rp_id(rp,
|
||||
i))) == NULL) {
|
||||
r = -1;
|
||||
skdebug(__func__, "alloc sk_resident_key");
|
||||
goto out;
|
||||
}
|
||||
|
||||
srk->key.key_handle_len = fido_cred_id_len(cred);
|
||||
memcpy(srk->key.key_handle,
|
||||
fido_cred_id_ptr(cred),
|
||||
srk->key.key_handle_len);
|
||||
|
||||
switch (fido_cred_type(cred)) {
|
||||
case COSE_ES256:
|
||||
srk->alg = SK_ECDSA;
|
||||
break;
|
||||
case COSE_EDDSA:
|
||||
srk->alg = SK_ED25519;
|
||||
break;
|
||||
default:
|
||||
skdebug(__func__, "unsupported key type %d",
|
||||
fido_cred_type(cred));
|
||||
goto out;
|
||||
}
|
||||
|
||||
if ((r = pack_public_key(srk->alg, cred,
|
||||
&srk->key)) != 0) {
|
||||
skdebug(__func__, "pack public key failed");
|
||||
goto out;
|
||||
}
|
||||
/* append */
|
||||
if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1,
|
||||
sizeof(**rksp))) == NULL) {
|
||||
r = -1;
|
||||
skdebug(__func__, "alloc rksp");
|
||||
goto out;
|
||||
}
|
||||
*rksp = tmp;
|
||||
(*rksp)[(*nrksp)++] = srk;
|
||||
srk = NULL;
|
||||
}
|
||||
}
|
||||
/* Success */
|
||||
r = 0;
|
||||
out:
|
||||
if (srk != NULL) {
|
||||
free(srk->application);
|
||||
freezero(srk->key.public_key, srk->key.public_key_len);
|
||||
freezero(srk->key.key_handle, srk->key.key_handle_len);
|
||||
freezero(srk, sizeof(*srk));
|
||||
}
|
||||
fido_credman_rp_free(&rp);
|
||||
fido_credman_rk_free(&rk);
|
||||
fido_dev_close(dev);
|
||||
fido_dev_free(&dev);
|
||||
fido_credman_metadata_free(&metadata);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
sk_load_resident_keys(const char *pin,
|
||||
struct sk_resident_key ***rksp, size_t *nrksp)
|
||||
{
|
||||
int r = -1;
|
||||
fido_dev_info_t *devlist = NULL;
|
||||
size_t i, ndev = 0, nrks = 0;
|
||||
const fido_dev_info_t *di;
|
||||
struct sk_resident_key **rks = NULL;
|
||||
*rksp = NULL;
|
||||
*nrksp = 0;
|
||||
|
||||
if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
|
||||
skdebug(__func__, "fido_dev_info_new failed");
|
||||
goto out;
|
||||
}
|
||||
if ((r = fido_dev_info_manifest(devlist,
|
||||
MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) {
|
||||
skdebug(__func__, "fido_dev_info_manifest failed: %s",
|
||||
fido_strerr(r));
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < ndev; i++) {
|
||||
if ((di = fido_dev_info_ptr(devlist, i)) == NULL) {
|
||||
skdebug(__func__, "no dev info at %zu", i);
|
||||
continue;
|
||||
}
|
||||
skdebug(__func__, "trying %s", fido_dev_info_path(di));
|
||||
if ((r = read_rks(fido_dev_info_path(di), pin,
|
||||
&rks, &nrks)) != 0) {
|
||||
skdebug(__func__, "read_rks failed for %s",
|
||||
fido_dev_info_path(di));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* success */
|
||||
r = 0;
|
||||
*rksp = rks;
|
||||
*nrksp = nrks;
|
||||
rks = NULL;
|
||||
nrks = 0;
|
||||
out:
|
||||
for (i = 0; i < nrks; i++) {
|
||||
free(rks[i]->application);
|
||||
freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
|
||||
freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
|
||||
freezero(rks[i], sizeof(*rks[i]));
|
||||
}
|
||||
free(rks);
|
||||
fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif /* ENABLE_SK_INTERNAL */
|
||||
|
|
116
ssh-sk.c
116
ssh-sk.c
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh-sk.c,v 1.19 2019/12/30 09:20:36 djm Exp $ */
|
||||
/* $OpenBSD: ssh-sk.c,v 1.20 2019/12/30 09:21:16 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Google LLC
|
||||
*
|
||||
|
@ -60,6 +60,10 @@ struct sshsk_provider {
|
|||
const char *application,
|
||||
const uint8_t *key_handle, size_t key_handle_len,
|
||||
uint8_t flags, struct sk_sign_response **sign_response);
|
||||
|
||||
/* Enumerate resident keys */
|
||||
int (*sk_load_resident_keys)(const char *pin,
|
||||
struct sk_resident_key ***rks, size_t *nrks);
|
||||
};
|
||||
|
||||
/* Built-in version */
|
||||
|
@ -70,6 +74,8 @@ int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len,
|
|||
const char *application,
|
||||
const uint8_t *key_handle, size_t key_handle_len,
|
||||
uint8_t flags, struct sk_sign_response **sign_response);
|
||||
int ssh_sk_load_resident_keys(const char *pin,
|
||||
struct sk_resident_key ***rks, size_t *nrks);
|
||||
|
||||
static void
|
||||
sshsk_free(struct sshsk_provider *p)
|
||||
|
@ -101,6 +107,7 @@ sshsk_open(const char *path)
|
|||
#ifdef ENABLE_SK_INTERNAL
|
||||
ret->sk_enroll = ssh_sk_enroll;
|
||||
ret->sk_sign = ssh_sk_sign;
|
||||
ret->sk_load_resident_keys = ssh_sk_load_resident_keys;
|
||||
#else
|
||||
error("internal security key support not enabled");
|
||||
#endif
|
||||
|
@ -136,6 +143,12 @@ sshsk_open(const char *path)
|
|||
path, dlerror());
|
||||
goto fail;
|
||||
}
|
||||
if ((ret->sk_load_resident_keys = dlsym(ret->dlhandle,
|
||||
"sk_load_resident_keys")) == NULL) {
|
||||
error("Security key provider %s dlsym(sk_load_resident_keys) "
|
||||
"failed: %s", path, dlerror());
|
||||
goto fail;
|
||||
}
|
||||
/* success */
|
||||
return ret;
|
||||
fail:
|
||||
|
@ -264,9 +277,7 @@ sshsk_key_from_response(int alg, const char *application, uint8_t flags,
|
|||
*keyp = NULL;
|
||||
|
||||
/* Check response validity */
|
||||
if (resp->public_key == NULL || resp->key_handle == NULL ||
|
||||
resp->signature == NULL ||
|
||||
(resp->attestation_cert == NULL && resp->attestation_cert_len != 0)) {
|
||||
if (resp->public_key == NULL || resp->key_handle == NULL) {
|
||||
error("%s: sk_enroll response invalid", __func__);
|
||||
r = SSH_ERR_INVALID_FORMAT;
|
||||
goto out;
|
||||
|
@ -595,4 +606,101 @@ sshsk_sign(const char *provider_path, struct sshkey *key,
|
|||
sshbuf_free(inner_sig);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (nrks == 0 || rks == NULL)
|
||||
return;
|
||||
for (i = 0; i < nrks; i++) {
|
||||
free(rks[i]->application);
|
||||
freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
|
||||
freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
|
||||
freezero(rks[i]->key.signature, rks[i]->key.signature_len);
|
||||
freezero(rks[i]->key.attestation_cert,
|
||||
rks[i]->key.attestation_cert_len);
|
||||
freezero(rks[i], sizeof(**rks));
|
||||
}
|
||||
free(rks);
|
||||
}
|
||||
|
||||
int
|
||||
sshsk_load_resident(const char *provider_path, const char *pin,
|
||||
struct sshkey ***keysp, size_t *nkeysp)
|
||||
{
|
||||
struct sshsk_provider *skp = NULL;
|
||||
int r = SSH_ERR_INTERNAL_ERROR;
|
||||
struct sk_resident_key **rks = NULL;
|
||||
size_t i, nrks = 0, nkeys = 0;
|
||||
struct sshkey *key = NULL, **keys = NULL, **tmp;
|
||||
uint8_t flags;
|
||||
|
||||
debug("%s: provider \"%s\"%s", __func__, provider_path,
|
||||
(pin != NULL && *pin != '\0') ? ", have-pin": "");
|
||||
|
||||
if (keysp == NULL || nkeysp == NULL)
|
||||
return SSH_ERR_INVALID_ARGUMENT;
|
||||
*keysp = NULL;
|
||||
*nkeysp = 0;
|
||||
|
||||
if ((skp = sshsk_open(provider_path)) == NULL) {
|
||||
r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
|
||||
goto out;
|
||||
}
|
||||
if ((r = skp->sk_load_resident_keys(pin, &rks, &nrks)) != 0) {
|
||||
error("Security key provider %s returned failure %d",
|
||||
provider_path, r);
|
||||
r = SSH_ERR_INVALID_FORMAT; /* XXX error codes in API? */
|
||||
goto out;
|
||||
}
|
||||
for (i = 0; i < nrks; i++) {
|
||||
debug3("%s: rk %zu: slot = %zu, alg = %d, application = \"%s\"",
|
||||
__func__, i, rks[i]->slot, rks[i]->alg,
|
||||
rks[i]->application);
|
||||
/* XXX need better filter here */
|
||||
if (strncmp(rks[i]->application, "ssh:", 4) != 0)
|
||||
continue;
|
||||
switch (rks[i]->alg) {
|
||||
case SSH_SK_ECDSA:
|
||||
case SSH_SK_ED25519:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
/* XXX where to get flags? */
|
||||
flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY;
|
||||
if ((r = sshsk_key_from_response(rks[i]->alg,
|
||||
rks[i]->application, flags, &rks[i]->key, &key)) != 0)
|
||||
goto out;
|
||||
if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
|
||||
sizeof(*tmp))) == NULL) {
|
||||
error("%s: recallocarray failed", __func__);
|
||||
r = SSH_ERR_ALLOC_FAIL;
|
||||
goto out;
|
||||
}
|
||||
keys = tmp;
|
||||
keys[nkeys++] = key;
|
||||
key = NULL;
|
||||
/* XXX synthesise comment */
|
||||
}
|
||||
/* success */
|
||||
*keysp = keys;
|
||||
*nkeysp = nkeys;
|
||||
keys = NULL;
|
||||
nkeys = 0;
|
||||
r = 0;
|
||||
out:
|
||||
sshsk_free(skp);
|
||||
sshsk_free_sk_resident_keys(rks, nrks);
|
||||
sshkey_free(key);
|
||||
if (nkeys != 0) {
|
||||
for (i = 0; i < nkeys; i++)
|
||||
sshkey_free(keys[i]);
|
||||
free(keys);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
#endif /* ENABLE_SK */
|
||||
|
|
11
ssh-sk.h
11
ssh-sk.h
|
@ -1,4 +1,4 @@
|
|||
/* $OpenBSD: ssh-sk.h,v 1.6 2019/12/13 19:09:10 djm Exp $ */
|
||||
/* $OpenBSD: ssh-sk.h,v 1.7 2019/12/30 09:21:16 djm Exp $ */
|
||||
/*
|
||||
* Copyright (c) 2019 Google LLC
|
||||
*
|
||||
|
@ -45,5 +45,14 @@ int sshsk_sign(const char *provider_path, struct sshkey *key,
|
|||
u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
|
||||
u_int compat);
|
||||
|
||||
/*
|
||||
* Enumerates and loads all SSH-compatible resident keys from a security
|
||||
* key.
|
||||
*
|
||||
* Returns 0 on success or a ssherr.h error code on failure.
|
||||
*/
|
||||
int sshsk_load_resident(const char *provider_path, const char *pin,
|
||||
struct sshkey ***keysp, size_t *nkeysp);
|
||||
|
||||
#endif /* _SSH_SK_H */
|
||||
|
||||
|
|
Loading…
Reference in New Issue