xref: /openbsd/usr.bin/ssh/sk-usbhid.c (revision b8914b02)
1*b8914b02Sdjm /* $OpenBSD: sk-usbhid.c,v 1.36 2021/12/02 23:23:13 djm Exp $ */
2094c80e0Sdjm /*
3094c80e0Sdjm  * Copyright (c) 2019 Markus Friedl
401bb7af0Sdjm  * Copyright (c) 2020 Pedro Martelletto
5094c80e0Sdjm  *
6094c80e0Sdjm  * Permission to use, copy, modify, and distribute this software for any
7094c80e0Sdjm  * purpose with or without fee is hereby granted, provided that the above
8094c80e0Sdjm  * copyright notice and this permission notice appear in all copies.
9094c80e0Sdjm  *
10094c80e0Sdjm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11094c80e0Sdjm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12094c80e0Sdjm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13094c80e0Sdjm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14094c80e0Sdjm  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15094c80e0Sdjm  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16094c80e0Sdjm  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17094c80e0Sdjm  */
18094c80e0Sdjm 
19094c80e0Sdjm #include <stdint.h>
20094c80e0Sdjm #include <stdlib.h>
21094c80e0Sdjm #include <string.h>
22094c80e0Sdjm #include <stdio.h>
23094c80e0Sdjm #include <stddef.h>
24094c80e0Sdjm #include <stdarg.h>
253ce2af41Sdjm #include <sha2.h>
26719d3f82Sdjm #include <time.h>
27094c80e0Sdjm 
28f8cd6cb1Snaddy #ifdef WITH_OPENSSL
29094c80e0Sdjm #include <openssl/opensslv.h>
30094c80e0Sdjm #include <openssl/crypto.h>
31094c80e0Sdjm #include <openssl/bn.h>
32094c80e0Sdjm #include <openssl/ec.h>
33094c80e0Sdjm #include <openssl/ecdsa.h>
343ce2af41Sdjm #include <openssl/evp.h>
35f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
36094c80e0Sdjm 
37094c80e0Sdjm #include <fido.h>
381ac4a90aSdjm #include <fido/credman.h>
39094c80e0Sdjm 
40094c80e0Sdjm #ifndef SK_STANDALONE
41094c80e0Sdjm # include "log.h"
42094c80e0Sdjm # include "xmalloc.h"
4301bb7af0Sdjm # include "misc.h"
44b0297854Sdjm /*
45b0297854Sdjm  * If building as part of OpenSSH, then rename exported functions.
46b0297854Sdjm  * This must be done before including sk-api.h.
47b0297854Sdjm  */
48b0297854Sdjm # define sk_api_version		ssh_sk_api_version
49b0297854Sdjm # define sk_enroll		ssh_sk_enroll
50b0297854Sdjm # define sk_sign		ssh_sk_sign
51b0297854Sdjm # define sk_load_resident_keys	ssh_sk_load_resident_keys
52b0297854Sdjm #endif /* !SK_STANDALONE */
53b0297854Sdjm 
54b0297854Sdjm #include "sk-api.h"
55094c80e0Sdjm 
56094c80e0Sdjm /* #define SK_DEBUG 1 */
57094c80e0Sdjm 
58fb0a6bcaSdjm #ifdef SK_DEBUG
59fb0a6bcaSdjm #define SSH_FIDO_INIT_ARG	FIDO_DEBUG
60fb0a6bcaSdjm #else
61fb0a6bcaSdjm #define SSH_FIDO_INIT_ARG	0
62fb0a6bcaSdjm #endif
63fb0a6bcaSdjm 
6401bb7af0Sdjm #define MAX_FIDO_DEVICES	8
6501bb7af0Sdjm #define FIDO_POLL_MS		50
6601bb7af0Sdjm #define SELECT_MS		15000
6701bb7af0Sdjm #define POLL_SLEEP_NS		200000000
68094c80e0Sdjm 
69094c80e0Sdjm /* Compatibility with OpenSSH 1.0.x */
70094c80e0Sdjm #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
71094c80e0Sdjm #define ECDSA_SIG_get0(sig, pr, ps) \
72094c80e0Sdjm 	do { \
73094c80e0Sdjm 		(*pr) = sig->r; \
74094c80e0Sdjm 		(*ps) = sig->s; \
75094c80e0Sdjm 	} while (0)
76094c80e0Sdjm #endif
7718b3d906Sdjm #ifndef FIDO_ERR_OPERATION_DENIED
7818b3d906Sdjm #define FIDO_ERR_OPERATION_DENIED 0x27
7918b3d906Sdjm #endif
80094c80e0Sdjm 
8101bb7af0Sdjm struct sk_usbhid {
8201bb7af0Sdjm 	fido_dev_t *dev;
8301bb7af0Sdjm 	char *path;
8401bb7af0Sdjm };
8501bb7af0Sdjm 
86094c80e0Sdjm /* Return the version of the middleware API */
87094c80e0Sdjm uint32_t sk_api_version(void);
88094c80e0Sdjm 
89094c80e0Sdjm /* Enroll a U2F key (private key generation) */
90a0caf565Sdjm int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
912db06755Sdjm     const char *application, uint8_t flags, const char *pin,
92a0caf565Sdjm     struct sk_option **options, struct sk_enroll_response **enroll_response);
93094c80e0Sdjm 
94094c80e0Sdjm /* Sign a challenge */
9574d7b7bdSdjm int sk_sign(uint32_t alg, const uint8_t *data, size_t data_len,
96094c80e0Sdjm     const char *application, const uint8_t *key_handle, size_t key_handle_len,
97a0caf565Sdjm     uint8_t flags, const char *pin, struct sk_option **options,
98a0caf565Sdjm     struct sk_sign_response **sign_response);
99094c80e0Sdjm 
1001ac4a90aSdjm /* Load resident keys */
101a0caf565Sdjm int sk_load_resident_keys(const char *pin, struct sk_option **options,
1021ac4a90aSdjm     struct sk_resident_key ***rks, size_t *nrks);
1031ac4a90aSdjm 
104094c80e0Sdjm static void skdebug(const char *func, const char *fmt, ...)
105094c80e0Sdjm     __attribute__((__format__ (printf, 2, 3)));
106094c80e0Sdjm 
107094c80e0Sdjm static void
108094c80e0Sdjm skdebug(const char *func, const char *fmt, ...)
109094c80e0Sdjm {
110094c80e0Sdjm #if !defined(SK_STANDALONE)
111094c80e0Sdjm 	char *msg;
112094c80e0Sdjm 	va_list ap;
113094c80e0Sdjm 
114094c80e0Sdjm 	va_start(ap, fmt);
115094c80e0Sdjm 	xvasprintf(&msg, fmt, ap);
116094c80e0Sdjm 	va_end(ap);
11759959935Sdjm 	debug("%s: %s", func, msg);
118094c80e0Sdjm 	free(msg);
119094c80e0Sdjm #elif defined(SK_DEBUG)
120094c80e0Sdjm 	va_list ap;
121094c80e0Sdjm 
122094c80e0Sdjm 	va_start(ap, fmt);
123094c80e0Sdjm 	fprintf(stderr, "%s: ", func);
124094c80e0Sdjm 	vfprintf(stderr, fmt, ap);
125094c80e0Sdjm 	fputc('\n', stderr);
126094c80e0Sdjm 	va_end(ap);
127094c80e0Sdjm #else
128094c80e0Sdjm 	(void)func; /* XXX */
129094c80e0Sdjm 	(void)fmt; /* XXX */
130094c80e0Sdjm #endif
131094c80e0Sdjm }
132094c80e0Sdjm 
133094c80e0Sdjm uint32_t
134094c80e0Sdjm sk_api_version(void)
135094c80e0Sdjm {
136b0297854Sdjm 	return SSH_SK_VERSION_MAJOR;
137094c80e0Sdjm }
138094c80e0Sdjm 
13901bb7af0Sdjm static struct sk_usbhid *
14001bb7af0Sdjm sk_open(const char *path)
141094c80e0Sdjm {
14201bb7af0Sdjm 	struct sk_usbhid *sk;
143094c80e0Sdjm 	int r;
144094c80e0Sdjm 
14501bb7af0Sdjm 	if (path == NULL) {
14601bb7af0Sdjm 		skdebug(__func__, "path == NULL");
14701bb7af0Sdjm 		return NULL;
148094c80e0Sdjm 	}
14901bb7af0Sdjm 	if ((sk = calloc(1, sizeof(*sk))) == NULL) {
15001bb7af0Sdjm 		skdebug(__func__, "calloc sk failed");
15101bb7af0Sdjm 		return NULL;
15201bb7af0Sdjm 	}
15301bb7af0Sdjm 	if ((sk->path = strdup(path)) == NULL) {
15401bb7af0Sdjm 		skdebug(__func__, "strdup path failed");
15501bb7af0Sdjm 		free(sk);
15601bb7af0Sdjm 		return NULL;
15701bb7af0Sdjm 	}
15801bb7af0Sdjm 	if ((sk->dev = fido_dev_new()) == NULL) {
15901bb7af0Sdjm 		skdebug(__func__, "fido_dev_new failed");
16001bb7af0Sdjm 		free(sk->path);
16101bb7af0Sdjm 		free(sk);
16201bb7af0Sdjm 		return NULL;
16301bb7af0Sdjm 	}
16401bb7af0Sdjm 	if ((r = fido_dev_open(sk->dev, sk->path)) != FIDO_OK) {
16501bb7af0Sdjm 		skdebug(__func__, "fido_dev_open %s failed: %s", sk->path,
166094c80e0Sdjm 		    fido_strerr(r));
16701bb7af0Sdjm 		fido_dev_free(&sk->dev);
16801bb7af0Sdjm 		free(sk->path);
16901bb7af0Sdjm 		free(sk);
17001bb7af0Sdjm 		return NULL;
171094c80e0Sdjm 	}
17201bb7af0Sdjm 	return sk;
173094c80e0Sdjm }
174094c80e0Sdjm 
17501bb7af0Sdjm static void
17601bb7af0Sdjm sk_close(struct sk_usbhid *sk)
17701bb7af0Sdjm {
17801bb7af0Sdjm 	if (sk == NULL)
17901bb7af0Sdjm 		return;
18001bb7af0Sdjm 	fido_dev_cancel(sk->dev); /* cancel any pending operation */
18101bb7af0Sdjm 	fido_dev_close(sk->dev);
18201bb7af0Sdjm 	fido_dev_free(&sk->dev);
18301bb7af0Sdjm 	free(sk->path);
18401bb7af0Sdjm 	free(sk);
18501bb7af0Sdjm }
18601bb7af0Sdjm 
18701bb7af0Sdjm static struct sk_usbhid **
18801bb7af0Sdjm sk_openv(const fido_dev_info_t *devlist, size_t ndevs, size_t *nopen)
18901bb7af0Sdjm {
19001bb7af0Sdjm 	const fido_dev_info_t *di;
19101bb7af0Sdjm 	struct sk_usbhid **skv;
19201bb7af0Sdjm 	size_t i;
19301bb7af0Sdjm 
19401bb7af0Sdjm 	*nopen = 0;
19501bb7af0Sdjm 	if ((skv = calloc(ndevs, sizeof(*skv))) == NULL) {
19601bb7af0Sdjm 		skdebug(__func__, "calloc skv failed");
19701bb7af0Sdjm 		return NULL;
19801bb7af0Sdjm 	}
19901bb7af0Sdjm 	for (i = 0; i < ndevs; i++) {
20001bb7af0Sdjm 		if ((di = fido_dev_info_ptr(devlist, i)) == NULL)
20101bb7af0Sdjm 			skdebug(__func__, "fido_dev_info_ptr failed");
20201bb7af0Sdjm 		else if ((skv[*nopen] = sk_open(fido_dev_info_path(di))) == NULL)
20301bb7af0Sdjm 			skdebug(__func__, "sk_open failed");
20401bb7af0Sdjm 		else
20501bb7af0Sdjm 			(*nopen)++;
20601bb7af0Sdjm 	}
20701bb7af0Sdjm 	if (*nopen == 0) {
20801bb7af0Sdjm 		for (i = 0; i < ndevs; i++)
20901bb7af0Sdjm 			sk_close(skv[i]);
21001bb7af0Sdjm 		free(skv);
21101bb7af0Sdjm 		skv = NULL;
21201bb7af0Sdjm 	}
21301bb7af0Sdjm 
21401bb7af0Sdjm 	return skv;
21501bb7af0Sdjm }
21601bb7af0Sdjm 
21701bb7af0Sdjm static void
21801bb7af0Sdjm sk_closev(struct sk_usbhid **skv, size_t nsk)
21901bb7af0Sdjm {
22001bb7af0Sdjm 	size_t i;
22101bb7af0Sdjm 
22201bb7af0Sdjm 	for (i = 0; i < nsk; i++)
22301bb7af0Sdjm 		sk_close(skv[i]);
22401bb7af0Sdjm 	free(skv);
22501bb7af0Sdjm }
22601bb7af0Sdjm 
227094c80e0Sdjm static int
22801bb7af0Sdjm sk_touch_begin(struct sk_usbhid **skv, size_t nsk)
22901bb7af0Sdjm {
23001bb7af0Sdjm 	size_t i, ok = 0;
23101bb7af0Sdjm 	int r;
23201bb7af0Sdjm 
23301bb7af0Sdjm 	for (i = 0; i < nsk; i++)
23401bb7af0Sdjm 		if ((r = fido_dev_get_touch_begin(skv[i]->dev)) != FIDO_OK)
23501bb7af0Sdjm 			skdebug(__func__, "fido_dev_get_touch_begin %s failed:"
23601bb7af0Sdjm 			    " %s", skv[i]->path, fido_strerr(r));
23701bb7af0Sdjm 		else
23801bb7af0Sdjm 			ok++;
23901bb7af0Sdjm 
24001bb7af0Sdjm 	return ok ? 0 : -1;
24101bb7af0Sdjm }
24201bb7af0Sdjm 
24301bb7af0Sdjm static int
24401bb7af0Sdjm sk_touch_poll(struct sk_usbhid **skv, size_t nsk, int *touch, size_t *idx)
24501bb7af0Sdjm {
24601bb7af0Sdjm 	struct timespec ts_pause;
24701bb7af0Sdjm 	size_t npoll, i;
24801bb7af0Sdjm 	int r;
24901bb7af0Sdjm 
25001bb7af0Sdjm 	ts_pause.tv_sec = 0;
25101bb7af0Sdjm 	ts_pause.tv_nsec = POLL_SLEEP_NS;
25201bb7af0Sdjm 	nanosleep(&ts_pause, NULL);
25301bb7af0Sdjm 	npoll = nsk;
25401bb7af0Sdjm 	for (i = 0; i < nsk; i++) {
25501bb7af0Sdjm 		if (skv[i] == NULL)
25601bb7af0Sdjm 			continue; /* device discarded */
25701bb7af0Sdjm 		skdebug(__func__, "polling %s", skv[i]->path);
25801bb7af0Sdjm 		if ((r = fido_dev_get_touch_status(skv[i]->dev, touch,
25901bb7af0Sdjm 		    FIDO_POLL_MS)) != FIDO_OK) {
26001bb7af0Sdjm 			skdebug(__func__, "fido_dev_get_touch_status %s: %s",
26101bb7af0Sdjm 			    skv[i]->path, fido_strerr(r));
26201bb7af0Sdjm 			sk_close(skv[i]); /* discard device */
26301bb7af0Sdjm 			skv[i] = NULL;
26401bb7af0Sdjm 			if (--npoll == 0) {
26501bb7af0Sdjm 				skdebug(__func__, "no device left to poll");
26601bb7af0Sdjm 				return -1;
26701bb7af0Sdjm 			}
26801bb7af0Sdjm 		} else if (*touch) {
26901bb7af0Sdjm 			*idx = i;
27001bb7af0Sdjm 			return 0;
27101bb7af0Sdjm 		}
27201bb7af0Sdjm 	}
27301bb7af0Sdjm 	*touch = 0;
27401bb7af0Sdjm 	return 0;
27501bb7af0Sdjm }
27601bb7af0Sdjm 
27701bb7af0Sdjm /* Calculate SHA256(m) */
27801bb7af0Sdjm static int
27901bb7af0Sdjm sha256_mem(const void *m, size_t mlen, u_char *d, size_t dlen)
28001bb7af0Sdjm {
28101bb7af0Sdjm #ifdef WITH_OPENSSL
28201bb7af0Sdjm 	u_int mdlen;
2839424a71bSdjm #else
2849424a71bSdjm 	SHA2_CTX ctx;
28501bb7af0Sdjm #endif
28601bb7af0Sdjm 
28701bb7af0Sdjm 	if (dlen != 32)
28801bb7af0Sdjm 		return -1;
28901bb7af0Sdjm #ifdef WITH_OPENSSL
29001bb7af0Sdjm 	mdlen = dlen;
29101bb7af0Sdjm 	if (!EVP_Digest(m, mlen, d, &mdlen, EVP_sha256(), NULL))
29201bb7af0Sdjm 		return -1;
29301bb7af0Sdjm #else
2949424a71bSdjm 	SHA256Init(&ctx);
2959424a71bSdjm 	SHA256Update(&ctx, (const uint8_t *)m, mlen);
2969424a71bSdjm 	SHA256Final(d, &ctx);
29701bb7af0Sdjm #endif
29801bb7af0Sdjm 	return 0;
29901bb7af0Sdjm }
30001bb7af0Sdjm 
30101bb7af0Sdjm /* Check if the specified key handle exists on a given sk. */
30201bb7af0Sdjm static int
30301bb7af0Sdjm sk_try(const struct sk_usbhid *sk, const char *application,
30401bb7af0Sdjm     const uint8_t *key_handle, size_t key_handle_len)
305094c80e0Sdjm {
306094c80e0Sdjm 	fido_assert_t *assert = NULL;
30701bb7af0Sdjm 	/* generate an invalid signature on FIDO2 tokens */
30801bb7af0Sdjm 	const char *data = "";
30901bb7af0Sdjm 	uint8_t message[32];
310094c80e0Sdjm 	int r = FIDO_ERR_INTERNAL;
311094c80e0Sdjm 
31201bb7af0Sdjm 	if (sha256_mem(data, strlen(data), message, sizeof(message)) != 0) {
31301bb7af0Sdjm 		skdebug(__func__, "hash message failed");
31401bb7af0Sdjm 		goto out;
31501bb7af0Sdjm 	}
316094c80e0Sdjm 	if ((assert = fido_assert_new()) == NULL) {
317094c80e0Sdjm 		skdebug(__func__, "fido_assert_new failed");
318094c80e0Sdjm 		goto out;
319094c80e0Sdjm 	}
320094c80e0Sdjm 	if ((r = fido_assert_set_clientdata_hash(assert, message,
32101bb7af0Sdjm 	    sizeof(message))) != FIDO_OK) {
322094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_clientdata_hash: %s",
323094c80e0Sdjm 		    fido_strerr(r));
324094c80e0Sdjm 		goto out;
325094c80e0Sdjm 	}
326094c80e0Sdjm 	if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
327094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
328094c80e0Sdjm 		goto out;
329094c80e0Sdjm 	}
330094c80e0Sdjm 	if ((r = fido_assert_allow_cred(assert, key_handle,
331094c80e0Sdjm 	    key_handle_len)) != FIDO_OK) {
332094c80e0Sdjm 		skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
333094c80e0Sdjm 		goto out;
334094c80e0Sdjm 	}
335094c80e0Sdjm 	if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) {
336094c80e0Sdjm 		skdebug(__func__, "fido_assert_up: %s", fido_strerr(r));
337094c80e0Sdjm 		goto out;
338094c80e0Sdjm 	}
33901bb7af0Sdjm 	r = fido_dev_get_assert(sk->dev, assert, NULL);
340094c80e0Sdjm 	skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
3417cf8e58dSdjm 	if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) {
3427cf8e58dSdjm 		/* U2F tokens may return this */
3437cf8e58dSdjm 		r = FIDO_OK;
3447cf8e58dSdjm 	}
345094c80e0Sdjm  out:
346094c80e0Sdjm 	fido_assert_free(&assert);
347094c80e0Sdjm 
348094c80e0Sdjm 	return r != FIDO_OK ? -1 : 0;
349094c80e0Sdjm }
350094c80e0Sdjm 
351c5365f21Sdjm static int
352c5365f21Sdjm check_sk_options(fido_dev_t *dev, const char *opt, int *ret)
353c5365f21Sdjm {
354c5365f21Sdjm 	fido_cbor_info_t *info;
355c5365f21Sdjm 	char * const *name;
356c5365f21Sdjm 	const bool *value;
357c5365f21Sdjm 	size_t len, i;
358c5365f21Sdjm 	int r;
359c5365f21Sdjm 
360c5365f21Sdjm 	*ret = -1;
361c5365f21Sdjm 
362c5365f21Sdjm 	if (!fido_dev_is_fido2(dev)) {
363c5365f21Sdjm 		skdebug(__func__, "device is not fido2");
364c5365f21Sdjm 		return 0;
365c5365f21Sdjm 	}
366c5365f21Sdjm 	if ((info = fido_cbor_info_new()) == NULL) {
367c5365f21Sdjm 		skdebug(__func__, "fido_cbor_info_new failed");
368c5365f21Sdjm 		return -1;
369c5365f21Sdjm 	}
370c5365f21Sdjm 	if ((r = fido_dev_get_cbor_info(dev, info)) != FIDO_OK) {
371c5365f21Sdjm 		skdebug(__func__, "fido_dev_get_cbor_info: %s", fido_strerr(r));
372c5365f21Sdjm 		fido_cbor_info_free(&info);
373c5365f21Sdjm 		return -1;
374c5365f21Sdjm 	}
375c5365f21Sdjm 	name = fido_cbor_info_options_name_ptr(info);
376c5365f21Sdjm 	value = fido_cbor_info_options_value_ptr(info);
377c5365f21Sdjm 	len = fido_cbor_info_options_len(info);
378c5365f21Sdjm 	for (i = 0; i < len; i++) {
379c5365f21Sdjm 		if (!strcmp(name[i], opt)) {
380c5365f21Sdjm 			*ret = value[i];
381c5365f21Sdjm 			break;
382c5365f21Sdjm 		}
383c5365f21Sdjm 	}
384c5365f21Sdjm 	fido_cbor_info_free(&info);
385c5365f21Sdjm 	if (*ret == -1)
386c5365f21Sdjm 		skdebug(__func__, "option %s is unknown", opt);
387c5365f21Sdjm 	else
388c5365f21Sdjm 		skdebug(__func__, "option %s is %s", opt, *ret ? "on" : "off");
389c5365f21Sdjm 
390c5365f21Sdjm 	return 0;
391c5365f21Sdjm }
392c5365f21Sdjm 
39301bb7af0Sdjm static struct sk_usbhid *
39401bb7af0Sdjm sk_select_by_cred(const fido_dev_info_t *devlist, size_t ndevs,
39501bb7af0Sdjm     const char *application, const uint8_t *key_handle, size_t key_handle_len)
396094c80e0Sdjm {
39701bb7af0Sdjm 	struct sk_usbhid **skv, *sk;
39801bb7af0Sdjm 	size_t skvcnt, i;
399*b8914b02Sdjm 	int internal_uv;
400094c80e0Sdjm 
40101bb7af0Sdjm 	if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
40201bb7af0Sdjm 		skdebug(__func__, "sk_openv failed");
403a0caf565Sdjm 		return NULL;
404a0caf565Sdjm 	}
405*b8914b02Sdjm 	if (skvcnt == 1 && check_sk_options(skv[0]->dev, "uv",
406*b8914b02Sdjm 	    &internal_uv) == 0 && internal_uv != -1) {
4078c966c57Sdjm 		sk = skv[0];
4088c966c57Sdjm 		skv[0] = NULL;
4098c966c57Sdjm 		goto out;
4108c966c57Sdjm 	}
41101bb7af0Sdjm 	sk = NULL;
4128c966c57Sdjm 	for (i = 0; i < skvcnt; i++) {
41301bb7af0Sdjm 		if (sk_try(skv[i], application, key_handle,
41401bb7af0Sdjm 		    key_handle_len) == 0) {
41501bb7af0Sdjm 			sk = skv[i];
41601bb7af0Sdjm 			skv[i] = NULL;
41701bb7af0Sdjm 			skdebug(__func__, "found key in %s", sk->path);
41801bb7af0Sdjm 			break;
41901bb7af0Sdjm 		}
4208c966c57Sdjm 	}
4218c966c57Sdjm  out:
42201bb7af0Sdjm 	sk_closev(skv, skvcnt);
42301bb7af0Sdjm 	return sk;
42401bb7af0Sdjm }
42501bb7af0Sdjm 
42601bb7af0Sdjm static struct sk_usbhid *
42701bb7af0Sdjm sk_select_by_touch(const fido_dev_info_t *devlist, size_t ndevs)
42801bb7af0Sdjm {
42901bb7af0Sdjm 	struct sk_usbhid **skv, *sk;
43001bb7af0Sdjm 	struct timeval tv_start, tv_now, tv_delta;
43101bb7af0Sdjm 	size_t skvcnt, idx;
43201bb7af0Sdjm 	int touch, ms_remain;
43301bb7af0Sdjm 
43401bb7af0Sdjm 	if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
43501bb7af0Sdjm 		skdebug(__func__, "sk_openv failed");
436a0caf565Sdjm 		return NULL;
437a0caf565Sdjm 	}
43801bb7af0Sdjm 	sk = NULL;
43901bb7af0Sdjm 	if (skvcnt < 2) {
44001bb7af0Sdjm 		if (skvcnt == 1) {
44101bb7af0Sdjm 			/* single candidate */
44201bb7af0Sdjm 			sk = skv[0];
44301bb7af0Sdjm 			skv[0] = NULL;
444a0caf565Sdjm 		}
44501bb7af0Sdjm 		goto out;
44601bb7af0Sdjm 	}
44701bb7af0Sdjm 	if (sk_touch_begin(skv, skvcnt) == -1) {
44801bb7af0Sdjm 		skdebug(__func__, "sk_touch_begin failed");
44901bb7af0Sdjm 		goto out;
45001bb7af0Sdjm 	}
45101bb7af0Sdjm 	monotime_tv(&tv_start);
45201bb7af0Sdjm 	do {
45301bb7af0Sdjm 		if (sk_touch_poll(skv, skvcnt, &touch, &idx) == -1) {
45401bb7af0Sdjm 			skdebug(__func__, "sk_touch_poll failed");
45501bb7af0Sdjm 			goto out;
45601bb7af0Sdjm 		}
45701bb7af0Sdjm 		if (touch) {
45801bb7af0Sdjm 			sk = skv[idx];
45901bb7af0Sdjm 			skv[idx] = NULL;
46001bb7af0Sdjm 			goto out;
46101bb7af0Sdjm 		}
46201bb7af0Sdjm 		monotime_tv(&tv_now);
46301bb7af0Sdjm 		timersub(&tv_now, &tv_start, &tv_delta);
46401bb7af0Sdjm 		ms_remain = SELECT_MS - tv_delta.tv_sec * 1000 -
46501bb7af0Sdjm 		    tv_delta.tv_usec / 1000;
46601bb7af0Sdjm 	} while (ms_remain >= FIDO_POLL_MS);
46701bb7af0Sdjm 	skdebug(__func__, "timeout");
46801bb7af0Sdjm out:
46901bb7af0Sdjm 	sk_closev(skv, skvcnt);
47001bb7af0Sdjm 	return sk;
47101bb7af0Sdjm }
47201bb7af0Sdjm 
47301bb7af0Sdjm static struct sk_usbhid *
47401bb7af0Sdjm sk_probe(const char *application, const uint8_t *key_handle,
47501bb7af0Sdjm     size_t key_handle_len)
47601bb7af0Sdjm {
47701bb7af0Sdjm 	struct sk_usbhid *sk;
47801bb7af0Sdjm 	fido_dev_info_t *devlist;
47901bb7af0Sdjm 	size_t ndevs;
48001bb7af0Sdjm 	int r;
481a0caf565Sdjm 
482094c80e0Sdjm 	if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
483094c80e0Sdjm 		skdebug(__func__, "fido_dev_info_new failed");
48401bb7af0Sdjm 		return NULL;
485094c80e0Sdjm 	}
486094c80e0Sdjm 	if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES,
48701bb7af0Sdjm 	    &ndevs)) != FIDO_OK) {
48801bb7af0Sdjm 		skdebug(__func__, "fido_dev_info_manifest failed: %s",
48901bb7af0Sdjm 		    fido_strerr(r));
490094c80e0Sdjm 		fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
49101bb7af0Sdjm 		return NULL;
49201bb7af0Sdjm 	}
49301bb7af0Sdjm 	skdebug(__func__, "%zu device(s) detected", ndevs);
49401bb7af0Sdjm 	if (ndevs == 0) {
49501bb7af0Sdjm 		sk = NULL;
49601bb7af0Sdjm 	} else if (application != NULL && key_handle != NULL) {
49701bb7af0Sdjm 		skdebug(__func__, "selecting sk by cred");
49801bb7af0Sdjm 		sk = sk_select_by_cred(devlist, ndevs, application, key_handle,
49901bb7af0Sdjm 		    key_handle_len);
50001bb7af0Sdjm 	} else {
50101bb7af0Sdjm 		skdebug(__func__, "selecting sk by touch");
50201bb7af0Sdjm 		sk = sk_select_by_touch(devlist, ndevs);
50301bb7af0Sdjm 	}
50401bb7af0Sdjm 	fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
50501bb7af0Sdjm 	return sk;
506094c80e0Sdjm }
507094c80e0Sdjm 
508f8cd6cb1Snaddy #ifdef WITH_OPENSSL
509094c80e0Sdjm /*
510094c80e0Sdjm  * The key returned via fido_cred_pubkey_ptr() is in affine coordinates,
511094c80e0Sdjm  * but the API expects a SEC1 octet string.
512094c80e0Sdjm  */
513094c80e0Sdjm static int
5141ac4a90aSdjm pack_public_key_ecdsa(const fido_cred_t *cred,
5151ac4a90aSdjm     struct sk_enroll_response *response)
516094c80e0Sdjm {
517094c80e0Sdjm 	const uint8_t *ptr;
518094c80e0Sdjm 	BIGNUM *x = NULL, *y = NULL;
519094c80e0Sdjm 	EC_POINT *q = NULL;
520094c80e0Sdjm 	EC_GROUP *g = NULL;
521094c80e0Sdjm 	int ret = -1;
522094c80e0Sdjm 
523094c80e0Sdjm 	response->public_key = NULL;
524094c80e0Sdjm 	response->public_key_len = 0;
525094c80e0Sdjm 
52607b718edSdjm 	if ((x = BN_new()) == NULL ||
52707b718edSdjm 	    (y = BN_new()) == NULL ||
528094c80e0Sdjm 	    (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL ||
529094c80e0Sdjm 	    (q = EC_POINT_new(g)) == NULL) {
530094c80e0Sdjm 		skdebug(__func__, "libcrypto setup failed");
531094c80e0Sdjm 		goto out;
532094c80e0Sdjm 	}
533094c80e0Sdjm 	if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
534094c80e0Sdjm 		skdebug(__func__, "fido_cred_pubkey_ptr failed");
535094c80e0Sdjm 		goto out;
536094c80e0Sdjm 	}
537094c80e0Sdjm 	if (fido_cred_pubkey_len(cred) != 64) {
538094c80e0Sdjm 		skdebug(__func__, "bad fido_cred_pubkey_len %zu",
539094c80e0Sdjm 		    fido_cred_pubkey_len(cred));
540094c80e0Sdjm 		goto out;
541094c80e0Sdjm 	}
542094c80e0Sdjm 
543094c80e0Sdjm 	if (BN_bin2bn(ptr, 32, x) == NULL ||
544094c80e0Sdjm 	    BN_bin2bn(ptr + 32, 32, y) == NULL) {
545094c80e0Sdjm 		skdebug(__func__, "BN_bin2bn failed");
546094c80e0Sdjm 		goto out;
547094c80e0Sdjm 	}
54807b718edSdjm 	if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) {
549094c80e0Sdjm 		skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed");
550094c80e0Sdjm 		goto out;
551094c80e0Sdjm 	}
552094c80e0Sdjm 	response->public_key_len = EC_POINT_point2oct(g, q,
55307b718edSdjm 	    POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
554094c80e0Sdjm 	if (response->public_key_len == 0 || response->public_key_len > 2048) {
555094c80e0Sdjm 		skdebug(__func__, "bad pubkey length %zu",
556094c80e0Sdjm 		    response->public_key_len);
557094c80e0Sdjm 		goto out;
558094c80e0Sdjm 	}
559094c80e0Sdjm 	if ((response->public_key = malloc(response->public_key_len)) == NULL) {
560094c80e0Sdjm 		skdebug(__func__, "malloc pubkey failed");
561094c80e0Sdjm 		goto out;
562094c80e0Sdjm 	}
563094c80e0Sdjm 	if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED,
56407b718edSdjm 	    response->public_key, response->public_key_len, NULL) == 0) {
565094c80e0Sdjm 		skdebug(__func__, "EC_POINT_point2oct failed");
566094c80e0Sdjm 		goto out;
567094c80e0Sdjm 	}
568094c80e0Sdjm 	/* success */
569094c80e0Sdjm 	ret = 0;
570094c80e0Sdjm  out:
571094c80e0Sdjm 	if (ret != 0 && response->public_key != NULL) {
572094c80e0Sdjm 		memset(response->public_key, 0, response->public_key_len);
573094c80e0Sdjm 		free(response->public_key);
574094c80e0Sdjm 		response->public_key = NULL;
575094c80e0Sdjm 	}
576094c80e0Sdjm 	EC_POINT_free(q);
577094c80e0Sdjm 	EC_GROUP_free(g);
57807b718edSdjm 	BN_clear_free(x);
57907b718edSdjm 	BN_clear_free(y);
580094c80e0Sdjm 	return ret;
581094c80e0Sdjm }
582f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
583094c80e0Sdjm 
584094c80e0Sdjm static int
5851ac4a90aSdjm pack_public_key_ed25519(const fido_cred_t *cred,
5861ac4a90aSdjm     struct sk_enroll_response *response)
587094c80e0Sdjm {
588094c80e0Sdjm 	const uint8_t *ptr;
589094c80e0Sdjm 	size_t len;
590094c80e0Sdjm 	int ret = -1;
591094c80e0Sdjm 
592094c80e0Sdjm 	response->public_key = NULL;
593094c80e0Sdjm 	response->public_key_len = 0;
594094c80e0Sdjm 
595094c80e0Sdjm 	if ((len = fido_cred_pubkey_len(cred)) != 32) {
596094c80e0Sdjm 		skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len);
597094c80e0Sdjm 		goto out;
598094c80e0Sdjm 	}
599094c80e0Sdjm 	if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
600094c80e0Sdjm 		skdebug(__func__, "fido_cred_pubkey_ptr failed");
601094c80e0Sdjm 		goto out;
602094c80e0Sdjm 	}
603094c80e0Sdjm 	response->public_key_len = len;
604094c80e0Sdjm 	if ((response->public_key = malloc(response->public_key_len)) == NULL) {
605094c80e0Sdjm 		skdebug(__func__, "malloc pubkey failed");
606094c80e0Sdjm 		goto out;
607094c80e0Sdjm 	}
608094c80e0Sdjm 	memcpy(response->public_key, ptr, len);
609094c80e0Sdjm 	ret = 0;
610094c80e0Sdjm  out:
611094c80e0Sdjm 	if (ret != 0)
612094c80e0Sdjm 		free(response->public_key);
613094c80e0Sdjm 	return ret;
614094c80e0Sdjm }
615094c80e0Sdjm 
616094c80e0Sdjm static int
617a0caf565Sdjm pack_public_key(uint32_t alg, const fido_cred_t *cred,
6181ac4a90aSdjm     struct sk_enroll_response *response)
619094c80e0Sdjm {
620094c80e0Sdjm 	switch(alg) {
621f8cd6cb1Snaddy #ifdef WITH_OPENSSL
622b0297854Sdjm 	case SSH_SK_ECDSA:
623094c80e0Sdjm 		return pack_public_key_ecdsa(cred, response);
624f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
625b0297854Sdjm 	case SSH_SK_ED25519:
626094c80e0Sdjm 		return pack_public_key_ed25519(cred, response);
627094c80e0Sdjm 	default:
628094c80e0Sdjm 		return -1;
629094c80e0Sdjm 	}
630094c80e0Sdjm }
631094c80e0Sdjm 
632480af03fSdjm static int
633480af03fSdjm fidoerr_to_skerr(int fidoerr)
634480af03fSdjm {
635480af03fSdjm 	switch (fidoerr) {
636480af03fSdjm 	case FIDO_ERR_UNSUPPORTED_OPTION:
637b0297854Sdjm 	case FIDO_ERR_UNSUPPORTED_ALGORITHM:
638480af03fSdjm 		return SSH_SK_ERR_UNSUPPORTED;
639480af03fSdjm 	case FIDO_ERR_PIN_REQUIRED:
640480af03fSdjm 	case FIDO_ERR_PIN_INVALID:
64118b3d906Sdjm 	case FIDO_ERR_OPERATION_DENIED:
642480af03fSdjm 		return SSH_SK_ERR_PIN_REQUIRED;
643480af03fSdjm 	default:
644480af03fSdjm 		return -1;
645480af03fSdjm 	}
646480af03fSdjm }
647480af03fSdjm 
648a0caf565Sdjm static int
649a0caf565Sdjm check_enroll_options(struct sk_option **options, char **devicep,
650a0caf565Sdjm     uint8_t *user_id, size_t user_id_len)
651a0caf565Sdjm {
652a0caf565Sdjm 	size_t i;
653a0caf565Sdjm 
654a0caf565Sdjm 	if (options == NULL)
655a0caf565Sdjm 		return 0;
656a0caf565Sdjm 	for (i = 0; options[i] != NULL; i++) {
657a0caf565Sdjm 		if (strcmp(options[i]->name, "device") == 0) {
658a0caf565Sdjm 			if ((*devicep = strdup(options[i]->value)) == NULL) {
659a0caf565Sdjm 				skdebug(__func__, "strdup device failed");
660a0caf565Sdjm 				return -1;
661a0caf565Sdjm 			}
662a0caf565Sdjm 			skdebug(__func__, "requested device %s", *devicep);
6631a9bfc1cSdjm 		} else if (strcmp(options[i]->name, "user") == 0) {
664a0caf565Sdjm 			if (strlcpy(user_id, options[i]->value, user_id_len) >=
665a0caf565Sdjm 			    user_id_len) {
666a0caf565Sdjm 				skdebug(__func__, "user too long");
667a0caf565Sdjm 				return -1;
668a0caf565Sdjm 			}
669a0caf565Sdjm 			skdebug(__func__, "requested user %s",
670a0caf565Sdjm 			    (char *)user_id);
671a0caf565Sdjm 		} else {
672a0caf565Sdjm 			skdebug(__func__, "requested unsupported option %s",
673a0caf565Sdjm 			    options[i]->name);
674a0caf565Sdjm 			if (options[i]->required) {
675a0caf565Sdjm 				skdebug(__func__, "unknown required option");
676a0caf565Sdjm 				return -1;
677a0caf565Sdjm 			}
678a0caf565Sdjm 		}
679a0caf565Sdjm 	}
680a0caf565Sdjm 	return 0;
681a0caf565Sdjm }
682a0caf565Sdjm 
683094c80e0Sdjm int
684a0caf565Sdjm sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
6852db06755Sdjm     const char *application, uint8_t flags, const char *pin,
686a0caf565Sdjm     struct sk_option **options, struct sk_enroll_response **enroll_response)
687094c80e0Sdjm {
688094c80e0Sdjm 	fido_cred_t *cred = NULL;
689094c80e0Sdjm 	const uint8_t *ptr;
6905e70647bSdjm 	uint8_t user_id[32], chall_hash[32];
69101bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
692094c80e0Sdjm 	struct sk_enroll_response *response = NULL;
693094c80e0Sdjm 	size_t len;
6944a0fa473Sdjm 	int credprot;
69518b3d906Sdjm 	int internal_uv;
696094c80e0Sdjm 	int cose_alg;
697480af03fSdjm 	int ret = SSH_SK_ERR_GENERAL;
698094c80e0Sdjm 	int r;
699094c80e0Sdjm 	char *device = NULL;
700094c80e0Sdjm 
701fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
702fb0a6bcaSdjm 
70335f0234cSmarkus 	if (enroll_response == NULL) {
70435f0234cSmarkus 		skdebug(__func__, "enroll_response == NULL");
705094c80e0Sdjm 		goto out;
706094c80e0Sdjm 	}
70701bb7af0Sdjm 	*enroll_response = NULL;
708a0caf565Sdjm 	memset(user_id, 0, sizeof(user_id));
70901bb7af0Sdjm 	if (check_enroll_options(options, &device, user_id,
71001bb7af0Sdjm 	    sizeof(user_id)) != 0)
711a0caf565Sdjm 		goto out; /* error already logged */
712a0caf565Sdjm 
713094c80e0Sdjm 	switch(alg) {
714f8cd6cb1Snaddy #ifdef WITH_OPENSSL
715b0297854Sdjm 	case SSH_SK_ECDSA:
716094c80e0Sdjm 		cose_alg = COSE_ES256;
717094c80e0Sdjm 		break;
718f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
719b0297854Sdjm 	case SSH_SK_ED25519:
720094c80e0Sdjm 		cose_alg = COSE_EDDSA;
721094c80e0Sdjm 		break;
722094c80e0Sdjm 	default:
723094c80e0Sdjm 		skdebug(__func__, "unsupported key type %d", alg);
724094c80e0Sdjm 		goto out;
725094c80e0Sdjm 	}
72601bb7af0Sdjm 	if (device != NULL)
72701bb7af0Sdjm 		sk = sk_open(device);
72801bb7af0Sdjm 	else
72901bb7af0Sdjm 		sk = sk_probe(NULL, NULL, 0);
73001bb7af0Sdjm 	if (sk == NULL) {
73101bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
732094c80e0Sdjm 		goto out;
733094c80e0Sdjm 	}
73401bb7af0Sdjm 	skdebug(__func__, "using device %s", sk->path);
735094c80e0Sdjm 	if ((cred = fido_cred_new()) == NULL) {
736094c80e0Sdjm 		skdebug(__func__, "fido_cred_new failed");
737094c80e0Sdjm 		goto out;
738094c80e0Sdjm 	}
739094c80e0Sdjm 	if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) {
740094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r));
741094c80e0Sdjm 		goto out;
742094c80e0Sdjm 	}
7435e70647bSdjm 	if (sha256_mem(challenge, challenge_len,
7445e70647bSdjm 	    chall_hash, sizeof(chall_hash)) != 0) {
7455e70647bSdjm 		skdebug(__func__, "hash challenge failed");
7465e70647bSdjm 		goto out;
7475e70647bSdjm 	}
7485e70647bSdjm 	if ((r = fido_cred_set_clientdata_hash(cred, chall_hash,
7495e70647bSdjm 	    sizeof(chall_hash))) != FIDO_OK) {
750094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_clientdata_hash: %s",
751094c80e0Sdjm 		    fido_strerr(r));
752094c80e0Sdjm 		goto out;
753094c80e0Sdjm 	}
754b0297854Sdjm 	if ((r = fido_cred_set_rk(cred, (flags & SSH_SK_RESIDENT_KEY) != 0 ?
7558908bc36Sdjm 	    FIDO_OPT_TRUE : FIDO_OPT_OMIT)) != FIDO_OK) {
7568908bc36Sdjm 		skdebug(__func__, "fido_cred_set_rk: %s", fido_strerr(r));
7578908bc36Sdjm 		goto out;
7588908bc36Sdjm 	}
759094c80e0Sdjm 	if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id),
760094c80e0Sdjm 	    "openssh", "openssh", NULL)) != FIDO_OK) {
761094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r));
762094c80e0Sdjm 		goto out;
763094c80e0Sdjm 	}
764094c80e0Sdjm 	if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) {
765094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r));
766094c80e0Sdjm 		goto out;
767094c80e0Sdjm 	}
7681f63d3c4Sdjm 	if ((flags & (SSH_SK_RESIDENT_KEY|SSH_SK_USER_VERIFICATION_REQD)) != 0) {
76901bb7af0Sdjm 		if (!fido_dev_supports_cred_prot(sk->dev)) {
77001bb7af0Sdjm 			skdebug(__func__, "%s does not support credprot, "
77101bb7af0Sdjm 			    "refusing to create unprotected "
77201bb7af0Sdjm 			    "resident/verify-required key", sk->path);
7734a0fa473Sdjm 			ret = SSH_SK_ERR_UNSUPPORTED;
7744a0fa473Sdjm 			goto out;
7754a0fa473Sdjm 		}
7761f63d3c4Sdjm 		if ((flags & SSH_SK_USER_VERIFICATION_REQD))
7771f63d3c4Sdjm 			credprot = FIDO_CRED_PROT_UV_REQUIRED;
7781f63d3c4Sdjm 		else
7791f63d3c4Sdjm 			credprot = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID;
7801f63d3c4Sdjm 
7811f63d3c4Sdjm 		if ((r = fido_cred_set_prot(cred, credprot)) != FIDO_OK) {
7824a0fa473Sdjm 			skdebug(__func__, "fido_cred_set_prot: %s",
7834a0fa473Sdjm 			    fido_strerr(r));
7844a0fa473Sdjm 			ret = fidoerr_to_skerr(r);
7854a0fa473Sdjm 			goto out;
7864a0fa473Sdjm 		}
7874a0fa473Sdjm 	}
78801bb7af0Sdjm 	if ((r = fido_dev_make_cred(sk->dev, cred, pin)) != FIDO_OK) {
789094c80e0Sdjm 		skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r));
790480af03fSdjm 		ret = fidoerr_to_skerr(r);
791094c80e0Sdjm 		goto out;
792094c80e0Sdjm 	}
793094c80e0Sdjm 	if (fido_cred_x5c_ptr(cred) != NULL) {
794094c80e0Sdjm 		if ((r = fido_cred_verify(cred)) != FIDO_OK) {
795094c80e0Sdjm 			skdebug(__func__, "fido_cred_verify: %s",
796094c80e0Sdjm 			    fido_strerr(r));
797094c80e0Sdjm 			goto out;
798094c80e0Sdjm 		}
799094c80e0Sdjm 	} else {
800094c80e0Sdjm 		skdebug(__func__, "self-attested credential");
801094c80e0Sdjm 		if ((r = fido_cred_verify_self(cred)) != FIDO_OK) {
802094c80e0Sdjm 			skdebug(__func__, "fido_cred_verify_self: %s",
803094c80e0Sdjm 			    fido_strerr(r));
804094c80e0Sdjm 			goto out;
805094c80e0Sdjm 		}
806094c80e0Sdjm 	}
807094c80e0Sdjm 	if ((response = calloc(1, sizeof(*response))) == NULL) {
808094c80e0Sdjm 		skdebug(__func__, "calloc response failed");
809094c80e0Sdjm 		goto out;
810094c80e0Sdjm 	}
81118b3d906Sdjm 	response->flags = flags;
81218b3d906Sdjm 	if ((flags & SSH_SK_USER_VERIFICATION_REQD)) {
81318b3d906Sdjm 		if (check_sk_options(sk->dev, "uv", &internal_uv) == 0 &&
81418b3d906Sdjm 		    internal_uv != -1) {
81518b3d906Sdjm 			/* user verification handled by token */
81618b3d906Sdjm 			response->flags &= ~SSH_SK_USER_VERIFICATION_REQD;
81718b3d906Sdjm 		}
81818b3d906Sdjm 	}
819094c80e0Sdjm 	if (pack_public_key(alg, cred, response) != 0) {
820094c80e0Sdjm 		skdebug(__func__, "pack_public_key failed");
821094c80e0Sdjm 		goto out;
822094c80e0Sdjm 	}
823094c80e0Sdjm 	if ((ptr = fido_cred_id_ptr(cred)) != NULL) {
824094c80e0Sdjm 		len = fido_cred_id_len(cred);
825094c80e0Sdjm 		if ((response->key_handle = calloc(1, len)) == NULL) {
826094c80e0Sdjm 			skdebug(__func__, "calloc key handle failed");
827094c80e0Sdjm 			goto out;
828094c80e0Sdjm 		}
829094c80e0Sdjm 		memcpy(response->key_handle, ptr, len);
830094c80e0Sdjm 		response->key_handle_len = len;
831094c80e0Sdjm 	}
832094c80e0Sdjm 	if ((ptr = fido_cred_sig_ptr(cred)) != NULL) {
833094c80e0Sdjm 		len = fido_cred_sig_len(cred);
834094c80e0Sdjm 		if ((response->signature = calloc(1, len)) == NULL) {
835094c80e0Sdjm 			skdebug(__func__, "calloc signature failed");
836094c80e0Sdjm 			goto out;
837094c80e0Sdjm 		}
838094c80e0Sdjm 		memcpy(response->signature, ptr, len);
839094c80e0Sdjm 		response->signature_len = len;
840094c80e0Sdjm 	}
841094c80e0Sdjm 	if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) {
842094c80e0Sdjm 		len = fido_cred_x5c_len(cred);
84348e6b99dSdjm 		skdebug(__func__, "attestation cert len=%zu", len);
844094c80e0Sdjm 		if ((response->attestation_cert = calloc(1, len)) == NULL) {
845094c80e0Sdjm 			skdebug(__func__, "calloc attestation cert failed");
846094c80e0Sdjm 			goto out;
847094c80e0Sdjm 		}
848094c80e0Sdjm 		memcpy(response->attestation_cert, ptr, len);
849094c80e0Sdjm 		response->attestation_cert_len = len;
850094c80e0Sdjm 	}
851ee0a8761Sdjm 	if ((ptr = fido_cred_authdata_ptr(cred)) != NULL) {
852ee0a8761Sdjm 		len = fido_cred_authdata_len(cred);
85348e6b99dSdjm 		skdebug(__func__, "authdata len=%zu", len);
854ee0a8761Sdjm 		if ((response->authdata = calloc(1, len)) == NULL) {
855ee0a8761Sdjm 			skdebug(__func__, "calloc authdata failed");
856ee0a8761Sdjm 			goto out;
857ee0a8761Sdjm 		}
858ee0a8761Sdjm 		memcpy(response->authdata, ptr, len);
859ee0a8761Sdjm 		response->authdata_len = len;
860ee0a8761Sdjm 	}
86135f0234cSmarkus 	*enroll_response = response;
862094c80e0Sdjm 	response = NULL;
863094c80e0Sdjm 	ret = 0;
864094c80e0Sdjm  out:
865094c80e0Sdjm 	free(device);
866094c80e0Sdjm 	if (response != NULL) {
867094c80e0Sdjm 		free(response->public_key);
868094c80e0Sdjm 		free(response->key_handle);
869094c80e0Sdjm 		free(response->signature);
870094c80e0Sdjm 		free(response->attestation_cert);
871ee0a8761Sdjm 		free(response->authdata);
872094c80e0Sdjm 		free(response);
873094c80e0Sdjm 	}
87401bb7af0Sdjm 	sk_close(sk);
875094c80e0Sdjm 	fido_cred_free(&cred);
876094c80e0Sdjm 	return ret;
877094c80e0Sdjm }
878094c80e0Sdjm 
879f8cd6cb1Snaddy #ifdef WITH_OPENSSL
880094c80e0Sdjm static int
881094c80e0Sdjm pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response)
882094c80e0Sdjm {
883094c80e0Sdjm 	ECDSA_SIG *sig = NULL;
884094c80e0Sdjm 	const BIGNUM *sig_r, *sig_s;
885094c80e0Sdjm 	const unsigned char *cp;
886094c80e0Sdjm 	size_t sig_len;
887094c80e0Sdjm 	int ret = -1;
888094c80e0Sdjm 
889094c80e0Sdjm 	cp = fido_assert_sig_ptr(assert, 0);
890094c80e0Sdjm 	sig_len = fido_assert_sig_len(assert, 0);
891094c80e0Sdjm 	if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) {
892094c80e0Sdjm 		skdebug(__func__, "d2i_ECDSA_SIG failed");
893094c80e0Sdjm 		goto out;
894094c80e0Sdjm 	}
895094c80e0Sdjm 	ECDSA_SIG_get0(sig, &sig_r, &sig_s);
896094c80e0Sdjm 	response->sig_r_len = BN_num_bytes(sig_r);
897094c80e0Sdjm 	response->sig_s_len = BN_num_bytes(sig_s);
898094c80e0Sdjm 	if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL ||
899094c80e0Sdjm 	    (response->sig_s = calloc(1, response->sig_s_len)) == NULL) {
900094c80e0Sdjm 		skdebug(__func__, "calloc signature failed");
901094c80e0Sdjm 		goto out;
902094c80e0Sdjm 	}
903094c80e0Sdjm 	BN_bn2bin(sig_r, response->sig_r);
904094c80e0Sdjm 	BN_bn2bin(sig_s, response->sig_s);
905094c80e0Sdjm 	ret = 0;
906094c80e0Sdjm  out:
907094c80e0Sdjm 	ECDSA_SIG_free(sig);
908094c80e0Sdjm 	if (ret != 0) {
909094c80e0Sdjm 		free(response->sig_r);
910094c80e0Sdjm 		free(response->sig_s);
911094c80e0Sdjm 		response->sig_r = NULL;
912094c80e0Sdjm 		response->sig_s = NULL;
913094c80e0Sdjm 	}
914094c80e0Sdjm 	return ret;
915094c80e0Sdjm }
916f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
917094c80e0Sdjm 
918094c80e0Sdjm static int
919094c80e0Sdjm pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response)
920094c80e0Sdjm {
921094c80e0Sdjm 	const unsigned char *ptr;
922094c80e0Sdjm 	size_t len;
923094c80e0Sdjm 	int ret = -1;
924094c80e0Sdjm 
925094c80e0Sdjm 	ptr = fido_assert_sig_ptr(assert, 0);
926094c80e0Sdjm 	len = fido_assert_sig_len(assert, 0);
927094c80e0Sdjm 	if (len != 64) {
928094c80e0Sdjm 		skdebug(__func__, "bad length %zu", len);
929094c80e0Sdjm 		goto out;
930094c80e0Sdjm 	}
931094c80e0Sdjm 	response->sig_r_len = len;
932094c80e0Sdjm 	if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) {
933094c80e0Sdjm 		skdebug(__func__, "calloc signature failed");
934094c80e0Sdjm 		goto out;
935094c80e0Sdjm 	}
936094c80e0Sdjm 	memcpy(response->sig_r, ptr, len);
937094c80e0Sdjm 	ret = 0;
938094c80e0Sdjm  out:
939094c80e0Sdjm 	if (ret != 0) {
940094c80e0Sdjm 		free(response->sig_r);
941094c80e0Sdjm 		response->sig_r = NULL;
942094c80e0Sdjm 	}
943094c80e0Sdjm 	return ret;
944094c80e0Sdjm }
945094c80e0Sdjm 
946094c80e0Sdjm static int
947a0caf565Sdjm pack_sig(uint32_t  alg, fido_assert_t *assert,
948a0caf565Sdjm     struct sk_sign_response *response)
949094c80e0Sdjm {
950094c80e0Sdjm 	switch(alg) {
951f8cd6cb1Snaddy #ifdef WITH_OPENSSL
952b0297854Sdjm 	case SSH_SK_ECDSA:
953094c80e0Sdjm 		return pack_sig_ecdsa(assert, response);
954f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
955b0297854Sdjm 	case SSH_SK_ED25519:
956094c80e0Sdjm 		return pack_sig_ed25519(assert, response);
957094c80e0Sdjm 	default:
958094c80e0Sdjm 		return -1;
959094c80e0Sdjm 	}
960094c80e0Sdjm }
961094c80e0Sdjm 
962a0caf565Sdjm /* Checks sk_options for sk_sign() and sk_load_resident_keys() */
963a0caf565Sdjm static int
964a0caf565Sdjm check_sign_load_resident_options(struct sk_option **options, char **devicep)
965a0caf565Sdjm {
966a0caf565Sdjm 	size_t i;
967a0caf565Sdjm 
968a0caf565Sdjm 	if (options == NULL)
969a0caf565Sdjm 		return 0;
970a0caf565Sdjm 	for (i = 0; options[i] != NULL; i++) {
971a0caf565Sdjm 		if (strcmp(options[i]->name, "device") == 0) {
972a0caf565Sdjm 			if ((*devicep = strdup(options[i]->value)) == NULL) {
973a0caf565Sdjm 				skdebug(__func__, "strdup device failed");
974a0caf565Sdjm 				return -1;
975a0caf565Sdjm 			}
976a0caf565Sdjm 			skdebug(__func__, "requested device %s", *devicep);
977a0caf565Sdjm 		} else {
978a0caf565Sdjm 			skdebug(__func__, "requested unsupported option %s",
979a0caf565Sdjm 			    options[i]->name);
980a0caf565Sdjm 			if (options[i]->required) {
981a0caf565Sdjm 				skdebug(__func__, "unknown required option");
982a0caf565Sdjm 				return -1;
983a0caf565Sdjm 			}
984a0caf565Sdjm 		}
985a0caf565Sdjm 	}
986a0caf565Sdjm 	return 0;
987a0caf565Sdjm }
988a0caf565Sdjm 
989094c80e0Sdjm int
9903ce2af41Sdjm sk_sign(uint32_t alg, const uint8_t *data, size_t datalen,
991094c80e0Sdjm     const char *application,
992094c80e0Sdjm     const uint8_t *key_handle, size_t key_handle_len,
993a0caf565Sdjm     uint8_t flags, const char *pin, struct sk_option **options,
994a0caf565Sdjm     struct sk_sign_response **sign_response)
995094c80e0Sdjm {
996094c80e0Sdjm 	fido_assert_t *assert = NULL;
997a0caf565Sdjm 	char *device = NULL;
99801bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
999094c80e0Sdjm 	struct sk_sign_response *response = NULL;
10003ce2af41Sdjm 	uint8_t message[32];
100118b3d906Sdjm 	int ret = SSH_SK_ERR_GENERAL, internal_uv;
1002094c80e0Sdjm 	int r;
1003094c80e0Sdjm 
1004fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
1005094c80e0Sdjm 
1006094c80e0Sdjm 	if (sign_response == NULL) {
1007094c80e0Sdjm 		skdebug(__func__, "sign_response == NULL");
1008094c80e0Sdjm 		goto out;
1009094c80e0Sdjm 	}
1010094c80e0Sdjm 	*sign_response = NULL;
1011a0caf565Sdjm 	if (check_sign_load_resident_options(options, &device) != 0)
1012a0caf565Sdjm 		goto out; /* error already logged */
10133ce2af41Sdjm 	/* hash data to be signed before it goes to the security key */
10143ce2af41Sdjm 	if ((r = sha256_mem(data, datalen, message, sizeof(message))) != 0) {
10153ce2af41Sdjm 		skdebug(__func__, "hash message failed");
10163ce2af41Sdjm 		goto out;
10173ce2af41Sdjm 	}
101801bb7af0Sdjm 	if (device != NULL)
101901bb7af0Sdjm 		sk = sk_open(device);
102001bb7af0Sdjm 	else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD))
102101bb7af0Sdjm 		sk = sk_probe(NULL, NULL, 0);
102201bb7af0Sdjm 	else
102301bb7af0Sdjm 		sk = sk_probe(application, key_handle, key_handle_len);
102401bb7af0Sdjm 	if (sk == NULL) {
102501bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
1026094c80e0Sdjm 		goto out;
1027094c80e0Sdjm 	}
1028094c80e0Sdjm 	if ((assert = fido_assert_new()) == NULL) {
1029094c80e0Sdjm 		skdebug(__func__, "fido_assert_new failed");
1030094c80e0Sdjm 		goto out;
1031094c80e0Sdjm 	}
1032094c80e0Sdjm 	if ((r = fido_assert_set_clientdata_hash(assert, message,
10333ce2af41Sdjm 	    sizeof(message))) != FIDO_OK) {
1034094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_clientdata_hash: %s",
1035094c80e0Sdjm 		    fido_strerr(r));
1036094c80e0Sdjm 		goto out;
1037094c80e0Sdjm 	}
1038094c80e0Sdjm 	if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
1039094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
1040094c80e0Sdjm 		goto out;
1041094c80e0Sdjm 	}
1042094c80e0Sdjm 	if ((r = fido_assert_allow_cred(assert, key_handle,
1043094c80e0Sdjm 	    key_handle_len)) != FIDO_OK) {
1044094c80e0Sdjm 		skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
1045094c80e0Sdjm 		goto out;
1046094c80e0Sdjm 	}
1047094c80e0Sdjm 	if ((r = fido_assert_set_up(assert,
1048b0297854Sdjm 	    (flags & SSH_SK_USER_PRESENCE_REQD) ?
1049094c80e0Sdjm 	    FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) {
1050094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r));
1051094c80e0Sdjm 		goto out;
1052094c80e0Sdjm 	}
105318b3d906Sdjm 	if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD)) {
105418b3d906Sdjm 		if (check_sk_options(sk->dev, "uv", &internal_uv) < 0 ||
105518b3d906Sdjm 		    internal_uv != 1) {
105618b3d906Sdjm 			skdebug(__func__, "check_sk_options uv");
105718b3d906Sdjm 			ret = SSH_SK_ERR_PIN_REQUIRED;
10581f63d3c4Sdjm 			goto out;
10591f63d3c4Sdjm 		}
106018b3d906Sdjm 		if ((r = fido_assert_set_uv(assert,
106118b3d906Sdjm 		    FIDO_OPT_TRUE)) != FIDO_OK) {
106218b3d906Sdjm 			skdebug(__func__, "fido_assert_set_uv: %s",
106318b3d906Sdjm 			    fido_strerr(r));
106418b3d906Sdjm 			ret = fidoerr_to_skerr(r);
106518b3d906Sdjm 			goto out;
106618b3d906Sdjm 		}
106718b3d906Sdjm 	}
106801bb7af0Sdjm 	if ((r = fido_dev_get_assert(sk->dev, assert, pin)) != FIDO_OK) {
1069094c80e0Sdjm 		skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
10701f63d3c4Sdjm 		ret = fidoerr_to_skerr(r);
1071094c80e0Sdjm 		goto out;
1072094c80e0Sdjm 	}
1073094c80e0Sdjm 	if ((response = calloc(1, sizeof(*response))) == NULL) {
1074094c80e0Sdjm 		skdebug(__func__, "calloc response failed");
1075094c80e0Sdjm 		goto out;
1076094c80e0Sdjm 	}
1077094c80e0Sdjm 	response->flags = fido_assert_flags(assert, 0);
1078094c80e0Sdjm 	response->counter = fido_assert_sigcount(assert, 0);
1079094c80e0Sdjm 	if (pack_sig(alg, assert, response) != 0) {
1080094c80e0Sdjm 		skdebug(__func__, "pack_sig failed");
1081094c80e0Sdjm 		goto out;
1082094c80e0Sdjm 	}
1083094c80e0Sdjm 	*sign_response = response;
1084094c80e0Sdjm 	response = NULL;
1085094c80e0Sdjm 	ret = 0;
1086094c80e0Sdjm  out:
10873ce2af41Sdjm 	explicit_bzero(message, sizeof(message));
1088a0caf565Sdjm 	free(device);
1089094c80e0Sdjm 	if (response != NULL) {
1090094c80e0Sdjm 		free(response->sig_r);
1091094c80e0Sdjm 		free(response->sig_s);
1092094c80e0Sdjm 		free(response);
1093094c80e0Sdjm 	}
109401bb7af0Sdjm 	sk_close(sk);
1095094c80e0Sdjm 	fido_assert_free(&assert);
1096094c80e0Sdjm 	return ret;
1097094c80e0Sdjm }
10981ac4a90aSdjm 
10991ac4a90aSdjm static int
110001bb7af0Sdjm read_rks(struct sk_usbhid *sk, const char *pin,
11011ac4a90aSdjm     struct sk_resident_key ***rksp, size_t *nrksp)
11021ac4a90aSdjm {
110318b3d906Sdjm 	int ret = SSH_SK_ERR_GENERAL, r = -1, internal_uv;
11041ac4a90aSdjm 	fido_credman_metadata_t *metadata = NULL;
11051ac4a90aSdjm 	fido_credman_rp_t *rp = NULL;
11061ac4a90aSdjm 	fido_credman_rk_t *rk = NULL;
1107991d5a20Sdjm 	size_t i, j, nrp, nrk, user_id_len;
11081ac4a90aSdjm 	const fido_cred_t *cred;
1109991d5a20Sdjm 	const char *rp_id, *rp_name, *user_name;
11101ac4a90aSdjm 	struct sk_resident_key *srk = NULL, **tmp;
1111991d5a20Sdjm 	const u_char *user_id;
11121ac4a90aSdjm 
111301bb7af0Sdjm 	if (pin == NULL) {
111401bb7af0Sdjm 		skdebug(__func__, "no PIN specified");
111501bb7af0Sdjm 		ret = SSH_SK_ERR_PIN_REQUIRED;
111601bb7af0Sdjm 		goto out;
11171ac4a90aSdjm 	}
11181ac4a90aSdjm 	if ((metadata = fido_credman_metadata_new()) == NULL) {
11191ac4a90aSdjm 		skdebug(__func__, "alloc failed");
11201ac4a90aSdjm 		goto out;
11211ac4a90aSdjm 	}
112218b3d906Sdjm 	if (check_sk_options(sk->dev, "uv", &internal_uv) != 0) {
112318b3d906Sdjm 		skdebug(__func__, "check_sk_options failed");
112418b3d906Sdjm 		goto out;
112518b3d906Sdjm 	}
11261ac4a90aSdjm 
112701bb7af0Sdjm 	if ((r = fido_credman_get_dev_metadata(sk->dev, metadata, pin)) != 0) {
11281ac4a90aSdjm 		if (r == FIDO_ERR_INVALID_COMMAND) {
11291ac4a90aSdjm 			skdebug(__func__, "device %s does not support "
113001bb7af0Sdjm 			    "resident keys", sk->path);
1131480af03fSdjm 			ret = 0;
11321ac4a90aSdjm 			goto out;
11331ac4a90aSdjm 		}
11341ac4a90aSdjm 		skdebug(__func__, "get metadata for %s failed: %s",
113501bb7af0Sdjm 		    sk->path, fido_strerr(r));
1136a0caf565Sdjm 		ret = fidoerr_to_skerr(r);
11371ac4a90aSdjm 		goto out;
11381ac4a90aSdjm 	}
11391ac4a90aSdjm 	skdebug(__func__, "existing %llu, remaining %llu",
11401ac4a90aSdjm 	    (unsigned long long)fido_credman_rk_existing(metadata),
11411ac4a90aSdjm 	    (unsigned long long)fido_credman_rk_remaining(metadata));
11421ac4a90aSdjm 	if ((rp = fido_credman_rp_new()) == NULL) {
11431ac4a90aSdjm 		skdebug(__func__, "alloc rp failed");
11441ac4a90aSdjm 		goto out;
11451ac4a90aSdjm 	}
114601bb7af0Sdjm 	if ((r = fido_credman_get_dev_rp(sk->dev, rp, pin)) != 0) {
11471ac4a90aSdjm 		skdebug(__func__, "get RPs for %s failed: %s",
114801bb7af0Sdjm 		    sk->path, fido_strerr(r));
11491ac4a90aSdjm 		goto out;
11501ac4a90aSdjm 	}
11511ac4a90aSdjm 	nrp = fido_credman_rp_count(rp);
11521ac4a90aSdjm 	skdebug(__func__, "Device %s has resident keys for %zu RPs",
115301bb7af0Sdjm 	    sk->path, nrp);
11541ac4a90aSdjm 
11551ac4a90aSdjm 	/* Iterate over RP IDs that have resident keys */
11561ac4a90aSdjm 	for (i = 0; i < nrp; i++) {
1157991d5a20Sdjm 		rp_id = fido_credman_rp_id(rp, i);
1158991d5a20Sdjm 		rp_name = fido_credman_rp_name(rp, i);
11591ac4a90aSdjm 		skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
1160991d5a20Sdjm 		    i, rp_name == NULL ? "(none)" : rp_name,
1161991d5a20Sdjm 		    rp_id == NULL ? "(none)" : rp_id,
11621ac4a90aSdjm 		    fido_credman_rp_id_hash_len(rp, i));
11631ac4a90aSdjm 
11641ac4a90aSdjm 		/* Skip non-SSH RP IDs */
1165991d5a20Sdjm 		if (rp_id == NULL ||
1166991d5a20Sdjm 		    strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
11671ac4a90aSdjm 			continue;
11681ac4a90aSdjm 
11691ac4a90aSdjm 		fido_credman_rk_free(&rk);
11701ac4a90aSdjm 		if ((rk = fido_credman_rk_new()) == NULL) {
11711ac4a90aSdjm 			skdebug(__func__, "alloc rk failed");
11721ac4a90aSdjm 			goto out;
11731ac4a90aSdjm 		}
117401bb7af0Sdjm 		if ((r = fido_credman_get_dev_rk(sk->dev,
117501bb7af0Sdjm 		    fido_credman_rp_id(rp, i), rk, pin)) != 0) {
11761ac4a90aSdjm 			skdebug(__func__, "get RKs for %s slot %zu failed: %s",
117701bb7af0Sdjm 			    sk->path, i, fido_strerr(r));
11781ac4a90aSdjm 			goto out;
11791ac4a90aSdjm 		}
11801ac4a90aSdjm 		nrk = fido_credman_rk_count(rk);
11811ac4a90aSdjm 		skdebug(__func__, "RP \"%s\" has %zu resident keys",
11821ac4a90aSdjm 		    fido_credman_rp_id(rp, i), nrk);
11831ac4a90aSdjm 
11841ac4a90aSdjm 		/* Iterate over resident keys for this RP ID */
11851ac4a90aSdjm 		for (j = 0; j < nrk; j++) {
11861ac4a90aSdjm 			if ((cred = fido_credman_rk(rk, j)) == NULL) {
11871ac4a90aSdjm 				skdebug(__func__, "no RK in slot %zu", j);
11881ac4a90aSdjm 				continue;
11891ac4a90aSdjm 			}
1190991d5a20Sdjm 			if ((user_name = fido_cred_user_name(cred)) == NULL)
1191991d5a20Sdjm 				user_name = "";
1192991d5a20Sdjm 			user_id = fido_cred_user_id_ptr(cred);
1193991d5a20Sdjm 			user_id_len = fido_cred_user_id_len(cred);
1194991d5a20Sdjm 			skdebug(__func__, "Device %s RP \"%s\" user \"%s\" "
1195991d5a20Sdjm 			    "uidlen %zu slot %zu: type %d flags 0x%02x "
1196991d5a20Sdjm 			    "prot 0x%02x", sk->path, rp_id, user_name,
1197991d5a20Sdjm 			    user_id_len, j, fido_cred_type(cred),
11981f63d3c4Sdjm 			    fido_cred_flags(cred), fido_cred_prot(cred));
11991ac4a90aSdjm 
12001ac4a90aSdjm 			/* build response entry */
12011ac4a90aSdjm 			if ((srk = calloc(1, sizeof(*srk))) == NULL ||
12021ac4a90aSdjm 			    (srk->key.key_handle = calloc(1,
12031ac4a90aSdjm 			    fido_cred_id_len(cred))) == NULL ||
1204991d5a20Sdjm 			    (srk->application = strdup(rp_id)) == NULL ||
1205991d5a20Sdjm 			    (user_id_len > 0 &&
1206991d5a20Sdjm 			     (srk->user_id = calloc(1, user_id_len)) == NULL)) {
12071ac4a90aSdjm 				skdebug(__func__, "alloc sk_resident_key");
12081ac4a90aSdjm 				goto out;
12091ac4a90aSdjm 			}
12101ac4a90aSdjm 
12111ac4a90aSdjm 			srk->key.key_handle_len = fido_cred_id_len(cred);
121215a2cdb6Sdjm 			memcpy(srk->key.key_handle, fido_cred_id_ptr(cred),
12131ac4a90aSdjm 			    srk->key.key_handle_len);
1214991d5a20Sdjm 			srk->user_id_len = user_id_len;
1215991d5a20Sdjm 			if (srk->user_id_len != 0)
1216991d5a20Sdjm 				memcpy(srk->user_id, user_id, srk->user_id_len);
12171ac4a90aSdjm 
12181ac4a90aSdjm 			switch (fido_cred_type(cred)) {
12191ac4a90aSdjm 			case COSE_ES256:
1220b0297854Sdjm 				srk->alg = SSH_SK_ECDSA;
12211ac4a90aSdjm 				break;
12221ac4a90aSdjm 			case COSE_EDDSA:
1223b0297854Sdjm 				srk->alg = SSH_SK_ED25519;
12241ac4a90aSdjm 				break;
12251ac4a90aSdjm 			default:
12261ac4a90aSdjm 				skdebug(__func__, "unsupported key type %d",
12271ac4a90aSdjm 				    fido_cred_type(cred));
1228b0297854Sdjm 				goto out; /* XXX free rk and continue */
12291ac4a90aSdjm 			}
12301ac4a90aSdjm 
123118b3d906Sdjm 			if (fido_cred_prot(cred) == FIDO_CRED_PROT_UV_REQUIRED
123218b3d906Sdjm 			    && internal_uv == -1)
123315a2cdb6Sdjm 				srk->flags |=  SSH_SK_USER_VERIFICATION_REQD;
123415a2cdb6Sdjm 
12351ac4a90aSdjm 			if ((r = pack_public_key(srk->alg, cred,
12361ac4a90aSdjm 			    &srk->key)) != 0) {
12371ac4a90aSdjm 				skdebug(__func__, "pack public key failed");
12381ac4a90aSdjm 				goto out;
12391ac4a90aSdjm 			}
12401ac4a90aSdjm 			/* append */
12411ac4a90aSdjm 			if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1,
12421ac4a90aSdjm 			    sizeof(**rksp))) == NULL) {
12431ac4a90aSdjm 				skdebug(__func__, "alloc rksp");
12441ac4a90aSdjm 				goto out;
12451ac4a90aSdjm 			}
12461ac4a90aSdjm 			*rksp = tmp;
12471ac4a90aSdjm 			(*rksp)[(*nrksp)++] = srk;
12481ac4a90aSdjm 			srk = NULL;
12491ac4a90aSdjm 		}
12501ac4a90aSdjm 	}
12511ac4a90aSdjm 	/* Success */
1252480af03fSdjm 	ret = 0;
12531ac4a90aSdjm  out:
12541ac4a90aSdjm 	if (srk != NULL) {
12551ac4a90aSdjm 		free(srk->application);
12561ac4a90aSdjm 		freezero(srk->key.public_key, srk->key.public_key_len);
12571ac4a90aSdjm 		freezero(srk->key.key_handle, srk->key.key_handle_len);
1258991d5a20Sdjm 		freezero(srk->user_id, srk->user_id_len);
12591ac4a90aSdjm 		freezero(srk, sizeof(*srk));
12601ac4a90aSdjm 	}
12611ac4a90aSdjm 	fido_credman_rp_free(&rp);
12621ac4a90aSdjm 	fido_credman_rk_free(&rk);
12631ac4a90aSdjm 	fido_credman_metadata_free(&metadata);
1264480af03fSdjm 	return ret;
12651ac4a90aSdjm }
12661ac4a90aSdjm 
12671ac4a90aSdjm int
1268a0caf565Sdjm sk_load_resident_keys(const char *pin, struct sk_option **options,
12691ac4a90aSdjm     struct sk_resident_key ***rksp, size_t *nrksp)
12701ac4a90aSdjm {
1271480af03fSdjm 	int ret = SSH_SK_ERR_GENERAL, r = -1;
127201bb7af0Sdjm 	size_t i, nrks = 0;
12731ac4a90aSdjm 	struct sk_resident_key **rks = NULL;
127401bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
1275a0caf565Sdjm 	char *device = NULL;
127601bb7af0Sdjm 
12771ac4a90aSdjm 	*rksp = NULL;
12781ac4a90aSdjm 	*nrksp = 0;
12791ac4a90aSdjm 
1280fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
1281fb0a6bcaSdjm 
1282a0caf565Sdjm 	if (check_sign_load_resident_options(options, &device) != 0)
1283a0caf565Sdjm 		goto out; /* error already logged */
128401bb7af0Sdjm 	if (device != NULL)
128501bb7af0Sdjm 		sk = sk_open(device);
128601bb7af0Sdjm 	else
128701bb7af0Sdjm 		sk = sk_probe(NULL, NULL, 0);
128801bb7af0Sdjm 	if (sk == NULL) {
128901bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
129001bb7af0Sdjm 		goto out;
129101bb7af0Sdjm 	}
129201bb7af0Sdjm 	skdebug(__func__, "trying %s", sk->path);
129301bb7af0Sdjm 	if ((r = read_rks(sk, pin, &rks, &nrks)) != 0) {
129401bb7af0Sdjm 		skdebug(__func__, "read_rks failed for %s", sk->path);
1295a0caf565Sdjm 		ret = r;
1296a0caf565Sdjm 		goto out;
1297a0caf565Sdjm 	}
1298a0caf565Sdjm 	/* success, unless we have no keys but a specific error */
1299a0caf565Sdjm 	if (nrks > 0 || ret == SSH_SK_ERR_GENERAL)
1300480af03fSdjm 		ret = 0;
13011ac4a90aSdjm 	*rksp = rks;
13021ac4a90aSdjm 	*nrksp = nrks;
13031ac4a90aSdjm 	rks = NULL;
13041ac4a90aSdjm 	nrks = 0;
13051ac4a90aSdjm  out:
130601bb7af0Sdjm 	sk_close(sk);
13071ac4a90aSdjm 	for (i = 0; i < nrks; i++) {
13081ac4a90aSdjm 		free(rks[i]->application);
13091ac4a90aSdjm 		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
13101ac4a90aSdjm 		freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
1311991d5a20Sdjm 		freezero(rks[i]->user_id, rks[i]->user_id_len);
13121ac4a90aSdjm 		freezero(rks[i], sizeof(*rks[i]));
13131ac4a90aSdjm 	}
13141ac4a90aSdjm 	free(rks);
1315480af03fSdjm 	return ret;
13161ac4a90aSdjm }
13171ac4a90aSdjm 
1318