diff --git a/sk-api.h b/sk-api.h index 5947e0ed7..10f1fdb10 100644 --- a/sk-api.h +++ b/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 */ diff --git a/sk-usbhid.c b/sk-usbhid.c index 61b52bbb9..fa4424483 100644 --- a/sk-usbhid.c +++ b/sk-usbhid.c @@ -34,6 +34,7 @@ #endif /* WITH_OPENSSL */ #include +#include #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 */ diff --git a/ssh-sk.c b/ssh-sk.c index 01927669e..d48f34e30 100644 --- a/ssh-sk.c +++ b/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 */ diff --git a/ssh-sk.h b/ssh-sk.h index 4d667884e..1afe839db 100644 --- a/ssh-sk.h +++ b/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 */