1*9469f4f1Schristos /* $NetBSD: ssh-sk.c,v 1.9 2024/09/24 21:32:19 christos Exp $ */ 2*9469f4f1Schristos /* $OpenBSD: ssh-sk.c,v 1.41 2024/08/15 00:51:51 djm Exp $ */ 3e160b4e8Schristos 418504831Schristos /* 518504831Schristos * Copyright (c) 2019 Google LLC 618504831Schristos * 718504831Schristos * Permission to use, copy, modify, and distribute this software for any 818504831Schristos * purpose with or without fee is hereby granted, provided that the above 918504831Schristos * copyright notice and this permission notice appear in all copies. 1018504831Schristos * 1118504831Schristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1218504831Schristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1318504831Schristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1418504831Schristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1518504831Schristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1618504831Schristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1718504831Schristos * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1818504831Schristos */ 19ed75d7a8Schristos #include "includes.h" 20*9469f4f1Schristos __RCSID("$NetBSD: ssh-sk.c,v 1.9 2024/09/24 21:32:19 christos Exp $"); 2118504831Schristos 2218504831Schristos /* #define DEBUG_SK 1 */ 2318504831Schristos 2418504831Schristos #include <dlfcn.h> 2518504831Schristos #include <stddef.h> 2618504831Schristos #include <stdint.h> 2718504831Schristos #include <string.h> 2818504831Schristos #include <stdio.h> 2918504831Schristos 3018504831Schristos #ifdef WITH_OPENSSL 3118504831Schristos #include <openssl/objects.h> 3218504831Schristos #include <openssl/ec.h> 33*9469f4f1Schristos #include <openssl/evp.h> 3418504831Schristos #endif /* WITH_OPENSSL */ 3518504831Schristos 3618504831Schristos #include "log.h" 3718504831Schristos #include "misc.h" 3818504831Schristos #include "sshbuf.h" 3918504831Schristos #include "sshkey.h" 4018504831Schristos #include "ssherr.h" 4118504831Schristos #include "digest.h" 4218504831Schristos 4318504831Schristos #include "ssh-sk.h" 4418504831Schristos #include "sk-api.h" 4518504831Schristos #include "crypto_api.h" 4618504831Schristos 4718504831Schristos struct sshsk_provider { 4818504831Schristos char *path; 4918504831Schristos void *dlhandle; 5018504831Schristos 5118504831Schristos /* Return the version of the middleware API */ 5218504831Schristos uint32_t (*sk_api_version)(void); 5318504831Schristos 5418504831Schristos /* Enroll a U2F key (private key generation) */ 5518504831Schristos int (*sk_enroll)(int alg, const uint8_t *challenge, 5618504831Schristos size_t challenge_len, const char *application, uint8_t flags, 5718504831Schristos const char *pin, struct sk_option **opts, 5818504831Schristos struct sk_enroll_response **enroll_response); 5918504831Schristos 6018504831Schristos /* Sign a challenge */ 6118504831Schristos int (*sk_sign)(int alg, const uint8_t *message, size_t message_len, 6218504831Schristos const char *application, 6318504831Schristos const uint8_t *key_handle, size_t key_handle_len, 6418504831Schristos uint8_t flags, const char *pin, struct sk_option **opts, 6518504831Schristos struct sk_sign_response **sign_response); 6618504831Schristos 6718504831Schristos /* Enumerate resident keys */ 6818504831Schristos int (*sk_load_resident_keys)(const char *pin, struct sk_option **opts, 6918504831Schristos struct sk_resident_key ***rks, size_t *nrks); 7018504831Schristos }; 7118504831Schristos 7218504831Schristos /* Built-in version */ 7318504831Schristos int ssh_sk_enroll(int alg, const uint8_t *challenge, 7418504831Schristos size_t challenge_len, const char *application, uint8_t flags, 7518504831Schristos const char *pin, struct sk_option **opts, 7618504831Schristos struct sk_enroll_response **enroll_response); 7718504831Schristos int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len, 7818504831Schristos const char *application, 7918504831Schristos const uint8_t *key_handle, size_t key_handle_len, 8018504831Schristos uint8_t flags, const char *pin, struct sk_option **opts, 8118504831Schristos struct sk_sign_response **sign_response); 8218504831Schristos int ssh_sk_load_resident_keys(const char *pin, struct sk_option **opts, 8318504831Schristos struct sk_resident_key ***rks, size_t *nrks); 8418504831Schristos 8518504831Schristos static void 8618504831Schristos sshsk_free(struct sshsk_provider *p) 8718504831Schristos { 8818504831Schristos if (p == NULL) 8918504831Schristos return; 9018504831Schristos free(p->path); 9118504831Schristos if (p->dlhandle != NULL) 9218504831Schristos dlclose(p->dlhandle); 9318504831Schristos free(p); 9418504831Schristos } 9518504831Schristos 9618504831Schristos static struct sshsk_provider * 9718504831Schristos sshsk_open(const char *path) 9818504831Schristos { 9918504831Schristos struct sshsk_provider *ret = NULL; 10018504831Schristos uint32_t version; 10118504831Schristos 1028db691beSchristos if (path == NULL || *path == '\0') { 1038db691beSchristos error("No FIDO SecurityKeyProvider specified"); 1048db691beSchristos return NULL; 1058db691beSchristos } 10618504831Schristos if ((ret = calloc(1, sizeof(*ret))) == NULL) { 10717418e98Schristos error_f("calloc failed"); 10818504831Schristos return NULL; 10918504831Schristos } 11018504831Schristos if ((ret->path = strdup(path)) == NULL) { 11117418e98Schristos error_f("strdup failed"); 11218504831Schristos goto fail; 11318504831Schristos } 11418504831Schristos /* Skip the rest if we're using the linked in middleware */ 11518504831Schristos if (strcasecmp(ret->path, "internal") == 0) { 11618504831Schristos ret->sk_enroll = ssh_sk_enroll; 11718504831Schristos ret->sk_sign = ssh_sk_sign; 11818504831Schristos ret->sk_load_resident_keys = ssh_sk_load_resident_keys; 11918504831Schristos return ret; 12018504831Schristos } 121a629fefcSchristos if (lib_contains_symbol(path, "sk_api_version") != 0) { 122a629fefcSchristos error("provider %s is not an OpenSSH FIDO library", path); 123a629fefcSchristos goto fail; 124a629fefcSchristos } 12518504831Schristos if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) { 12618504831Schristos error("Provider \"%s\" dlopen failed: %s", path, dlerror()); 12718504831Schristos goto fail; 12818504831Schristos } 12918504831Schristos if ((ret->sk_api_version = dlsym(ret->dlhandle, 13018504831Schristos "sk_api_version")) == NULL) { 131a629fefcSchristos fatal("Provider \"%s\" dlsym(sk_api_version) failed: %s", 13218504831Schristos path, dlerror()); 13318504831Schristos } 13418504831Schristos version = ret->sk_api_version(); 13517418e98Schristos debug_f("provider %s implements version 0x%08lx", ret->path, 13617418e98Schristos (u_long)version); 13718504831Schristos if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) { 13818504831Schristos error("Provider \"%s\" implements unsupported " 13918504831Schristos "version 0x%08lx (supported: 0x%08lx)", 14018504831Schristos path, (u_long)version, (u_long)SSH_SK_VERSION_MAJOR); 14118504831Schristos goto fail; 14218504831Schristos } 14318504831Schristos if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) { 14418504831Schristos error("Provider %s dlsym(sk_enroll) failed: %s", 14518504831Schristos path, dlerror()); 14618504831Schristos goto fail; 14718504831Schristos } 14818504831Schristos if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) { 14918504831Schristos error("Provider \"%s\" dlsym(sk_sign) failed: %s", 15018504831Schristos path, dlerror()); 15118504831Schristos goto fail; 15218504831Schristos } 15318504831Schristos if ((ret->sk_load_resident_keys = dlsym(ret->dlhandle, 15418504831Schristos "sk_load_resident_keys")) == NULL) { 15518504831Schristos error("Provider \"%s\" dlsym(sk_load_resident_keys) " 15618504831Schristos "failed: %s", path, dlerror()); 15718504831Schristos goto fail; 15818504831Schristos } 15918504831Schristos /* success */ 16018504831Schristos return ret; 16118504831Schristos fail: 16218504831Schristos sshsk_free(ret); 16318504831Schristos return NULL; 16418504831Schristos } 16518504831Schristos 16618504831Schristos static void 16718504831Schristos sshsk_free_enroll_response(struct sk_enroll_response *r) 16818504831Schristos { 16918504831Schristos if (r == NULL) 17018504831Schristos return; 17118504831Schristos freezero(r->key_handle, r->key_handle_len); 17218504831Schristos freezero(r->public_key, r->public_key_len); 17318504831Schristos freezero(r->signature, r->signature_len); 17418504831Schristos freezero(r->attestation_cert, r->attestation_cert_len); 1752d3b0f52Schristos freezero(r->authdata, r->authdata_len); 17618504831Schristos freezero(r, sizeof(*r)); 17718504831Schristos } 17818504831Schristos 17918504831Schristos static void 18018504831Schristos sshsk_free_sign_response(struct sk_sign_response *r) 18118504831Schristos { 18218504831Schristos if (r == NULL) 18318504831Schristos return; 18418504831Schristos freezero(r->sig_r, r->sig_r_len); 18518504831Schristos freezero(r->sig_s, r->sig_s_len); 18618504831Schristos freezero(r, sizeof(*r)); 18718504831Schristos } 18818504831Schristos 18918504831Schristos #ifdef WITH_OPENSSL 19018504831Schristos /* Assemble key from response */ 19118504831Schristos static int 19218504831Schristos sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp) 19318504831Schristos { 19418504831Schristos struct sshkey *key = NULL; 19518504831Schristos struct sshbuf *b = NULL; 196*9469f4f1Schristos EC_KEY *ecdsa = NULL; 19718504831Schristos EC_POINT *q = NULL; 198*9469f4f1Schristos const EC_GROUP *g = NULL; 19918504831Schristos int r; 20018504831Schristos 20118504831Schristos *keyp = NULL; 20218504831Schristos if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) { 20317418e98Schristos error_f("sshkey_new failed"); 20418504831Schristos r = SSH_ERR_ALLOC_FAIL; 20518504831Schristos goto out; 20618504831Schristos } 20718504831Schristos key->ecdsa_nid = NID_X9_62_prime256v1; 208*9469f4f1Schristos if ((ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL || 209*9469f4f1Schristos (g = EC_KEY_get0_group(ecdsa)) == NULL || 210*9469f4f1Schristos (q = EC_POINT_new(g)) == NULL || 21118504831Schristos (b = sshbuf_new()) == NULL) { 21217418e98Schristos error_f("allocation failed"); 21318504831Schristos r = SSH_ERR_ALLOC_FAIL; 21418504831Schristos goto out; 21518504831Schristos } 21618504831Schristos if ((r = sshbuf_put_string(b, 21718504831Schristos resp->public_key, resp->public_key_len)) != 0) { 21817418e98Schristos error_fr(r, "sshbuf_put_string"); 21918504831Schristos goto out; 22018504831Schristos } 221*9469f4f1Schristos if ((r = sshbuf_get_ec(b, q, g)) != 0) { 22217418e98Schristos error_fr(r, "parse"); 22318504831Schristos r = SSH_ERR_INVALID_FORMAT; 22418504831Schristos goto out; 22518504831Schristos } 226*9469f4f1Schristos if (sshkey_ec_validate_public(g, q) != 0) { 22718504831Schristos error("Authenticator returned invalid ECDSA key"); 22818504831Schristos r = SSH_ERR_KEY_INVALID_EC_VALUE; 22918504831Schristos goto out; 23018504831Schristos } 231*9469f4f1Schristos if (EC_KEY_set_public_key(ecdsa, q) != 1) { 23218504831Schristos /* XXX assume it is a allocation error */ 23317418e98Schristos error_f("allocation failed"); 23418504831Schristos r = SSH_ERR_ALLOC_FAIL; 23518504831Schristos goto out; 23618504831Schristos } 237*9469f4f1Schristos if ((key->pkey = EVP_PKEY_new()) == NULL) { 238*9469f4f1Schristos error_f("allocation failed"); 239*9469f4f1Schristos r = SSH_ERR_ALLOC_FAIL; 240*9469f4f1Schristos goto out; 241*9469f4f1Schristos } 242*9469f4f1Schristos if (EVP_PKEY_set1_EC_KEY(key->pkey, ecdsa) != 1) { 243*9469f4f1Schristos error_f("Assigning EC_KEY failed"); 244*9469f4f1Schristos r = SSH_ERR_LIBCRYPTO_ERROR; 245*9469f4f1Schristos goto out; 246*9469f4f1Schristos } 24718504831Schristos /* success */ 24818504831Schristos *keyp = key; 24918504831Schristos key = NULL; /* transferred */ 25018504831Schristos r = 0; 25118504831Schristos out: 25218504831Schristos sshkey_free(key); 25318504831Schristos sshbuf_free(b); 254*9469f4f1Schristos EC_KEY_free(ecdsa); 255*9469f4f1Schristos EC_POINT_free(q); 25618504831Schristos return r; 25718504831Schristos } 25818504831Schristos #endif /* WITH_OPENSSL */ 25918504831Schristos 26018504831Schristos static int 26118504831Schristos sshsk_ed25519_assemble(struct sk_enroll_response *resp, struct sshkey **keyp) 26218504831Schristos { 26318504831Schristos struct sshkey *key = NULL; 26418504831Schristos int r; 26518504831Schristos 26618504831Schristos *keyp = NULL; 26718504831Schristos if (resp->public_key_len != ED25519_PK_SZ) { 26817418e98Schristos error_f("invalid size: %zu", resp->public_key_len); 26918504831Schristos r = SSH_ERR_INVALID_FORMAT; 27018504831Schristos goto out; 27118504831Schristos } 27218504831Schristos if ((key = sshkey_new(KEY_ED25519_SK)) == NULL) { 27317418e98Schristos error_f("sshkey_new failed"); 27418504831Schristos r = SSH_ERR_ALLOC_FAIL; 27518504831Schristos goto out; 27618504831Schristos } 27718504831Schristos if ((key->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL) { 27817418e98Schristos error_f("malloc failed"); 27918504831Schristos r = SSH_ERR_ALLOC_FAIL; 28018504831Schristos goto out; 28118504831Schristos } 28218504831Schristos memcpy(key->ed25519_pk, resp->public_key, ED25519_PK_SZ); 28318504831Schristos /* success */ 28418504831Schristos *keyp = key; 28518504831Schristos key = NULL; /* transferred */ 28618504831Schristos r = 0; 28718504831Schristos out: 28818504831Schristos sshkey_free(key); 28918504831Schristos return r; 29018504831Schristos } 29118504831Schristos 29218504831Schristos static int 29318504831Schristos sshsk_key_from_response(int alg, const char *application, uint8_t flags, 29418504831Schristos struct sk_enroll_response *resp, struct sshkey **keyp) 29518504831Schristos { 29618504831Schristos struct sshkey *key = NULL; 29718504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 29818504831Schristos 29918504831Schristos *keyp = NULL; 30018504831Schristos 30118504831Schristos /* Check response validity */ 30218504831Schristos if (resp->public_key == NULL || resp->key_handle == NULL) { 30317418e98Schristos error_f("sk_enroll response invalid"); 30418504831Schristos r = SSH_ERR_INVALID_FORMAT; 30518504831Schristos goto out; 30618504831Schristos } 30718504831Schristos switch (alg) { 30818504831Schristos #ifdef WITH_OPENSSL 30918504831Schristos case SSH_SK_ECDSA: 31018504831Schristos if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0) 31118504831Schristos goto out; 31218504831Schristos break; 31318504831Schristos #endif /* WITH_OPENSSL */ 31418504831Schristos case SSH_SK_ED25519: 31518504831Schristos if ((r = sshsk_ed25519_assemble(resp, &key)) != 0) 31618504831Schristos goto out; 31718504831Schristos break; 31818504831Schristos default: 31917418e98Schristos error_f("unsupported algorithm %d", alg); 32018504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 32118504831Schristos goto out; 32218504831Schristos } 32318504831Schristos key->sk_flags = flags; 32418504831Schristos if ((key->sk_key_handle = sshbuf_new()) == NULL || 32518504831Schristos (key->sk_reserved = sshbuf_new()) == NULL) { 32617418e98Schristos error_f("allocation failed"); 32718504831Schristos r = SSH_ERR_ALLOC_FAIL; 32818504831Schristos goto out; 32918504831Schristos } 33018504831Schristos if ((key->sk_application = strdup(application)) == NULL) { 33117418e98Schristos error_f("strdup application failed"); 33218504831Schristos r = SSH_ERR_ALLOC_FAIL; 33318504831Schristos goto out; 33418504831Schristos } 33518504831Schristos if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle, 33618504831Schristos resp->key_handle_len)) != 0) { 33717418e98Schristos error_fr(r, "put key handle"); 33818504831Schristos goto out; 33918504831Schristos } 34018504831Schristos /* success */ 34118504831Schristos r = 0; 34218504831Schristos *keyp = key; 34318504831Schristos key = NULL; 34418504831Schristos out: 34518504831Schristos sshkey_free(key); 34618504831Schristos return r; 34718504831Schristos } 34818504831Schristos 34918504831Schristos static int 35018504831Schristos skerr_to_ssherr(int skerr) 35118504831Schristos { 35218504831Schristos switch (skerr) { 35318504831Schristos case SSH_SK_ERR_UNSUPPORTED: 35418504831Schristos return SSH_ERR_FEATURE_UNSUPPORTED; 35518504831Schristos case SSH_SK_ERR_PIN_REQUIRED: 35618504831Schristos return SSH_ERR_KEY_WRONG_PASSPHRASE; 35718504831Schristos case SSH_SK_ERR_DEVICE_NOT_FOUND: 35818504831Schristos return SSH_ERR_DEVICE_NOT_FOUND; 359e160b4e8Schristos case SSH_SK_ERR_CREDENTIAL_EXISTS: 360e160b4e8Schristos return SSH_ERR_KEY_BAD_PERMISSIONS; 36118504831Schristos case SSH_SK_ERR_GENERAL: 36218504831Schristos default: 36318504831Schristos return SSH_ERR_INVALID_FORMAT; 36418504831Schristos } 36518504831Schristos } 36618504831Schristos 36718504831Schristos static void 36818504831Schristos sshsk_free_options(struct sk_option **opts) 36918504831Schristos { 37018504831Schristos size_t i; 37118504831Schristos 37218504831Schristos if (opts == NULL) 37318504831Schristos return; 37418504831Schristos for (i = 0; opts[i] != NULL; i++) { 37518504831Schristos free(opts[i]->name); 37618504831Schristos free(opts[i]->value); 37718504831Schristos free(opts[i]); 37818504831Schristos } 37918504831Schristos free(opts); 38018504831Schristos } 38118504831Schristos 38218504831Schristos static int 38318504831Schristos sshsk_add_option(struct sk_option ***optsp, size_t *noptsp, 38418504831Schristos const char *name, const char *value, uint8_t required) 38518504831Schristos { 38618504831Schristos struct sk_option **opts = *optsp; 38718504831Schristos size_t nopts = *noptsp; 38818504831Schristos 38918504831Schristos if ((opts = recallocarray(opts, nopts, nopts + 2, /* extra for NULL */ 39018504831Schristos sizeof(*opts))) == NULL) { 39117418e98Schristos error_f("array alloc failed"); 39218504831Schristos return SSH_ERR_ALLOC_FAIL; 39318504831Schristos } 39418504831Schristos *optsp = opts; 39518504831Schristos *noptsp = nopts + 1; 39618504831Schristos if ((opts[nopts] = calloc(1, sizeof(**opts))) == NULL) { 39717418e98Schristos error_f("alloc failed"); 39818504831Schristos return SSH_ERR_ALLOC_FAIL; 39918504831Schristos } 40018504831Schristos if ((opts[nopts]->name = strdup(name)) == NULL || 40118504831Schristos (opts[nopts]->value = strdup(value)) == NULL) { 40217418e98Schristos error_f("alloc failed"); 40318504831Schristos return SSH_ERR_ALLOC_FAIL; 40418504831Schristos } 40518504831Schristos opts[nopts]->required = required; 40618504831Schristos return 0; 40718504831Schristos } 40818504831Schristos 40918504831Schristos static int 41018504831Schristos make_options(const char *device, const char *user_id, 41118504831Schristos struct sk_option ***optsp) 41218504831Schristos { 41318504831Schristos struct sk_option **opts = NULL; 41418504831Schristos size_t nopts = 0; 41518504831Schristos int r, ret = SSH_ERR_INTERNAL_ERROR; 41618504831Schristos 41718504831Schristos if (device != NULL && 41818504831Schristos (r = sshsk_add_option(&opts, &nopts, "device", device, 0)) != 0) { 41918504831Schristos ret = r; 42018504831Schristos goto out; 42118504831Schristos } 42218504831Schristos if (user_id != NULL && 42318504831Schristos (r = sshsk_add_option(&opts, &nopts, "user", user_id, 0)) != 0) { 42418504831Schristos ret = r; 42518504831Schristos goto out; 42618504831Schristos } 42718504831Schristos /* success */ 42818504831Schristos *optsp = opts; 42918504831Schristos opts = NULL; 43018504831Schristos nopts = 0; 43118504831Schristos ret = 0; 43218504831Schristos out: 43318504831Schristos sshsk_free_options(opts); 43418504831Schristos return ret; 43518504831Schristos } 43618504831Schristos 4372d3b0f52Schristos 4382d3b0f52Schristos static int 4392d3b0f52Schristos fill_attestation_blob(const struct sk_enroll_response *resp, 4402d3b0f52Schristos struct sshbuf *attest) 4412d3b0f52Schristos { 4422d3b0f52Schristos int r; 4432d3b0f52Schristos 4442d3b0f52Schristos if (attest == NULL) 4452d3b0f52Schristos return 0; /* nothing to do */ 4462d3b0f52Schristos if ((r = sshbuf_put_cstring(attest, "ssh-sk-attest-v01")) != 0 || 4472d3b0f52Schristos (r = sshbuf_put_string(attest, 4482d3b0f52Schristos resp->attestation_cert, resp->attestation_cert_len)) != 0 || 4492d3b0f52Schristos (r = sshbuf_put_string(attest, 4502d3b0f52Schristos resp->signature, resp->signature_len)) != 0 || 4512d3b0f52Schristos (r = sshbuf_put_string(attest, 4522d3b0f52Schristos resp->authdata, resp->authdata_len)) != 0 || 4532d3b0f52Schristos (r = sshbuf_put_u32(attest, 0)) != 0 || /* resvd flags */ 4542d3b0f52Schristos (r = sshbuf_put_string(attest, NULL, 0)) != 0 /* resvd */) { 45517418e98Schristos error_fr(r, "compose"); 4562d3b0f52Schristos return r; 4572d3b0f52Schristos } 4582d3b0f52Schristos /* success */ 4592d3b0f52Schristos return 0; 4602d3b0f52Schristos } 4612d3b0f52Schristos 46218504831Schristos int 46318504831Schristos sshsk_enroll(int type, const char *provider_path, const char *device, 46418504831Schristos const char *application, const char *userid, uint8_t flags, 46518504831Schristos const char *pin, struct sshbuf *challenge_buf, 46618504831Schristos struct sshkey **keyp, struct sshbuf *attest) 46718504831Schristos { 46818504831Schristos struct sshsk_provider *skp = NULL; 46918504831Schristos struct sshkey *key = NULL; 47018504831Schristos u_char randchall[32]; 47118504831Schristos const u_char *challenge; 47218504831Schristos size_t challenge_len; 47318504831Schristos struct sk_enroll_response *resp = NULL; 47418504831Schristos struct sk_option **opts = NULL; 47518504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 47618504831Schristos int alg; 47718504831Schristos 47817418e98Schristos debug_f("provider \"%s\", device \"%s\", application \"%s\", " 47917418e98Schristos "userid \"%s\", flags 0x%02x, challenge len %zu%s", 48018504831Schristos provider_path, device, application, userid, flags, 48118504831Schristos challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf), 48218504831Schristos (pin != NULL && *pin != '\0') ? " with-pin" : ""); 48318504831Schristos 48418504831Schristos *keyp = NULL; 48518504831Schristos if (attest) 48618504831Schristos sshbuf_reset(attest); 48718504831Schristos 48818504831Schristos if ((r = make_options(device, userid, &opts)) != 0) 48918504831Schristos goto out; 49018504831Schristos 49118504831Schristos switch (type) { 49218504831Schristos #ifdef WITH_OPENSSL 49318504831Schristos case KEY_ECDSA_SK: 49418504831Schristos alg = SSH_SK_ECDSA; 49518504831Schristos break; 49618504831Schristos #endif /* WITH_OPENSSL */ 49718504831Schristos case KEY_ED25519_SK: 49818504831Schristos alg = SSH_SK_ED25519; 49918504831Schristos break; 50018504831Schristos default: 50117418e98Schristos error_f("unsupported key type"); 50218504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 50318504831Schristos goto out; 50418504831Schristos } 50518504831Schristos if (provider_path == NULL) { 50617418e98Schristos error_f("missing provider"); 50718504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 50818504831Schristos goto out; 50918504831Schristos } 51018504831Schristos if (application == NULL || *application == '\0') { 51117418e98Schristos error_f("missing application"); 51218504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 51318504831Schristos goto out; 51418504831Schristos } 51518504831Schristos if (challenge_buf == NULL) { 51617418e98Schristos debug_f("using random challenge"); 51718504831Schristos arc4random_buf(randchall, sizeof(randchall)); 51818504831Schristos challenge = randchall; 51918504831Schristos challenge_len = sizeof(randchall); 52018504831Schristos } else if (sshbuf_len(challenge_buf) == 0) { 52118504831Schristos error("Missing enrollment challenge"); 52218504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 52318504831Schristos goto out; 52418504831Schristos } else { 52518504831Schristos challenge = sshbuf_ptr(challenge_buf); 52618504831Schristos challenge_len = sshbuf_len(challenge_buf); 52717418e98Schristos debug3_f("using explicit challenge len=%zd", challenge_len); 52818504831Schristos } 52918504831Schristos if ((skp = sshsk_open(provider_path)) == NULL) { 53018504831Schristos r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */ 53118504831Schristos goto out; 53218504831Schristos } 53318504831Schristos /* XXX validate flags? */ 53418504831Schristos /* enroll key */ 53518504831Schristos if ((r = skp->sk_enroll(alg, challenge, challenge_len, application, 53618504831Schristos flags, pin, opts, &resp)) != 0) { 53717418e98Schristos debug_f("provider \"%s\" failure %d", provider_path, r); 53818504831Schristos r = skerr_to_ssherr(r); 53918504831Schristos goto out; 54018504831Schristos } 54118504831Schristos 542a03ec00cSchristos if ((r = sshsk_key_from_response(alg, application, resp->flags, 54318504831Schristos resp, &key)) != 0) 54418504831Schristos goto out; 54518504831Schristos 54618504831Schristos /* Optionally fill in the attestation information */ 5472d3b0f52Schristos if ((r = fill_attestation_blob(resp, attest)) != 0) 54818504831Schristos goto out; 5492d3b0f52Schristos 55018504831Schristos /* success */ 55118504831Schristos *keyp = key; 55218504831Schristos key = NULL; /* transferred */ 55318504831Schristos r = 0; 55418504831Schristos out: 55518504831Schristos sshsk_free_options(opts); 55618504831Schristos sshsk_free(skp); 55718504831Schristos sshkey_free(key); 55818504831Schristos sshsk_free_enroll_response(resp); 55918504831Schristos explicit_bzero(randchall, sizeof(randchall)); 56018504831Schristos return r; 56118504831Schristos } 56218504831Schristos 56318504831Schristos #ifdef WITH_OPENSSL 56418504831Schristos static int 56518504831Schristos sshsk_ecdsa_sig(struct sk_sign_response *resp, struct sshbuf *sig) 56618504831Schristos { 56718504831Schristos struct sshbuf *inner_sig = NULL; 56818504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 56918504831Schristos 57018504831Schristos /* Check response validity */ 57118504831Schristos if (resp->sig_r == NULL || resp->sig_s == NULL) { 57217418e98Schristos error_f("sk_sign response invalid"); 57318504831Schristos r = SSH_ERR_INVALID_FORMAT; 57418504831Schristos goto out; 57518504831Schristos } 57618504831Schristos if ((inner_sig = sshbuf_new()) == NULL) { 57718504831Schristos r = SSH_ERR_ALLOC_FAIL; 57818504831Schristos goto out; 57918504831Schristos } 58018504831Schristos /* Prepare and append inner signature object */ 58118504831Schristos if ((r = sshbuf_put_bignum2_bytes(inner_sig, 58218504831Schristos resp->sig_r, resp->sig_r_len)) != 0 || 58318504831Schristos (r = sshbuf_put_bignum2_bytes(inner_sig, 58418504831Schristos resp->sig_s, resp->sig_s_len)) != 0) { 58517418e98Schristos error_fr(r, "compose inner"); 58618504831Schristos goto out; 58718504831Schristos } 58818504831Schristos if ((r = sshbuf_put_stringb(sig, inner_sig)) != 0 || 58918504831Schristos (r = sshbuf_put_u8(sig, resp->flags)) != 0 || 59018504831Schristos (r = sshbuf_put_u32(sig, resp->counter)) != 0) { 59117418e98Schristos error_fr(r, "compose"); 59218504831Schristos goto out; 59318504831Schristos } 59418504831Schristos #ifdef DEBUG_SK 59518504831Schristos fprintf(stderr, "%s: sig_r:\n", __func__); 59618504831Schristos sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr); 59718504831Schristos fprintf(stderr, "%s: sig_s:\n", __func__); 59818504831Schristos sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr); 59918504831Schristos fprintf(stderr, "%s: inner:\n", __func__); 60018504831Schristos sshbuf_dump(inner_sig, stderr); 60118504831Schristos #endif 60218504831Schristos r = 0; 60318504831Schristos out: 60418504831Schristos sshbuf_free(inner_sig); 60518504831Schristos return r; 60618504831Schristos } 60718504831Schristos #endif /* WITH_OPENSSL */ 60818504831Schristos 60918504831Schristos static int 61018504831Schristos sshsk_ed25519_sig(struct sk_sign_response *resp, struct sshbuf *sig) 61118504831Schristos { 61218504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 61318504831Schristos 61418504831Schristos /* Check response validity */ 61518504831Schristos if (resp->sig_r == NULL) { 61617418e98Schristos error_f("sk_sign response invalid"); 61718504831Schristos r = SSH_ERR_INVALID_FORMAT; 61818504831Schristos goto out; 61918504831Schristos } 62018504831Schristos if ((r = sshbuf_put_string(sig, 62118504831Schristos resp->sig_r, resp->sig_r_len)) != 0 || 62218504831Schristos (r = sshbuf_put_u8(sig, resp->flags)) != 0 || 62318504831Schristos (r = sshbuf_put_u32(sig, resp->counter)) != 0) { 62417418e98Schristos error_fr(r, "compose"); 62518504831Schristos goto out; 62618504831Schristos } 62718504831Schristos #ifdef DEBUG_SK 62818504831Schristos fprintf(stderr, "%s: sig_r:\n", __func__); 62918504831Schristos sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr); 63018504831Schristos #endif 63118504831Schristos r = 0; 63218504831Schristos out: 6338db691beSchristos return r; 63418504831Schristos } 63518504831Schristos 63618504831Schristos int 63718504831Schristos sshsk_sign(const char *provider_path, struct sshkey *key, 63818504831Schristos u_char **sigp, size_t *lenp, const u_char *data, size_t datalen, 63918504831Schristos u_int compat, const char *pin) 64018504831Schristos { 64118504831Schristos struct sshsk_provider *skp = NULL; 64218504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 64318504831Schristos int type, alg; 64418504831Schristos struct sk_sign_response *resp = NULL; 64518504831Schristos struct sshbuf *inner_sig = NULL, *sig = NULL; 64618504831Schristos struct sk_option **opts = NULL; 64718504831Schristos 64817418e98Schristos debug_f("provider \"%s\", key %s, flags 0x%02x%s", 64918504831Schristos provider_path, sshkey_type(key), key->sk_flags, 65018504831Schristos (pin != NULL && *pin != '\0') ? " with-pin" : ""); 65118504831Schristos 65218504831Schristos if (sigp != NULL) 65318504831Schristos *sigp = NULL; 65418504831Schristos if (lenp != NULL) 65518504831Schristos *lenp = 0; 65618504831Schristos type = sshkey_type_plain(key->type); 65718504831Schristos switch (type) { 65818504831Schristos #ifdef WITH_OPENSSL 65918504831Schristos case KEY_ECDSA_SK: 66018504831Schristos alg = SSH_SK_ECDSA; 66118504831Schristos break; 66218504831Schristos #endif /* WITH_OPENSSL */ 66318504831Schristos case KEY_ED25519_SK: 66418504831Schristos alg = SSH_SK_ED25519; 66518504831Schristos break; 66618504831Schristos default: 66718504831Schristos return SSH_ERR_INVALID_ARGUMENT; 66818504831Schristos } 66918504831Schristos if (provider_path == NULL || 67018504831Schristos key->sk_key_handle == NULL || 67118504831Schristos key->sk_application == NULL || *key->sk_application == '\0') { 67218504831Schristos r = SSH_ERR_INVALID_ARGUMENT; 67318504831Schristos goto out; 67418504831Schristos } 67518504831Schristos if ((skp = sshsk_open(provider_path)) == NULL) { 67618504831Schristos r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */ 67718504831Schristos goto out; 67818504831Schristos } 67917418e98Schristos #ifdef DEBUG_SK 68017418e98Schristos fprintf(stderr, "%s: sk_flags = 0x%02x, sk_application = \"%s\"\n", 68117418e98Schristos __func__, key->sk_flags, key->sk_application); 68217418e98Schristos fprintf(stderr, "%s: sk_key_handle:\n", __func__); 68317418e98Schristos sshbuf_dump(key->sk_key_handle, stderr); 68417418e98Schristos #endif 6858db691beSchristos if ((r = skp->sk_sign(alg, data, datalen, key->sk_application, 68618504831Schristos sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle), 68718504831Schristos key->sk_flags, pin, opts, &resp)) != 0) { 68817418e98Schristos debug_f("sk_sign failed with code %d", r); 68918504831Schristos r = skerr_to_ssherr(r); 69018504831Schristos goto out; 69118504831Schristos } 69218504831Schristos /* Assemble signature */ 69318504831Schristos if ((sig = sshbuf_new()) == NULL) { 69418504831Schristos r = SSH_ERR_ALLOC_FAIL; 69518504831Schristos goto out; 69618504831Schristos } 69718504831Schristos if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0) { 69817418e98Schristos error_fr(r, "compose outer"); 69918504831Schristos goto out; 70018504831Schristos } 70118504831Schristos switch (type) { 70218504831Schristos #ifdef WITH_OPENSSL 70318504831Schristos case KEY_ECDSA_SK: 70418504831Schristos if ((r = sshsk_ecdsa_sig(resp, sig)) != 0) 70518504831Schristos goto out; 70618504831Schristos break; 70718504831Schristos #endif /* WITH_OPENSSL */ 70818504831Schristos case KEY_ED25519_SK: 70918504831Schristos if ((r = sshsk_ed25519_sig(resp, sig)) != 0) 71018504831Schristos goto out; 71118504831Schristos break; 71218504831Schristos } 71318504831Schristos #ifdef DEBUG_SK 71418504831Schristos fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n", 71518504831Schristos __func__, resp->flags, resp->counter); 71617418e98Schristos fprintf(stderr, "%s: data to sign:\n", __func__); 71717418e98Schristos sshbuf_dump_data(data, datalen, stderr); 71818504831Schristos fprintf(stderr, "%s: sigbuf:\n", __func__); 71918504831Schristos sshbuf_dump(sig, stderr); 72018504831Schristos #endif 72118504831Schristos if (sigp != NULL) { 72218504831Schristos if ((*sigp = malloc(sshbuf_len(sig))) == NULL) { 72318504831Schristos r = SSH_ERR_ALLOC_FAIL; 72418504831Schristos goto out; 72518504831Schristos } 72618504831Schristos memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig)); 72718504831Schristos } 72818504831Schristos if (lenp != NULL) 72918504831Schristos *lenp = sshbuf_len(sig); 73018504831Schristos /* success */ 73118504831Schristos r = 0; 73218504831Schristos out: 73318504831Schristos sshsk_free_options(opts); 73418504831Schristos sshsk_free(skp); 73518504831Schristos sshsk_free_sign_response(resp); 73618504831Schristos sshbuf_free(sig); 73718504831Schristos sshbuf_free(inner_sig); 73818504831Schristos return r; 73918504831Schristos } 74018504831Schristos 74118504831Schristos static void 74218504831Schristos sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks) 74318504831Schristos { 74418504831Schristos size_t i; 74518504831Schristos 74618504831Schristos if (nrks == 0 || rks == NULL) 74718504831Schristos return; 74818504831Schristos for (i = 0; i < nrks; i++) { 74918504831Schristos free(rks[i]->application); 750a03ec00cSchristos freezero(rks[i]->user_id, rks[i]->user_id_len); 75118504831Schristos freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len); 75218504831Schristos freezero(rks[i]->key.public_key, rks[i]->key.public_key_len); 75318504831Schristos freezero(rks[i]->key.signature, rks[i]->key.signature_len); 75418504831Schristos freezero(rks[i]->key.attestation_cert, 75518504831Schristos rks[i]->key.attestation_cert_len); 75618504831Schristos freezero(rks[i], sizeof(**rks)); 75718504831Schristos } 75818504831Schristos free(rks); 75918504831Schristos } 76018504831Schristos 761a03ec00cSchristos static void 762a03ec00cSchristos sshsk_free_resident_key(struct sshsk_resident_key *srk) 763a03ec00cSchristos { 764a03ec00cSchristos if (srk == NULL) 765a03ec00cSchristos return; 766a03ec00cSchristos sshkey_free(srk->key); 767a03ec00cSchristos freezero(srk->user_id, srk->user_id_len); 768a03ec00cSchristos free(srk); 769a03ec00cSchristos } 770a03ec00cSchristos 771a03ec00cSchristos 772a03ec00cSchristos void 773a03ec00cSchristos sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks) 774a03ec00cSchristos { 775a03ec00cSchristos size_t i; 776a03ec00cSchristos 777a03ec00cSchristos if (srks == NULL || nsrks == 0) 778a03ec00cSchristos return; 779a03ec00cSchristos 780a03ec00cSchristos for (i = 0; i < nsrks; i++) 781a03ec00cSchristos sshsk_free_resident_key(srks[i]); 782a03ec00cSchristos free(srks); 783a03ec00cSchristos } 784a03ec00cSchristos 78518504831Schristos int 78618504831Schristos sshsk_load_resident(const char *provider_path, const char *device, 787a03ec00cSchristos const char *pin, u_int flags, struct sshsk_resident_key ***srksp, 788a03ec00cSchristos size_t *nsrksp) 78918504831Schristos { 79018504831Schristos struct sshsk_provider *skp = NULL; 79118504831Schristos int r = SSH_ERR_INTERNAL_ERROR; 79218504831Schristos struct sk_resident_key **rks = NULL; 793a03ec00cSchristos size_t i, nrks = 0, nsrks = 0; 794a03ec00cSchristos struct sshkey *key = NULL; 795a03ec00cSchristos struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp; 796a03ec00cSchristos uint8_t sk_flags; 79718504831Schristos struct sk_option **opts = NULL; 79818504831Schristos 79917418e98Schristos debug_f("provider \"%s\"%s", provider_path, 80018504831Schristos (pin != NULL && *pin != '\0') ? ", have-pin": ""); 80118504831Schristos 802a03ec00cSchristos if (srksp == NULL || nsrksp == NULL) 80318504831Schristos return SSH_ERR_INVALID_ARGUMENT; 804a03ec00cSchristos *srksp = NULL; 805a03ec00cSchristos *nsrksp = 0; 80618504831Schristos 80718504831Schristos if ((r = make_options(device, NULL, &opts)) != 0) 80818504831Schristos goto out; 80918504831Schristos if ((skp = sshsk_open(provider_path)) == NULL) { 81018504831Schristos r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */ 81118504831Schristos goto out; 81218504831Schristos } 81318504831Schristos if ((r = skp->sk_load_resident_keys(pin, opts, &rks, &nrks)) != 0) { 81418504831Schristos error("Provider \"%s\" returned failure %d", provider_path, r); 81518504831Schristos r = skerr_to_ssherr(r); 81618504831Schristos goto out; 81718504831Schristos } 81818504831Schristos for (i = 0; i < nrks; i++) { 819a03ec00cSchristos debug3_f("rk %zu: slot %zu, alg %d, app \"%s\", uidlen %zu", 820a03ec00cSchristos i, rks[i]->slot, rks[i]->alg, rks[i]->application, 821a03ec00cSchristos rks[i]->user_id_len); 82218504831Schristos /* XXX need better filter here */ 82318504831Schristos if (strncmp(rks[i]->application, "ssh:", 4) != 0) 82418504831Schristos continue; 82518504831Schristos switch (rks[i]->alg) { 82618504831Schristos case SSH_SK_ECDSA: 82718504831Schristos case SSH_SK_ED25519: 82818504831Schristos break; 82918504831Schristos default: 83018504831Schristos continue; 83118504831Schristos } 832a03ec00cSchristos sk_flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY; 8332d3b0f52Schristos if ((rks[i]->flags & SSH_SK_USER_VERIFICATION_REQD)) 834a03ec00cSchristos sk_flags |= SSH_SK_USER_VERIFICATION_REQD; 83518504831Schristos if ((r = sshsk_key_from_response(rks[i]->alg, 836a03ec00cSchristos rks[i]->application, sk_flags, &rks[i]->key, &key)) != 0) 83718504831Schristos goto out; 838a03ec00cSchristos if ((srk = calloc(1, sizeof(*srk))) == NULL) { 839a03ec00cSchristos error_f("calloc failed"); 840a03ec00cSchristos r = SSH_ERR_ALLOC_FAIL; 841a03ec00cSchristos goto out; 842a03ec00cSchristos } 843a03ec00cSchristos srk->key = key; 844a03ec00cSchristos key = NULL; /* transferred */ 845a03ec00cSchristos if ((srk->user_id = calloc(1, rks[i]->user_id_len)) == NULL) { 846a03ec00cSchristos error_f("calloc failed"); 847a03ec00cSchristos r = SSH_ERR_ALLOC_FAIL; 848a03ec00cSchristos goto out; 849a03ec00cSchristos } 850a03ec00cSchristos memcpy(srk->user_id, rks[i]->user_id, rks[i]->user_id_len); 851a03ec00cSchristos srk->user_id_len = rks[i]->user_id_len; 852a03ec00cSchristos if ((tmp = recallocarray(srks, nsrks, nsrks + 1, 85318504831Schristos sizeof(*tmp))) == NULL) { 85417418e98Schristos error_f("recallocarray failed"); 85518504831Schristos r = SSH_ERR_ALLOC_FAIL; 85618504831Schristos goto out; 85718504831Schristos } 858a03ec00cSchristos srks = tmp; 859a03ec00cSchristos srks[nsrks++] = srk; 860a03ec00cSchristos srk = NULL; 86118504831Schristos /* XXX synthesise comment */ 86218504831Schristos } 86318504831Schristos /* success */ 864a03ec00cSchristos *srksp = srks; 865a03ec00cSchristos *nsrksp = nsrks; 866a03ec00cSchristos srks = NULL; 867a03ec00cSchristos nsrks = 0; 86818504831Schristos r = 0; 86918504831Schristos out: 87018504831Schristos sshsk_free_options(opts); 87118504831Schristos sshsk_free(skp); 87218504831Schristos sshsk_free_sk_resident_keys(rks, nrks); 87318504831Schristos sshkey_free(key); 874a03ec00cSchristos sshsk_free_resident_key(srk); 875a03ec00cSchristos sshsk_free_resident_keys(srks, nsrks); 87618504831Schristos return r; 87718504831Schristos } 87818504831Schristos 879