xref: /openbsd/usr.bin/ssh/sk-usbhid.c (revision f96d262e)
1*f96d262eSdtucker /* $OpenBSD: sk-usbhid.c,v 1.46 2023/03/28 06:12:38 dtucker 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>
25719d3f82Sdjm #include <time.h>
26094c80e0Sdjm 
27f8cd6cb1Snaddy #ifdef WITH_OPENSSL
28094c80e0Sdjm #include <openssl/opensslv.h>
29094c80e0Sdjm #include <openssl/crypto.h>
30094c80e0Sdjm #include <openssl/bn.h>
31094c80e0Sdjm #include <openssl/ec.h>
32094c80e0Sdjm #include <openssl/ecdsa.h>
333ce2af41Sdjm #include <openssl/evp.h>
34f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
35094c80e0Sdjm 
36094c80e0Sdjm #include <fido.h>
371ac4a90aSdjm #include <fido/credman.h>
38094c80e0Sdjm 
39094c80e0Sdjm #ifndef SK_STANDALONE
40094c80e0Sdjm # include "log.h"
41094c80e0Sdjm # include "xmalloc.h"
4201bb7af0Sdjm # include "misc.h"
43b0297854Sdjm /*
44b0297854Sdjm  * If building as part of OpenSSH, then rename exported functions.
45b0297854Sdjm  * This must be done before including sk-api.h.
46b0297854Sdjm  */
47b0297854Sdjm # define sk_api_version		ssh_sk_api_version
48b0297854Sdjm # define sk_enroll		ssh_sk_enroll
49b0297854Sdjm # define sk_sign		ssh_sk_sign
50b0297854Sdjm # define sk_load_resident_keys	ssh_sk_load_resident_keys
51b0297854Sdjm #endif /* !SK_STANDALONE */
52b0297854Sdjm 
53b0297854Sdjm #include "sk-api.h"
54094c80e0Sdjm 
55094c80e0Sdjm /* #define SK_DEBUG 1 */
56094c80e0Sdjm 
57fb0a6bcaSdjm #ifdef SK_DEBUG
58fb0a6bcaSdjm #define SSH_FIDO_INIT_ARG	FIDO_DEBUG
59fb0a6bcaSdjm #else
60fb0a6bcaSdjm #define SSH_FIDO_INIT_ARG	0
61fb0a6bcaSdjm #endif
62fb0a6bcaSdjm 
6301bb7af0Sdjm #define MAX_FIDO_DEVICES	8
6401bb7af0Sdjm #define FIDO_POLL_MS		50
6501bb7af0Sdjm #define SELECT_MS		15000
6601bb7af0Sdjm #define POLL_SLEEP_NS		200000000
67094c80e0Sdjm 
6818b3d906Sdjm #ifndef FIDO_ERR_OPERATION_DENIED
6918b3d906Sdjm #define FIDO_ERR_OPERATION_DENIED 0x27
7018b3d906Sdjm #endif
71094c80e0Sdjm 
7201bb7af0Sdjm struct sk_usbhid {
7301bb7af0Sdjm 	fido_dev_t *dev;
7401bb7af0Sdjm 	char *path;
7501bb7af0Sdjm };
7601bb7af0Sdjm 
77094c80e0Sdjm /* Return the version of the middleware API */
78094c80e0Sdjm uint32_t sk_api_version(void);
79094c80e0Sdjm 
80094c80e0Sdjm /* Enroll a U2F key (private key generation) */
81a0caf565Sdjm int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
822db06755Sdjm     const char *application, uint8_t flags, const char *pin,
83a0caf565Sdjm     struct sk_option **options, struct sk_enroll_response **enroll_response);
84094c80e0Sdjm 
85094c80e0Sdjm /* Sign a challenge */
8674d7b7bdSdjm int sk_sign(uint32_t alg, const uint8_t *data, size_t data_len,
87094c80e0Sdjm     const char *application, const uint8_t *key_handle, size_t key_handle_len,
88a0caf565Sdjm     uint8_t flags, const char *pin, struct sk_option **options,
89a0caf565Sdjm     struct sk_sign_response **sign_response);
90094c80e0Sdjm 
911ac4a90aSdjm /* Load resident keys */
92a0caf565Sdjm int sk_load_resident_keys(const char *pin, struct sk_option **options,
931ac4a90aSdjm     struct sk_resident_key ***rks, size_t *nrks);
941ac4a90aSdjm 
95094c80e0Sdjm static void skdebug(const char *func, const char *fmt, ...)
96094c80e0Sdjm     __attribute__((__format__ (printf, 2, 3)));
97094c80e0Sdjm 
98094c80e0Sdjm static void
skdebug(const char * func,const char * fmt,...)99094c80e0Sdjm skdebug(const char *func, const char *fmt, ...)
100094c80e0Sdjm {
101094c80e0Sdjm #if !defined(SK_STANDALONE)
102094c80e0Sdjm 	char *msg;
103094c80e0Sdjm 	va_list ap;
104094c80e0Sdjm 
105094c80e0Sdjm 	va_start(ap, fmt);
106094c80e0Sdjm 	xvasprintf(&msg, fmt, ap);
107094c80e0Sdjm 	va_end(ap);
10859959935Sdjm 	debug("%s: %s", func, msg);
109094c80e0Sdjm 	free(msg);
110094c80e0Sdjm #elif defined(SK_DEBUG)
111094c80e0Sdjm 	va_list ap;
112094c80e0Sdjm 
113094c80e0Sdjm 	va_start(ap, fmt);
114094c80e0Sdjm 	fprintf(stderr, "%s: ", func);
115094c80e0Sdjm 	vfprintf(stderr, fmt, ap);
116094c80e0Sdjm 	fputc('\n', stderr);
117094c80e0Sdjm 	va_end(ap);
118094c80e0Sdjm #else
119094c80e0Sdjm 	(void)func; /* XXX */
120094c80e0Sdjm 	(void)fmt; /* XXX */
121094c80e0Sdjm #endif
122094c80e0Sdjm }
123094c80e0Sdjm 
124094c80e0Sdjm uint32_t
sk_api_version(void)125094c80e0Sdjm sk_api_version(void)
126094c80e0Sdjm {
127b0297854Sdjm 	return SSH_SK_VERSION_MAJOR;
128094c80e0Sdjm }
129094c80e0Sdjm 
13001bb7af0Sdjm static struct sk_usbhid *
sk_open(const char * path)13101bb7af0Sdjm sk_open(const char *path)
132094c80e0Sdjm {
13301bb7af0Sdjm 	struct sk_usbhid *sk;
134094c80e0Sdjm 	int r;
135094c80e0Sdjm 
13601bb7af0Sdjm 	if (path == NULL) {
13701bb7af0Sdjm 		skdebug(__func__, "path == NULL");
13801bb7af0Sdjm 		return NULL;
139094c80e0Sdjm 	}
14001bb7af0Sdjm 	if ((sk = calloc(1, sizeof(*sk))) == NULL) {
14101bb7af0Sdjm 		skdebug(__func__, "calloc sk failed");
14201bb7af0Sdjm 		return NULL;
14301bb7af0Sdjm 	}
14401bb7af0Sdjm 	if ((sk->path = strdup(path)) == NULL) {
14501bb7af0Sdjm 		skdebug(__func__, "strdup path failed");
14601bb7af0Sdjm 		free(sk);
14701bb7af0Sdjm 		return NULL;
14801bb7af0Sdjm 	}
14901bb7af0Sdjm 	if ((sk->dev = fido_dev_new()) == NULL) {
15001bb7af0Sdjm 		skdebug(__func__, "fido_dev_new failed");
15101bb7af0Sdjm 		free(sk->path);
15201bb7af0Sdjm 		free(sk);
15301bb7af0Sdjm 		return NULL;
15401bb7af0Sdjm 	}
15501bb7af0Sdjm 	if ((r = fido_dev_open(sk->dev, sk->path)) != FIDO_OK) {
15601bb7af0Sdjm 		skdebug(__func__, "fido_dev_open %s failed: %s", sk->path,
157094c80e0Sdjm 		    fido_strerr(r));
15801bb7af0Sdjm 		fido_dev_free(&sk->dev);
15901bb7af0Sdjm 		free(sk->path);
16001bb7af0Sdjm 		free(sk);
16101bb7af0Sdjm 		return NULL;
162094c80e0Sdjm 	}
16301bb7af0Sdjm 	return sk;
164094c80e0Sdjm }
165094c80e0Sdjm 
16601bb7af0Sdjm static void
sk_close(struct sk_usbhid * sk)16701bb7af0Sdjm sk_close(struct sk_usbhid *sk)
16801bb7af0Sdjm {
16901bb7af0Sdjm 	if (sk == NULL)
17001bb7af0Sdjm 		return;
17101bb7af0Sdjm 	fido_dev_cancel(sk->dev); /* cancel any pending operation */
17201bb7af0Sdjm 	fido_dev_close(sk->dev);
17301bb7af0Sdjm 	fido_dev_free(&sk->dev);
17401bb7af0Sdjm 	free(sk->path);
17501bb7af0Sdjm 	free(sk);
17601bb7af0Sdjm }
17701bb7af0Sdjm 
17801bb7af0Sdjm static struct sk_usbhid **
sk_openv(const fido_dev_info_t * devlist,size_t ndevs,size_t * nopen)17901bb7af0Sdjm sk_openv(const fido_dev_info_t *devlist, size_t ndevs, size_t *nopen)
18001bb7af0Sdjm {
18101bb7af0Sdjm 	const fido_dev_info_t *di;
18201bb7af0Sdjm 	struct sk_usbhid **skv;
18301bb7af0Sdjm 	size_t i;
18401bb7af0Sdjm 
18501bb7af0Sdjm 	*nopen = 0;
18601bb7af0Sdjm 	if ((skv = calloc(ndevs, sizeof(*skv))) == NULL) {
18701bb7af0Sdjm 		skdebug(__func__, "calloc skv failed");
18801bb7af0Sdjm 		return NULL;
18901bb7af0Sdjm 	}
19001bb7af0Sdjm 	for (i = 0; i < ndevs; i++) {
19101bb7af0Sdjm 		if ((di = fido_dev_info_ptr(devlist, i)) == NULL)
19201bb7af0Sdjm 			skdebug(__func__, "fido_dev_info_ptr failed");
19301bb7af0Sdjm 		else if ((skv[*nopen] = sk_open(fido_dev_info_path(di))) == NULL)
19401bb7af0Sdjm 			skdebug(__func__, "sk_open failed");
19501bb7af0Sdjm 		else
19601bb7af0Sdjm 			(*nopen)++;
19701bb7af0Sdjm 	}
19801bb7af0Sdjm 	if (*nopen == 0) {
19901bb7af0Sdjm 		for (i = 0; i < ndevs; i++)
20001bb7af0Sdjm 			sk_close(skv[i]);
20101bb7af0Sdjm 		free(skv);
20201bb7af0Sdjm 		skv = NULL;
20301bb7af0Sdjm 	}
20401bb7af0Sdjm 
20501bb7af0Sdjm 	return skv;
20601bb7af0Sdjm }
20701bb7af0Sdjm 
20801bb7af0Sdjm static void
sk_closev(struct sk_usbhid ** skv,size_t nsk)20901bb7af0Sdjm sk_closev(struct sk_usbhid **skv, size_t nsk)
21001bb7af0Sdjm {
21101bb7af0Sdjm 	size_t i;
21201bb7af0Sdjm 
21301bb7af0Sdjm 	for (i = 0; i < nsk; i++)
21401bb7af0Sdjm 		sk_close(skv[i]);
21501bb7af0Sdjm 	free(skv);
21601bb7af0Sdjm }
21701bb7af0Sdjm 
218094c80e0Sdjm static int
sk_touch_begin(struct sk_usbhid ** skv,size_t nsk)21901bb7af0Sdjm sk_touch_begin(struct sk_usbhid **skv, size_t nsk)
22001bb7af0Sdjm {
22101bb7af0Sdjm 	size_t i, ok = 0;
22201bb7af0Sdjm 	int r;
22301bb7af0Sdjm 
22401bb7af0Sdjm 	for (i = 0; i < nsk; i++)
22501bb7af0Sdjm 		if ((r = fido_dev_get_touch_begin(skv[i]->dev)) != FIDO_OK)
22601bb7af0Sdjm 			skdebug(__func__, "fido_dev_get_touch_begin %s failed:"
22701bb7af0Sdjm 			    " %s", skv[i]->path, fido_strerr(r));
22801bb7af0Sdjm 		else
22901bb7af0Sdjm 			ok++;
23001bb7af0Sdjm 
23101bb7af0Sdjm 	return ok ? 0 : -1;
23201bb7af0Sdjm }
23301bb7af0Sdjm 
23401bb7af0Sdjm static int
sk_touch_poll(struct sk_usbhid ** skv,size_t nsk,int * touch,size_t * idx)23501bb7af0Sdjm sk_touch_poll(struct sk_usbhid **skv, size_t nsk, int *touch, size_t *idx)
23601bb7af0Sdjm {
23701bb7af0Sdjm 	struct timespec ts_pause;
23801bb7af0Sdjm 	size_t npoll, i;
23901bb7af0Sdjm 	int r;
24001bb7af0Sdjm 
24101bb7af0Sdjm 	ts_pause.tv_sec = 0;
24201bb7af0Sdjm 	ts_pause.tv_nsec = POLL_SLEEP_NS;
24301bb7af0Sdjm 	nanosleep(&ts_pause, NULL);
24401bb7af0Sdjm 	npoll = nsk;
24501bb7af0Sdjm 	for (i = 0; i < nsk; i++) {
24601bb7af0Sdjm 		if (skv[i] == NULL)
24701bb7af0Sdjm 			continue; /* device discarded */
24801bb7af0Sdjm 		skdebug(__func__, "polling %s", skv[i]->path);
24901bb7af0Sdjm 		if ((r = fido_dev_get_touch_status(skv[i]->dev, touch,
25001bb7af0Sdjm 		    FIDO_POLL_MS)) != FIDO_OK) {
25101bb7af0Sdjm 			skdebug(__func__, "fido_dev_get_touch_status %s: %s",
25201bb7af0Sdjm 			    skv[i]->path, fido_strerr(r));
25301bb7af0Sdjm 			sk_close(skv[i]); /* discard device */
25401bb7af0Sdjm 			skv[i] = NULL;
25501bb7af0Sdjm 			if (--npoll == 0) {
25601bb7af0Sdjm 				skdebug(__func__, "no device left to poll");
25701bb7af0Sdjm 				return -1;
25801bb7af0Sdjm 			}
25901bb7af0Sdjm 		} else if (*touch) {
26001bb7af0Sdjm 			*idx = i;
26101bb7af0Sdjm 			return 0;
26201bb7af0Sdjm 		}
26301bb7af0Sdjm 	}
26401bb7af0Sdjm 	*touch = 0;
26501bb7af0Sdjm 	return 0;
26601bb7af0Sdjm }
26701bb7af0Sdjm 
26801bb7af0Sdjm /* Check if the specified key handle exists on a given sk. */
26901bb7af0Sdjm static int
sk_try(const struct sk_usbhid * sk,const char * application,const uint8_t * key_handle,size_t key_handle_len)27001bb7af0Sdjm sk_try(const struct sk_usbhid *sk, const char *application,
27101bb7af0Sdjm     const uint8_t *key_handle, size_t key_handle_len)
272094c80e0Sdjm {
273094c80e0Sdjm 	fido_assert_t *assert = NULL;
274094c80e0Sdjm 	int r = FIDO_ERR_INTERNAL;
275b4f4fe96Sdjm 	uint8_t message[32];
276094c80e0Sdjm 
277b4f4fe96Sdjm 	memset(message, '\0', sizeof(message));
278094c80e0Sdjm 	if ((assert = fido_assert_new()) == NULL) {
279094c80e0Sdjm 		skdebug(__func__, "fido_assert_new failed");
280094c80e0Sdjm 		goto out;
281094c80e0Sdjm 	}
282b4f4fe96Sdjm 	/* generate an invalid signature on FIDO2 tokens */
283b4f4fe96Sdjm 	if ((r = fido_assert_set_clientdata(assert, message,
28401bb7af0Sdjm 	    sizeof(message))) != FIDO_OK) {
2854fe47877Sdjm 		skdebug(__func__, "fido_assert_set_clientdata: %s",
286094c80e0Sdjm 		    fido_strerr(r));
287094c80e0Sdjm 		goto out;
288094c80e0Sdjm 	}
289094c80e0Sdjm 	if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
290094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
291094c80e0Sdjm 		goto out;
292094c80e0Sdjm 	}
293094c80e0Sdjm 	if ((r = fido_assert_allow_cred(assert, key_handle,
294094c80e0Sdjm 	    key_handle_len)) != FIDO_OK) {
295094c80e0Sdjm 		skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
296094c80e0Sdjm 		goto out;
297094c80e0Sdjm 	}
298094c80e0Sdjm 	if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) {
299094c80e0Sdjm 		skdebug(__func__, "fido_assert_up: %s", fido_strerr(r));
300094c80e0Sdjm 		goto out;
301094c80e0Sdjm 	}
30201bb7af0Sdjm 	r = fido_dev_get_assert(sk->dev, assert, NULL);
303094c80e0Sdjm 	skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
3047cf8e58dSdjm 	if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) {
3057cf8e58dSdjm 		/* U2F tokens may return this */
3067cf8e58dSdjm 		r = FIDO_OK;
3077cf8e58dSdjm 	}
308094c80e0Sdjm  out:
309094c80e0Sdjm 	fido_assert_free(&assert);
310094c80e0Sdjm 
311094c80e0Sdjm 	return r != FIDO_OK ? -1 : 0;
312094c80e0Sdjm }
313094c80e0Sdjm 
314c5365f21Sdjm static int
check_sk_options(fido_dev_t * dev,const char * opt,int * ret)315c5365f21Sdjm check_sk_options(fido_dev_t *dev, const char *opt, int *ret)
316c5365f21Sdjm {
317c5365f21Sdjm 	fido_cbor_info_t *info;
318c5365f21Sdjm 	char * const *name;
319c5365f21Sdjm 	const bool *value;
320c5365f21Sdjm 	size_t len, i;
321c5365f21Sdjm 	int r;
322c5365f21Sdjm 
323c5365f21Sdjm 	*ret = -1;
324c5365f21Sdjm 
325c5365f21Sdjm 	if (!fido_dev_is_fido2(dev)) {
326c5365f21Sdjm 		skdebug(__func__, "device is not fido2");
327c5365f21Sdjm 		return 0;
328c5365f21Sdjm 	}
329c5365f21Sdjm 	if ((info = fido_cbor_info_new()) == NULL) {
330c5365f21Sdjm 		skdebug(__func__, "fido_cbor_info_new failed");
331c5365f21Sdjm 		return -1;
332c5365f21Sdjm 	}
333c5365f21Sdjm 	if ((r = fido_dev_get_cbor_info(dev, info)) != FIDO_OK) {
334c5365f21Sdjm 		skdebug(__func__, "fido_dev_get_cbor_info: %s", fido_strerr(r));
335c5365f21Sdjm 		fido_cbor_info_free(&info);
336c5365f21Sdjm 		return -1;
337c5365f21Sdjm 	}
338c5365f21Sdjm 	name = fido_cbor_info_options_name_ptr(info);
339c5365f21Sdjm 	value = fido_cbor_info_options_value_ptr(info);
340c5365f21Sdjm 	len = fido_cbor_info_options_len(info);
341c5365f21Sdjm 	for (i = 0; i < len; i++) {
342c5365f21Sdjm 		if (!strcmp(name[i], opt)) {
343c5365f21Sdjm 			*ret = value[i];
344c5365f21Sdjm 			break;
345c5365f21Sdjm 		}
346c5365f21Sdjm 	}
347c5365f21Sdjm 	fido_cbor_info_free(&info);
348c5365f21Sdjm 	if (*ret == -1)
349c5365f21Sdjm 		skdebug(__func__, "option %s is unknown", opt);
350c5365f21Sdjm 	else
351c5365f21Sdjm 		skdebug(__func__, "option %s is %s", opt, *ret ? "on" : "off");
352c5365f21Sdjm 
353c5365f21Sdjm 	return 0;
354c5365f21Sdjm }
355c5365f21Sdjm 
35601bb7af0Sdjm static struct sk_usbhid *
sk_select_by_cred(const fido_dev_info_t * devlist,size_t ndevs,const char * application,const uint8_t * key_handle,size_t key_handle_len)35701bb7af0Sdjm sk_select_by_cred(const fido_dev_info_t *devlist, size_t ndevs,
35801bb7af0Sdjm     const char *application, const uint8_t *key_handle, size_t key_handle_len)
359094c80e0Sdjm {
36001bb7af0Sdjm 	struct sk_usbhid **skv, *sk;
36101bb7af0Sdjm 	size_t skvcnt, i;
362b8914b02Sdjm 	int internal_uv;
363094c80e0Sdjm 
36401bb7af0Sdjm 	if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
36501bb7af0Sdjm 		skdebug(__func__, "sk_openv failed");
366a0caf565Sdjm 		return NULL;
367a0caf565Sdjm 	}
368b8914b02Sdjm 	if (skvcnt == 1 && check_sk_options(skv[0]->dev, "uv",
369b8914b02Sdjm 	    &internal_uv) == 0 && internal_uv != -1) {
3708c966c57Sdjm 		sk = skv[0];
3718c966c57Sdjm 		skv[0] = NULL;
3728c966c57Sdjm 		goto out;
3738c966c57Sdjm 	}
37401bb7af0Sdjm 	sk = NULL;
3758c966c57Sdjm 	for (i = 0; i < skvcnt; i++) {
37601bb7af0Sdjm 		if (sk_try(skv[i], application, key_handle,
37701bb7af0Sdjm 		    key_handle_len) == 0) {
37801bb7af0Sdjm 			sk = skv[i];
37901bb7af0Sdjm 			skv[i] = NULL;
38001bb7af0Sdjm 			skdebug(__func__, "found key in %s", sk->path);
38101bb7af0Sdjm 			break;
38201bb7af0Sdjm 		}
3838c966c57Sdjm 	}
3848c966c57Sdjm  out:
38501bb7af0Sdjm 	sk_closev(skv, skvcnt);
38601bb7af0Sdjm 	return sk;
38701bb7af0Sdjm }
38801bb7af0Sdjm 
38901bb7af0Sdjm static struct sk_usbhid *
sk_select_by_touch(const fido_dev_info_t * devlist,size_t ndevs)39001bb7af0Sdjm sk_select_by_touch(const fido_dev_info_t *devlist, size_t ndevs)
39101bb7af0Sdjm {
39201bb7af0Sdjm 	struct sk_usbhid **skv, *sk;
39301bb7af0Sdjm 	struct timeval tv_start, tv_now, tv_delta;
39401bb7af0Sdjm 	size_t skvcnt, idx;
39501bb7af0Sdjm 	int touch, ms_remain;
39601bb7af0Sdjm 
39701bb7af0Sdjm 	if ((skv = sk_openv(devlist, ndevs, &skvcnt)) == NULL) {
39801bb7af0Sdjm 		skdebug(__func__, "sk_openv failed");
399a0caf565Sdjm 		return NULL;
400a0caf565Sdjm 	}
40101bb7af0Sdjm 	sk = NULL;
40201bb7af0Sdjm 	if (skvcnt < 2) {
40301bb7af0Sdjm 		if (skvcnt == 1) {
40401bb7af0Sdjm 			/* single candidate */
40501bb7af0Sdjm 			sk = skv[0];
40601bb7af0Sdjm 			skv[0] = NULL;
407a0caf565Sdjm 		}
40801bb7af0Sdjm 		goto out;
40901bb7af0Sdjm 	}
41001bb7af0Sdjm 	if (sk_touch_begin(skv, skvcnt) == -1) {
41101bb7af0Sdjm 		skdebug(__func__, "sk_touch_begin failed");
41201bb7af0Sdjm 		goto out;
41301bb7af0Sdjm 	}
41401bb7af0Sdjm 	monotime_tv(&tv_start);
41501bb7af0Sdjm 	do {
41601bb7af0Sdjm 		if (sk_touch_poll(skv, skvcnt, &touch, &idx) == -1) {
41701bb7af0Sdjm 			skdebug(__func__, "sk_touch_poll failed");
41801bb7af0Sdjm 			goto out;
41901bb7af0Sdjm 		}
42001bb7af0Sdjm 		if (touch) {
42101bb7af0Sdjm 			sk = skv[idx];
42201bb7af0Sdjm 			skv[idx] = NULL;
42301bb7af0Sdjm 			goto out;
42401bb7af0Sdjm 		}
42501bb7af0Sdjm 		monotime_tv(&tv_now);
42601bb7af0Sdjm 		timersub(&tv_now, &tv_start, &tv_delta);
42701bb7af0Sdjm 		ms_remain = SELECT_MS - tv_delta.tv_sec * 1000 -
42801bb7af0Sdjm 		    tv_delta.tv_usec / 1000;
42901bb7af0Sdjm 	} while (ms_remain >= FIDO_POLL_MS);
43001bb7af0Sdjm 	skdebug(__func__, "timeout");
43101bb7af0Sdjm out:
43201bb7af0Sdjm 	sk_closev(skv, skvcnt);
43301bb7af0Sdjm 	return sk;
43401bb7af0Sdjm }
43501bb7af0Sdjm 
43601bb7af0Sdjm static struct sk_usbhid *
sk_probe(const char * application,const uint8_t * key_handle,size_t key_handle_len,int probe_resident)43701bb7af0Sdjm sk_probe(const char *application, const uint8_t *key_handle,
4385814c2aaSdjm     size_t key_handle_len, int probe_resident)
43901bb7af0Sdjm {
44001bb7af0Sdjm 	struct sk_usbhid *sk;
44101bb7af0Sdjm 	fido_dev_info_t *devlist;
44201bb7af0Sdjm 	size_t ndevs;
44301bb7af0Sdjm 	int r;
444a0caf565Sdjm 
445094c80e0Sdjm 	if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) {
446094c80e0Sdjm 		skdebug(__func__, "fido_dev_info_new failed");
44701bb7af0Sdjm 		return NULL;
448094c80e0Sdjm 	}
449094c80e0Sdjm 	if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES,
45001bb7af0Sdjm 	    &ndevs)) != FIDO_OK) {
45101bb7af0Sdjm 		skdebug(__func__, "fido_dev_info_manifest failed: %s",
45201bb7af0Sdjm 		    fido_strerr(r));
453094c80e0Sdjm 		fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
45401bb7af0Sdjm 		return NULL;
45501bb7af0Sdjm 	}
45601bb7af0Sdjm 	skdebug(__func__, "%zu device(s) detected", ndevs);
45701bb7af0Sdjm 	if (ndevs == 0) {
45801bb7af0Sdjm 		sk = NULL;
45901bb7af0Sdjm 	} else if (application != NULL && key_handle != NULL) {
46001bb7af0Sdjm 		skdebug(__func__, "selecting sk by cred");
46101bb7af0Sdjm 		sk = sk_select_by_cred(devlist, ndevs, application, key_handle,
46201bb7af0Sdjm 		    key_handle_len);
46301bb7af0Sdjm 	} else {
46401bb7af0Sdjm 		skdebug(__func__, "selecting sk by touch");
46501bb7af0Sdjm 		sk = sk_select_by_touch(devlist, ndevs);
46601bb7af0Sdjm 	}
46701bb7af0Sdjm 	fido_dev_info_free(&devlist, MAX_FIDO_DEVICES);
46801bb7af0Sdjm 	return sk;
469094c80e0Sdjm }
470094c80e0Sdjm 
471f8cd6cb1Snaddy #ifdef WITH_OPENSSL
472094c80e0Sdjm /*
473094c80e0Sdjm  * The key returned via fido_cred_pubkey_ptr() is in affine coordinates,
474094c80e0Sdjm  * but the API expects a SEC1 octet string.
475094c80e0Sdjm  */
476094c80e0Sdjm static int
pack_public_key_ecdsa(const fido_cred_t * cred,struct sk_enroll_response * response)4771ac4a90aSdjm pack_public_key_ecdsa(const fido_cred_t *cred,
4781ac4a90aSdjm     struct sk_enroll_response *response)
479094c80e0Sdjm {
480094c80e0Sdjm 	const uint8_t *ptr;
481094c80e0Sdjm 	BIGNUM *x = NULL, *y = NULL;
482094c80e0Sdjm 	EC_POINT *q = NULL;
483094c80e0Sdjm 	EC_GROUP *g = NULL;
484094c80e0Sdjm 	int ret = -1;
485094c80e0Sdjm 
486094c80e0Sdjm 	response->public_key = NULL;
487094c80e0Sdjm 	response->public_key_len = 0;
488094c80e0Sdjm 
48907b718edSdjm 	if ((x = BN_new()) == NULL ||
49007b718edSdjm 	    (y = BN_new()) == NULL ||
491094c80e0Sdjm 	    (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL ||
492094c80e0Sdjm 	    (q = EC_POINT_new(g)) == NULL) {
493094c80e0Sdjm 		skdebug(__func__, "libcrypto setup failed");
494094c80e0Sdjm 		goto out;
495094c80e0Sdjm 	}
496094c80e0Sdjm 	if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
497094c80e0Sdjm 		skdebug(__func__, "fido_cred_pubkey_ptr failed");
498094c80e0Sdjm 		goto out;
499094c80e0Sdjm 	}
500094c80e0Sdjm 	if (fido_cred_pubkey_len(cred) != 64) {
501094c80e0Sdjm 		skdebug(__func__, "bad fido_cred_pubkey_len %zu",
502094c80e0Sdjm 		    fido_cred_pubkey_len(cred));
503094c80e0Sdjm 		goto out;
504094c80e0Sdjm 	}
505094c80e0Sdjm 
506094c80e0Sdjm 	if (BN_bin2bn(ptr, 32, x) == NULL ||
507094c80e0Sdjm 	    BN_bin2bn(ptr + 32, 32, y) == NULL) {
508094c80e0Sdjm 		skdebug(__func__, "BN_bin2bn failed");
509094c80e0Sdjm 		goto out;
510094c80e0Sdjm 	}
51107b718edSdjm 	if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) {
512094c80e0Sdjm 		skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed");
513094c80e0Sdjm 		goto out;
514094c80e0Sdjm 	}
515094c80e0Sdjm 	response->public_key_len = EC_POINT_point2oct(g, q,
51607b718edSdjm 	    POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL);
517094c80e0Sdjm 	if (response->public_key_len == 0 || response->public_key_len > 2048) {
518094c80e0Sdjm 		skdebug(__func__, "bad pubkey length %zu",
519094c80e0Sdjm 		    response->public_key_len);
520094c80e0Sdjm 		goto out;
521094c80e0Sdjm 	}
522094c80e0Sdjm 	if ((response->public_key = malloc(response->public_key_len)) == NULL) {
523094c80e0Sdjm 		skdebug(__func__, "malloc pubkey failed");
524094c80e0Sdjm 		goto out;
525094c80e0Sdjm 	}
526094c80e0Sdjm 	if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED,
52707b718edSdjm 	    response->public_key, response->public_key_len, NULL) == 0) {
528094c80e0Sdjm 		skdebug(__func__, "EC_POINT_point2oct failed");
529094c80e0Sdjm 		goto out;
530094c80e0Sdjm 	}
531094c80e0Sdjm 	/* success */
532094c80e0Sdjm 	ret = 0;
533094c80e0Sdjm  out:
534094c80e0Sdjm 	if (ret != 0 && response->public_key != NULL) {
535094c80e0Sdjm 		memset(response->public_key, 0, response->public_key_len);
536094c80e0Sdjm 		free(response->public_key);
537094c80e0Sdjm 		response->public_key = NULL;
538094c80e0Sdjm 	}
539094c80e0Sdjm 	EC_POINT_free(q);
540094c80e0Sdjm 	EC_GROUP_free(g);
54107b718edSdjm 	BN_clear_free(x);
54207b718edSdjm 	BN_clear_free(y);
543094c80e0Sdjm 	return ret;
544094c80e0Sdjm }
545f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
546094c80e0Sdjm 
547094c80e0Sdjm static int
pack_public_key_ed25519(const fido_cred_t * cred,struct sk_enroll_response * response)5481ac4a90aSdjm pack_public_key_ed25519(const fido_cred_t *cred,
5491ac4a90aSdjm     struct sk_enroll_response *response)
550094c80e0Sdjm {
551094c80e0Sdjm 	const uint8_t *ptr;
552094c80e0Sdjm 	size_t len;
553094c80e0Sdjm 	int ret = -1;
554094c80e0Sdjm 
555094c80e0Sdjm 	response->public_key = NULL;
556094c80e0Sdjm 	response->public_key_len = 0;
557094c80e0Sdjm 
558094c80e0Sdjm 	if ((len = fido_cred_pubkey_len(cred)) != 32) {
559094c80e0Sdjm 		skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len);
560094c80e0Sdjm 		goto out;
561094c80e0Sdjm 	}
562094c80e0Sdjm 	if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) {
563094c80e0Sdjm 		skdebug(__func__, "fido_cred_pubkey_ptr failed");
564094c80e0Sdjm 		goto out;
565094c80e0Sdjm 	}
566094c80e0Sdjm 	response->public_key_len = len;
567094c80e0Sdjm 	if ((response->public_key = malloc(response->public_key_len)) == NULL) {
568094c80e0Sdjm 		skdebug(__func__, "malloc pubkey failed");
569094c80e0Sdjm 		goto out;
570094c80e0Sdjm 	}
571094c80e0Sdjm 	memcpy(response->public_key, ptr, len);
572094c80e0Sdjm 	ret = 0;
573094c80e0Sdjm  out:
574094c80e0Sdjm 	if (ret != 0)
575094c80e0Sdjm 		free(response->public_key);
576094c80e0Sdjm 	return ret;
577094c80e0Sdjm }
578094c80e0Sdjm 
579094c80e0Sdjm static int
pack_public_key(uint32_t alg,const fido_cred_t * cred,struct sk_enroll_response * response)580a0caf565Sdjm pack_public_key(uint32_t alg, const fido_cred_t *cred,
5811ac4a90aSdjm     struct sk_enroll_response *response)
582094c80e0Sdjm {
583094c80e0Sdjm 	switch(alg) {
584f8cd6cb1Snaddy #ifdef WITH_OPENSSL
585b0297854Sdjm 	case SSH_SK_ECDSA:
586094c80e0Sdjm 		return pack_public_key_ecdsa(cred, response);
587f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
588b0297854Sdjm 	case SSH_SK_ED25519:
589094c80e0Sdjm 		return pack_public_key_ed25519(cred, response);
590094c80e0Sdjm 	default:
591094c80e0Sdjm 		return -1;
592094c80e0Sdjm 	}
593094c80e0Sdjm }
594094c80e0Sdjm 
595480af03fSdjm static int
fidoerr_to_skerr(int fidoerr)596480af03fSdjm fidoerr_to_skerr(int fidoerr)
597480af03fSdjm {
598480af03fSdjm 	switch (fidoerr) {
599480af03fSdjm 	case FIDO_ERR_UNSUPPORTED_OPTION:
600b0297854Sdjm 	case FIDO_ERR_UNSUPPORTED_ALGORITHM:
601480af03fSdjm 		return SSH_SK_ERR_UNSUPPORTED;
602480af03fSdjm 	case FIDO_ERR_PIN_REQUIRED:
603480af03fSdjm 	case FIDO_ERR_PIN_INVALID:
60418b3d906Sdjm 	case FIDO_ERR_OPERATION_DENIED:
605480af03fSdjm 		return SSH_SK_ERR_PIN_REQUIRED;
606480af03fSdjm 	default:
607480af03fSdjm 		return -1;
608480af03fSdjm 	}
609480af03fSdjm }
610480af03fSdjm 
611a0caf565Sdjm static int
check_enroll_options(struct sk_option ** options,char ** devicep,uint8_t * user_id,size_t user_id_len)612a0caf565Sdjm check_enroll_options(struct sk_option **options, char **devicep,
613a0caf565Sdjm     uint8_t *user_id, size_t user_id_len)
614a0caf565Sdjm {
615a0caf565Sdjm 	size_t i;
616a0caf565Sdjm 
617a0caf565Sdjm 	if (options == NULL)
618a0caf565Sdjm 		return 0;
619a0caf565Sdjm 	for (i = 0; options[i] != NULL; i++) {
620a0caf565Sdjm 		if (strcmp(options[i]->name, "device") == 0) {
621a0caf565Sdjm 			if ((*devicep = strdup(options[i]->value)) == NULL) {
622a0caf565Sdjm 				skdebug(__func__, "strdup device failed");
623a0caf565Sdjm 				return -1;
624a0caf565Sdjm 			}
625a0caf565Sdjm 			skdebug(__func__, "requested device %s", *devicep);
6261a9bfc1cSdjm 		} else if (strcmp(options[i]->name, "user") == 0) {
627a0caf565Sdjm 			if (strlcpy(user_id, options[i]->value, user_id_len) >=
628a0caf565Sdjm 			    user_id_len) {
629a0caf565Sdjm 				skdebug(__func__, "user too long");
630a0caf565Sdjm 				return -1;
631a0caf565Sdjm 			}
632a0caf565Sdjm 			skdebug(__func__, "requested user %s",
633a0caf565Sdjm 			    (char *)user_id);
634a0caf565Sdjm 		} else {
635a0caf565Sdjm 			skdebug(__func__, "requested unsupported option %s",
636a0caf565Sdjm 			    options[i]->name);
637a0caf565Sdjm 			if (options[i]->required) {
638a0caf565Sdjm 				skdebug(__func__, "unknown required option");
639a0caf565Sdjm 				return -1;
640a0caf565Sdjm 			}
641a0caf565Sdjm 		}
642a0caf565Sdjm 	}
643a0caf565Sdjm 	return 0;
644a0caf565Sdjm }
645a0caf565Sdjm 
6464fe47877Sdjm static int
key_lookup(fido_dev_t * dev,const char * application,const uint8_t * user_id,size_t user_id_len,const char * pin)6474fe47877Sdjm key_lookup(fido_dev_t *dev, const char *application, const uint8_t *user_id,
6484fe47877Sdjm     size_t user_id_len, const char *pin)
6494fe47877Sdjm {
6504fe47877Sdjm 	fido_assert_t *assert = NULL;
6514fe47877Sdjm 	uint8_t message[32];
6524fe47877Sdjm 	int r = FIDO_ERR_INTERNAL;
653e67f83d8Sdjm 	int sk_supports_uv, uv;
6544fe47877Sdjm 	size_t i;
6554fe47877Sdjm 
6564fe47877Sdjm 	memset(message, '\0', sizeof(message));
6574fe47877Sdjm 	if ((assert = fido_assert_new()) == NULL) {
6584fe47877Sdjm 		skdebug(__func__, "fido_assert_new failed");
6594fe47877Sdjm 		goto out;
6604fe47877Sdjm 	}
6614fe47877Sdjm 	/* generate an invalid signature on FIDO2 tokens */
6624fe47877Sdjm 	if ((r = fido_assert_set_clientdata(assert, message,
6634fe47877Sdjm 	    sizeof(message))) != FIDO_OK) {
6644fe47877Sdjm 		skdebug(__func__, "fido_assert_set_clientdata: %s",
6654fe47877Sdjm 		    fido_strerr(r));
6664fe47877Sdjm 		goto out;
6674fe47877Sdjm 	}
6684fe47877Sdjm 	if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
6694fe47877Sdjm 		skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
6704fe47877Sdjm 		goto out;
6714fe47877Sdjm 	}
6724fe47877Sdjm 	if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) {
673e67f83d8Sdjm 		skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r));
674e67f83d8Sdjm 		goto out;
675e67f83d8Sdjm 	}
676e67f83d8Sdjm 	uv = FIDO_OPT_OMIT;
677e67f83d8Sdjm 	if (pin == NULL && check_sk_options(dev, "uv", &sk_supports_uv) == 0 &&
678e67f83d8Sdjm 	    sk_supports_uv != -1)
679e67f83d8Sdjm 		uv = FIDO_OPT_TRUE;
680e67f83d8Sdjm 	if ((r = fido_assert_set_uv(assert, uv)) != FIDO_OK) {
681e67f83d8Sdjm 		skdebug(__func__, "fido_assert_set_uv: %s", fido_strerr(r));
6824fe47877Sdjm 		goto out;
6834fe47877Sdjm 	}
6844fe47877Sdjm 	if ((r = fido_dev_get_assert(dev, assert, pin)) != FIDO_OK) {
6854fe47877Sdjm 		skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
6864fe47877Sdjm 		goto out;
6874fe47877Sdjm 	}
6884fe47877Sdjm 	r = FIDO_ERR_NO_CREDENTIALS;
6894fe47877Sdjm 	skdebug(__func__, "%zu signatures returned", fido_assert_count(assert));
6904fe47877Sdjm 	for (i = 0; i < fido_assert_count(assert); i++) {
6914fe47877Sdjm 		if (fido_assert_user_id_len(assert, i) == user_id_len &&
6924fe47877Sdjm 		    memcmp(fido_assert_user_id_ptr(assert, i), user_id,
6934fe47877Sdjm 		    user_id_len) == 0) {
6944fe47877Sdjm 			skdebug(__func__, "credential exists");
6954fe47877Sdjm 			r = FIDO_OK;
6964fe47877Sdjm 			goto out;
6974fe47877Sdjm 		}
6984fe47877Sdjm 	}
6994fe47877Sdjm  out:
7004fe47877Sdjm 	fido_assert_free(&assert);
7014fe47877Sdjm 
7024fe47877Sdjm 	return r;
7034fe47877Sdjm }
7044fe47877Sdjm 
705094c80e0Sdjm int
sk_enroll(uint32_t alg,const uint8_t * challenge,size_t challenge_len,const char * application,uint8_t flags,const char * pin,struct sk_option ** options,struct sk_enroll_response ** enroll_response)706a0caf565Sdjm sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len,
7072db06755Sdjm     const char *application, uint8_t flags, const char *pin,
708a0caf565Sdjm     struct sk_option **options, struct sk_enroll_response **enroll_response)
709094c80e0Sdjm {
710094c80e0Sdjm 	fido_cred_t *cred = NULL;
711094c80e0Sdjm 	const uint8_t *ptr;
712b4f4fe96Sdjm 	uint8_t user_id[32];
71301bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
714094c80e0Sdjm 	struct sk_enroll_response *response = NULL;
715094c80e0Sdjm 	size_t len;
7164a0fa473Sdjm 	int credprot;
717094c80e0Sdjm 	int cose_alg;
718480af03fSdjm 	int ret = SSH_SK_ERR_GENERAL;
719094c80e0Sdjm 	int r;
720094c80e0Sdjm 	char *device = NULL;
721094c80e0Sdjm 
722fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
723fb0a6bcaSdjm 
72435f0234cSmarkus 	if (enroll_response == NULL) {
72535f0234cSmarkus 		skdebug(__func__, "enroll_response == NULL");
726094c80e0Sdjm 		goto out;
727094c80e0Sdjm 	}
72801bb7af0Sdjm 	*enroll_response = NULL;
729a0caf565Sdjm 	memset(user_id, 0, sizeof(user_id));
73001bb7af0Sdjm 	if (check_enroll_options(options, &device, user_id,
73101bb7af0Sdjm 	    sizeof(user_id)) != 0)
732a0caf565Sdjm 		goto out; /* error already logged */
733a0caf565Sdjm 
734094c80e0Sdjm 	switch(alg) {
735f8cd6cb1Snaddy #ifdef WITH_OPENSSL
736b0297854Sdjm 	case SSH_SK_ECDSA:
737094c80e0Sdjm 		cose_alg = COSE_ES256;
738094c80e0Sdjm 		break;
739f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
740b0297854Sdjm 	case SSH_SK_ED25519:
741094c80e0Sdjm 		cose_alg = COSE_EDDSA;
742094c80e0Sdjm 		break;
743094c80e0Sdjm 	default:
744094c80e0Sdjm 		skdebug(__func__, "unsupported key type %d", alg);
745094c80e0Sdjm 		goto out;
746094c80e0Sdjm 	}
74701bb7af0Sdjm 	if (device != NULL)
74801bb7af0Sdjm 		sk = sk_open(device);
74901bb7af0Sdjm 	else
7505814c2aaSdjm 		sk = sk_probe(NULL, NULL, 0, 0);
75101bb7af0Sdjm 	if (sk == NULL) {
75210f34025Sdjm 		ret = SSH_SK_ERR_DEVICE_NOT_FOUND;
75301bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
754094c80e0Sdjm 		goto out;
755094c80e0Sdjm 	}
75601bb7af0Sdjm 	skdebug(__func__, "using device %s", sk->path);
7574fe47877Sdjm 	if ((flags & SSH_SK_RESIDENT_KEY) != 0 &&
7584fe47877Sdjm 	    (flags & SSH_SK_FORCE_OPERATION) == 0 &&
7594fe47877Sdjm 	    (r = key_lookup(sk->dev, application, user_id, sizeof(user_id),
7604fe47877Sdjm 	    pin)) != FIDO_ERR_NO_CREDENTIALS) {
7614fe47877Sdjm 		if (r != FIDO_OK) {
76293c6acccSdjm 			ret = fidoerr_to_skerr(r);
7634fe47877Sdjm 			skdebug(__func__, "key_lookup failed");
7644fe47877Sdjm 		} else {
7654fe47877Sdjm 			ret = SSH_SK_ERR_CREDENTIAL_EXISTS;
7664fe47877Sdjm 			skdebug(__func__, "key exists");
7674fe47877Sdjm 		}
7684fe47877Sdjm 		goto out;
7694fe47877Sdjm 	}
770094c80e0Sdjm 	if ((cred = fido_cred_new()) == NULL) {
771094c80e0Sdjm 		skdebug(__func__, "fido_cred_new failed");
772094c80e0Sdjm 		goto out;
773094c80e0Sdjm 	}
774094c80e0Sdjm 	if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) {
775094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r));
776094c80e0Sdjm 		goto out;
777094c80e0Sdjm 	}
778b4f4fe96Sdjm 	if ((r = fido_cred_set_clientdata(cred,
779b4f4fe96Sdjm 	    challenge, challenge_len)) != FIDO_OK) {
780b4f4fe96Sdjm 		skdebug(__func__, "fido_cred_set_clientdata: %s",
781094c80e0Sdjm 		    fido_strerr(r));
782094c80e0Sdjm 		goto out;
783094c80e0Sdjm 	}
784b0297854Sdjm 	if ((r = fido_cred_set_rk(cred, (flags & SSH_SK_RESIDENT_KEY) != 0 ?
7858908bc36Sdjm 	    FIDO_OPT_TRUE : FIDO_OPT_OMIT)) != FIDO_OK) {
7868908bc36Sdjm 		skdebug(__func__, "fido_cred_set_rk: %s", fido_strerr(r));
7878908bc36Sdjm 		goto out;
7888908bc36Sdjm 	}
789094c80e0Sdjm 	if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id),
790094c80e0Sdjm 	    "openssh", "openssh", NULL)) != FIDO_OK) {
791094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r));
792094c80e0Sdjm 		goto out;
793094c80e0Sdjm 	}
794094c80e0Sdjm 	if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) {
795094c80e0Sdjm 		skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r));
796094c80e0Sdjm 		goto out;
797094c80e0Sdjm 	}
7981f63d3c4Sdjm 	if ((flags & (SSH_SK_RESIDENT_KEY|SSH_SK_USER_VERIFICATION_REQD)) != 0) {
79901bb7af0Sdjm 		if (!fido_dev_supports_cred_prot(sk->dev)) {
80001bb7af0Sdjm 			skdebug(__func__, "%s does not support credprot, "
80101bb7af0Sdjm 			    "refusing to create unprotected "
80201bb7af0Sdjm 			    "resident/verify-required key", sk->path);
8034a0fa473Sdjm 			ret = SSH_SK_ERR_UNSUPPORTED;
8044a0fa473Sdjm 			goto out;
8054a0fa473Sdjm 		}
8061f63d3c4Sdjm 		if ((flags & SSH_SK_USER_VERIFICATION_REQD))
8071f63d3c4Sdjm 			credprot = FIDO_CRED_PROT_UV_REQUIRED;
8081f63d3c4Sdjm 		else
8091f63d3c4Sdjm 			credprot = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID;
8101f63d3c4Sdjm 
8111f63d3c4Sdjm 		if ((r = fido_cred_set_prot(cred, credprot)) != FIDO_OK) {
8124a0fa473Sdjm 			skdebug(__func__, "fido_cred_set_prot: %s",
8134a0fa473Sdjm 			    fido_strerr(r));
8144a0fa473Sdjm 			ret = fidoerr_to_skerr(r);
8154a0fa473Sdjm 			goto out;
8164a0fa473Sdjm 		}
8174a0fa473Sdjm 	}
81801bb7af0Sdjm 	if ((r = fido_dev_make_cred(sk->dev, cred, pin)) != FIDO_OK) {
819094c80e0Sdjm 		skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r));
820480af03fSdjm 		ret = fidoerr_to_skerr(r);
821094c80e0Sdjm 		goto out;
822094c80e0Sdjm 	}
823094c80e0Sdjm 	if (fido_cred_x5c_ptr(cred) != NULL) {
824094c80e0Sdjm 		if ((r = fido_cred_verify(cred)) != FIDO_OK) {
825094c80e0Sdjm 			skdebug(__func__, "fido_cred_verify: %s",
826094c80e0Sdjm 			    fido_strerr(r));
827094c80e0Sdjm 			goto out;
828094c80e0Sdjm 		}
829094c80e0Sdjm 	} else {
830094c80e0Sdjm 		skdebug(__func__, "self-attested credential");
831094c80e0Sdjm 		if ((r = fido_cred_verify_self(cred)) != FIDO_OK) {
832094c80e0Sdjm 			skdebug(__func__, "fido_cred_verify_self: %s",
833094c80e0Sdjm 			    fido_strerr(r));
834094c80e0Sdjm 			goto out;
835094c80e0Sdjm 		}
836094c80e0Sdjm 	}
837094c80e0Sdjm 	if ((response = calloc(1, sizeof(*response))) == NULL) {
838094c80e0Sdjm 		skdebug(__func__, "calloc response failed");
839094c80e0Sdjm 		goto out;
840094c80e0Sdjm 	}
84118b3d906Sdjm 	response->flags = flags;
842094c80e0Sdjm 	if (pack_public_key(alg, cred, response) != 0) {
843094c80e0Sdjm 		skdebug(__func__, "pack_public_key failed");
844094c80e0Sdjm 		goto out;
845094c80e0Sdjm 	}
846094c80e0Sdjm 	if ((ptr = fido_cred_id_ptr(cred)) != NULL) {
847094c80e0Sdjm 		len = fido_cred_id_len(cred);
848094c80e0Sdjm 		if ((response->key_handle = calloc(1, len)) == NULL) {
849094c80e0Sdjm 			skdebug(__func__, "calloc key handle failed");
850094c80e0Sdjm 			goto out;
851094c80e0Sdjm 		}
852094c80e0Sdjm 		memcpy(response->key_handle, ptr, len);
853094c80e0Sdjm 		response->key_handle_len = len;
854094c80e0Sdjm 	}
855094c80e0Sdjm 	if ((ptr = fido_cred_sig_ptr(cred)) != NULL) {
856094c80e0Sdjm 		len = fido_cred_sig_len(cred);
857094c80e0Sdjm 		if ((response->signature = calloc(1, len)) == NULL) {
858094c80e0Sdjm 			skdebug(__func__, "calloc signature failed");
859094c80e0Sdjm 			goto out;
860094c80e0Sdjm 		}
861094c80e0Sdjm 		memcpy(response->signature, ptr, len);
862094c80e0Sdjm 		response->signature_len = len;
863094c80e0Sdjm 	}
864094c80e0Sdjm 	if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) {
865094c80e0Sdjm 		len = fido_cred_x5c_len(cred);
86648e6b99dSdjm 		skdebug(__func__, "attestation cert len=%zu", len);
867094c80e0Sdjm 		if ((response->attestation_cert = calloc(1, len)) == NULL) {
868094c80e0Sdjm 			skdebug(__func__, "calloc attestation cert failed");
869094c80e0Sdjm 			goto out;
870094c80e0Sdjm 		}
871094c80e0Sdjm 		memcpy(response->attestation_cert, ptr, len);
872094c80e0Sdjm 		response->attestation_cert_len = len;
873094c80e0Sdjm 	}
874ee0a8761Sdjm 	if ((ptr = fido_cred_authdata_ptr(cred)) != NULL) {
875ee0a8761Sdjm 		len = fido_cred_authdata_len(cred);
87648e6b99dSdjm 		skdebug(__func__, "authdata len=%zu", len);
877ee0a8761Sdjm 		if ((response->authdata = calloc(1, len)) == NULL) {
878ee0a8761Sdjm 			skdebug(__func__, "calloc authdata failed");
879ee0a8761Sdjm 			goto out;
880ee0a8761Sdjm 		}
881ee0a8761Sdjm 		memcpy(response->authdata, ptr, len);
882ee0a8761Sdjm 		response->authdata_len = len;
883ee0a8761Sdjm 	}
88435f0234cSmarkus 	*enroll_response = response;
885094c80e0Sdjm 	response = NULL;
886094c80e0Sdjm 	ret = 0;
887094c80e0Sdjm  out:
888094c80e0Sdjm 	free(device);
889094c80e0Sdjm 	if (response != NULL) {
890094c80e0Sdjm 		free(response->public_key);
891094c80e0Sdjm 		free(response->key_handle);
892094c80e0Sdjm 		free(response->signature);
893094c80e0Sdjm 		free(response->attestation_cert);
894ee0a8761Sdjm 		free(response->authdata);
895094c80e0Sdjm 		free(response);
896094c80e0Sdjm 	}
89701bb7af0Sdjm 	sk_close(sk);
898094c80e0Sdjm 	fido_cred_free(&cred);
899094c80e0Sdjm 	return ret;
900094c80e0Sdjm }
901094c80e0Sdjm 
902f8cd6cb1Snaddy #ifdef WITH_OPENSSL
903094c80e0Sdjm static int
pack_sig_ecdsa(fido_assert_t * assert,struct sk_sign_response * response)904094c80e0Sdjm pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response)
905094c80e0Sdjm {
906094c80e0Sdjm 	ECDSA_SIG *sig = NULL;
907094c80e0Sdjm 	const BIGNUM *sig_r, *sig_s;
908094c80e0Sdjm 	const unsigned char *cp;
909094c80e0Sdjm 	size_t sig_len;
910094c80e0Sdjm 	int ret = -1;
911094c80e0Sdjm 
912094c80e0Sdjm 	cp = fido_assert_sig_ptr(assert, 0);
913094c80e0Sdjm 	sig_len = fido_assert_sig_len(assert, 0);
914094c80e0Sdjm 	if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) {
915094c80e0Sdjm 		skdebug(__func__, "d2i_ECDSA_SIG failed");
916094c80e0Sdjm 		goto out;
917094c80e0Sdjm 	}
918094c80e0Sdjm 	ECDSA_SIG_get0(sig, &sig_r, &sig_s);
919094c80e0Sdjm 	response->sig_r_len = BN_num_bytes(sig_r);
920094c80e0Sdjm 	response->sig_s_len = BN_num_bytes(sig_s);
921094c80e0Sdjm 	if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL ||
922094c80e0Sdjm 	    (response->sig_s = calloc(1, response->sig_s_len)) == NULL) {
923094c80e0Sdjm 		skdebug(__func__, "calloc signature failed");
924094c80e0Sdjm 		goto out;
925094c80e0Sdjm 	}
926094c80e0Sdjm 	BN_bn2bin(sig_r, response->sig_r);
927094c80e0Sdjm 	BN_bn2bin(sig_s, response->sig_s);
928094c80e0Sdjm 	ret = 0;
929094c80e0Sdjm  out:
930094c80e0Sdjm 	ECDSA_SIG_free(sig);
931094c80e0Sdjm 	if (ret != 0) {
932094c80e0Sdjm 		free(response->sig_r);
933094c80e0Sdjm 		free(response->sig_s);
934094c80e0Sdjm 		response->sig_r = NULL;
935094c80e0Sdjm 		response->sig_s = NULL;
936094c80e0Sdjm 	}
937094c80e0Sdjm 	return ret;
938094c80e0Sdjm }
939f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
940094c80e0Sdjm 
941094c80e0Sdjm static int
pack_sig_ed25519(fido_assert_t * assert,struct sk_sign_response * response)942094c80e0Sdjm pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response)
943094c80e0Sdjm {
944094c80e0Sdjm 	const unsigned char *ptr;
945094c80e0Sdjm 	size_t len;
946094c80e0Sdjm 	int ret = -1;
947094c80e0Sdjm 
948094c80e0Sdjm 	ptr = fido_assert_sig_ptr(assert, 0);
949094c80e0Sdjm 	len = fido_assert_sig_len(assert, 0);
950094c80e0Sdjm 	if (len != 64) {
951094c80e0Sdjm 		skdebug(__func__, "bad length %zu", len);
952094c80e0Sdjm 		goto out;
953094c80e0Sdjm 	}
954094c80e0Sdjm 	response->sig_r_len = len;
955094c80e0Sdjm 	if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) {
956094c80e0Sdjm 		skdebug(__func__, "calloc signature failed");
957094c80e0Sdjm 		goto out;
958094c80e0Sdjm 	}
959094c80e0Sdjm 	memcpy(response->sig_r, ptr, len);
960094c80e0Sdjm 	ret = 0;
961094c80e0Sdjm  out:
962094c80e0Sdjm 	if (ret != 0) {
963094c80e0Sdjm 		free(response->sig_r);
964094c80e0Sdjm 		response->sig_r = NULL;
965094c80e0Sdjm 	}
966094c80e0Sdjm 	return ret;
967094c80e0Sdjm }
968094c80e0Sdjm 
969094c80e0Sdjm static int
pack_sig(uint32_t alg,fido_assert_t * assert,struct sk_sign_response * response)970a0caf565Sdjm pack_sig(uint32_t  alg, fido_assert_t *assert,
971a0caf565Sdjm     struct sk_sign_response *response)
972094c80e0Sdjm {
973094c80e0Sdjm 	switch(alg) {
974f8cd6cb1Snaddy #ifdef WITH_OPENSSL
975b0297854Sdjm 	case SSH_SK_ECDSA:
976094c80e0Sdjm 		return pack_sig_ecdsa(assert, response);
977f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
978b0297854Sdjm 	case SSH_SK_ED25519:
979094c80e0Sdjm 		return pack_sig_ed25519(assert, response);
980094c80e0Sdjm 	default:
981094c80e0Sdjm 		return -1;
982094c80e0Sdjm 	}
983094c80e0Sdjm }
984094c80e0Sdjm 
985a0caf565Sdjm /* Checks sk_options for sk_sign() and sk_load_resident_keys() */
986a0caf565Sdjm static int
check_sign_load_resident_options(struct sk_option ** options,char ** devicep)987a0caf565Sdjm check_sign_load_resident_options(struct sk_option **options, char **devicep)
988a0caf565Sdjm {
989a0caf565Sdjm 	size_t i;
990a0caf565Sdjm 
991a0caf565Sdjm 	if (options == NULL)
992a0caf565Sdjm 		return 0;
993a0caf565Sdjm 	for (i = 0; options[i] != NULL; i++) {
994a0caf565Sdjm 		if (strcmp(options[i]->name, "device") == 0) {
995a0caf565Sdjm 			if ((*devicep = strdup(options[i]->value)) == NULL) {
996a0caf565Sdjm 				skdebug(__func__, "strdup device failed");
997a0caf565Sdjm 				return -1;
998a0caf565Sdjm 			}
999a0caf565Sdjm 			skdebug(__func__, "requested device %s", *devicep);
1000a0caf565Sdjm 		} else {
1001a0caf565Sdjm 			skdebug(__func__, "requested unsupported option %s",
1002a0caf565Sdjm 			    options[i]->name);
1003a0caf565Sdjm 			if (options[i]->required) {
1004a0caf565Sdjm 				skdebug(__func__, "unknown required option");
1005a0caf565Sdjm 				return -1;
1006a0caf565Sdjm 			}
1007a0caf565Sdjm 		}
1008a0caf565Sdjm 	}
1009a0caf565Sdjm 	return 0;
1010a0caf565Sdjm }
1011a0caf565Sdjm 
1012094c80e0Sdjm int
sk_sign(uint32_t alg,const uint8_t * data,size_t datalen,const char * application,const uint8_t * key_handle,size_t key_handle_len,uint8_t flags,const char * pin,struct sk_option ** options,struct sk_sign_response ** sign_response)10133ce2af41Sdjm sk_sign(uint32_t alg, const uint8_t *data, size_t datalen,
1014094c80e0Sdjm     const char *application,
1015094c80e0Sdjm     const uint8_t *key_handle, size_t key_handle_len,
1016a0caf565Sdjm     uint8_t flags, const char *pin, struct sk_option **options,
1017a0caf565Sdjm     struct sk_sign_response **sign_response)
1018094c80e0Sdjm {
1019094c80e0Sdjm 	fido_assert_t *assert = NULL;
1020a0caf565Sdjm 	char *device = NULL;
102101bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
1022094c80e0Sdjm 	struct sk_sign_response *response = NULL;
102318b3d906Sdjm 	int ret = SSH_SK_ERR_GENERAL, internal_uv;
1024094c80e0Sdjm 	int r;
1025094c80e0Sdjm 
1026fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
1027094c80e0Sdjm 
1028094c80e0Sdjm 	if (sign_response == NULL) {
1029094c80e0Sdjm 		skdebug(__func__, "sign_response == NULL");
1030094c80e0Sdjm 		goto out;
1031094c80e0Sdjm 	}
1032094c80e0Sdjm 	*sign_response = NULL;
1033a0caf565Sdjm 	if (check_sign_load_resident_options(options, &device) != 0)
1034a0caf565Sdjm 		goto out; /* error already logged */
103501bb7af0Sdjm 	if (device != NULL)
103601bb7af0Sdjm 		sk = sk_open(device);
103701bb7af0Sdjm 	else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD))
10385814c2aaSdjm 		sk = sk_probe(NULL, NULL, 0, 0);
103901bb7af0Sdjm 	else
10405814c2aaSdjm 		sk = sk_probe(application, key_handle, key_handle_len, 0);
104101bb7af0Sdjm 	if (sk == NULL) {
104210f34025Sdjm 		ret = SSH_SK_ERR_DEVICE_NOT_FOUND;
104301bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
1044094c80e0Sdjm 		goto out;
1045094c80e0Sdjm 	}
1046094c80e0Sdjm 	if ((assert = fido_assert_new()) == NULL) {
1047094c80e0Sdjm 		skdebug(__func__, "fido_assert_new failed");
1048094c80e0Sdjm 		goto out;
1049094c80e0Sdjm 	}
1050b4f4fe96Sdjm 	if ((r = fido_assert_set_clientdata(assert,
1051b4f4fe96Sdjm 	    data, datalen)) != FIDO_OK)  {
1052b4f4fe96Sdjm 		skdebug(__func__, "fido_assert_set_clientdata: %s",
1053094c80e0Sdjm 		    fido_strerr(r));
1054094c80e0Sdjm 		goto out;
1055094c80e0Sdjm 	}
1056094c80e0Sdjm 	if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) {
1057094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r));
1058094c80e0Sdjm 		goto out;
1059094c80e0Sdjm 	}
1060094c80e0Sdjm 	if ((r = fido_assert_allow_cred(assert, key_handle,
1061094c80e0Sdjm 	    key_handle_len)) != FIDO_OK) {
1062094c80e0Sdjm 		skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r));
1063094c80e0Sdjm 		goto out;
1064094c80e0Sdjm 	}
1065094c80e0Sdjm 	if ((r = fido_assert_set_up(assert,
1066b0297854Sdjm 	    (flags & SSH_SK_USER_PRESENCE_REQD) ?
1067094c80e0Sdjm 	    FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) {
1068094c80e0Sdjm 		skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r));
1069094c80e0Sdjm 		goto out;
1070094c80e0Sdjm 	}
107118b3d906Sdjm 	if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD)) {
107218b3d906Sdjm 		if (check_sk_options(sk->dev, "uv", &internal_uv) < 0 ||
107318b3d906Sdjm 		    internal_uv != 1) {
107418b3d906Sdjm 			skdebug(__func__, "check_sk_options uv");
107518b3d906Sdjm 			ret = SSH_SK_ERR_PIN_REQUIRED;
10761f63d3c4Sdjm 			goto out;
10771f63d3c4Sdjm 		}
107818b3d906Sdjm 		if ((r = fido_assert_set_uv(assert,
107918b3d906Sdjm 		    FIDO_OPT_TRUE)) != FIDO_OK) {
108018b3d906Sdjm 			skdebug(__func__, "fido_assert_set_uv: %s",
108118b3d906Sdjm 			    fido_strerr(r));
108218b3d906Sdjm 			ret = fidoerr_to_skerr(r);
108318b3d906Sdjm 			goto out;
108418b3d906Sdjm 		}
108518b3d906Sdjm 	}
108601bb7af0Sdjm 	if ((r = fido_dev_get_assert(sk->dev, assert, pin)) != FIDO_OK) {
1087094c80e0Sdjm 		skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r));
10881f63d3c4Sdjm 		ret = fidoerr_to_skerr(r);
1089094c80e0Sdjm 		goto out;
1090094c80e0Sdjm 	}
1091094c80e0Sdjm 	if ((response = calloc(1, sizeof(*response))) == NULL) {
1092094c80e0Sdjm 		skdebug(__func__, "calloc response failed");
1093094c80e0Sdjm 		goto out;
1094094c80e0Sdjm 	}
1095094c80e0Sdjm 	response->flags = fido_assert_flags(assert, 0);
1096094c80e0Sdjm 	response->counter = fido_assert_sigcount(assert, 0);
1097094c80e0Sdjm 	if (pack_sig(alg, assert, response) != 0) {
1098094c80e0Sdjm 		skdebug(__func__, "pack_sig failed");
1099094c80e0Sdjm 		goto out;
1100094c80e0Sdjm 	}
1101094c80e0Sdjm 	*sign_response = response;
1102094c80e0Sdjm 	response = NULL;
1103094c80e0Sdjm 	ret = 0;
1104094c80e0Sdjm  out:
1105a0caf565Sdjm 	free(device);
1106094c80e0Sdjm 	if (response != NULL) {
1107094c80e0Sdjm 		free(response->sig_r);
1108094c80e0Sdjm 		free(response->sig_s);
1109094c80e0Sdjm 		free(response);
1110094c80e0Sdjm 	}
111101bb7af0Sdjm 	sk_close(sk);
1112094c80e0Sdjm 	fido_assert_free(&assert);
1113094c80e0Sdjm 	return ret;
1114094c80e0Sdjm }
11151ac4a90aSdjm 
11161ac4a90aSdjm static int
read_rks(struct sk_usbhid * sk,const char * pin,struct sk_resident_key *** rksp,size_t * nrksp)111701bb7af0Sdjm read_rks(struct sk_usbhid *sk, const char *pin,
11181ac4a90aSdjm     struct sk_resident_key ***rksp, size_t *nrksp)
11191ac4a90aSdjm {
112018b3d906Sdjm 	int ret = SSH_SK_ERR_GENERAL, r = -1, internal_uv;
11211ac4a90aSdjm 	fido_credman_metadata_t *metadata = NULL;
11221ac4a90aSdjm 	fido_credman_rp_t *rp = NULL;
11231ac4a90aSdjm 	fido_credman_rk_t *rk = NULL;
1124991d5a20Sdjm 	size_t i, j, nrp, nrk, user_id_len;
11251ac4a90aSdjm 	const fido_cred_t *cred;
1126991d5a20Sdjm 	const char *rp_id, *rp_name, *user_name;
11271ac4a90aSdjm 	struct sk_resident_key *srk = NULL, **tmp;
1128991d5a20Sdjm 	const u_char *user_id;
11291ac4a90aSdjm 
113001bb7af0Sdjm 	if (pin == NULL) {
113101bb7af0Sdjm 		skdebug(__func__, "no PIN specified");
113201bb7af0Sdjm 		ret = SSH_SK_ERR_PIN_REQUIRED;
113301bb7af0Sdjm 		goto out;
11341ac4a90aSdjm 	}
11351ac4a90aSdjm 	if ((metadata = fido_credman_metadata_new()) == NULL) {
11361ac4a90aSdjm 		skdebug(__func__, "alloc failed");
11371ac4a90aSdjm 		goto out;
11381ac4a90aSdjm 	}
113918b3d906Sdjm 	if (check_sk_options(sk->dev, "uv", &internal_uv) != 0) {
114018b3d906Sdjm 		skdebug(__func__, "check_sk_options failed");
114118b3d906Sdjm 		goto out;
114218b3d906Sdjm 	}
11431ac4a90aSdjm 
114401bb7af0Sdjm 	if ((r = fido_credman_get_dev_metadata(sk->dev, metadata, pin)) != 0) {
11451ac4a90aSdjm 		if (r == FIDO_ERR_INVALID_COMMAND) {
11461ac4a90aSdjm 			skdebug(__func__, "device %s does not support "
114701bb7af0Sdjm 			    "resident keys", sk->path);
1148480af03fSdjm 			ret = 0;
11491ac4a90aSdjm 			goto out;
11501ac4a90aSdjm 		}
11511ac4a90aSdjm 		skdebug(__func__, "get metadata for %s failed: %s",
115201bb7af0Sdjm 		    sk->path, fido_strerr(r));
1153a0caf565Sdjm 		ret = fidoerr_to_skerr(r);
11541ac4a90aSdjm 		goto out;
11551ac4a90aSdjm 	}
11561ac4a90aSdjm 	skdebug(__func__, "existing %llu, remaining %llu",
11571ac4a90aSdjm 	    (unsigned long long)fido_credman_rk_existing(metadata),
11581ac4a90aSdjm 	    (unsigned long long)fido_credman_rk_remaining(metadata));
11591ac4a90aSdjm 	if ((rp = fido_credman_rp_new()) == NULL) {
11601ac4a90aSdjm 		skdebug(__func__, "alloc rp failed");
11611ac4a90aSdjm 		goto out;
11621ac4a90aSdjm 	}
116301bb7af0Sdjm 	if ((r = fido_credman_get_dev_rp(sk->dev, rp, pin)) != 0) {
11641ac4a90aSdjm 		skdebug(__func__, "get RPs for %s failed: %s",
116501bb7af0Sdjm 		    sk->path, fido_strerr(r));
11661ac4a90aSdjm 		goto out;
11671ac4a90aSdjm 	}
11681ac4a90aSdjm 	nrp = fido_credman_rp_count(rp);
11691ac4a90aSdjm 	skdebug(__func__, "Device %s has resident keys for %zu RPs",
117001bb7af0Sdjm 	    sk->path, nrp);
11711ac4a90aSdjm 
11721ac4a90aSdjm 	/* Iterate over RP IDs that have resident keys */
11731ac4a90aSdjm 	for (i = 0; i < nrp; i++) {
1174991d5a20Sdjm 		rp_id = fido_credman_rp_id(rp, i);
1175991d5a20Sdjm 		rp_name = fido_credman_rp_name(rp, i);
11761ac4a90aSdjm 		skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu",
1177991d5a20Sdjm 		    i, rp_name == NULL ? "(none)" : rp_name,
1178991d5a20Sdjm 		    rp_id == NULL ? "(none)" : rp_id,
11791ac4a90aSdjm 		    fido_credman_rp_id_hash_len(rp, i));
11801ac4a90aSdjm 
11811ac4a90aSdjm 		/* Skip non-SSH RP IDs */
1182991d5a20Sdjm 		if (rp_id == NULL ||
1183991d5a20Sdjm 		    strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0)
11841ac4a90aSdjm 			continue;
11851ac4a90aSdjm 
11861ac4a90aSdjm 		fido_credman_rk_free(&rk);
11871ac4a90aSdjm 		if ((rk = fido_credman_rk_new()) == NULL) {
11881ac4a90aSdjm 			skdebug(__func__, "alloc rk failed");
11891ac4a90aSdjm 			goto out;
11901ac4a90aSdjm 		}
119101bb7af0Sdjm 		if ((r = fido_credman_get_dev_rk(sk->dev,
119201bb7af0Sdjm 		    fido_credman_rp_id(rp, i), rk, pin)) != 0) {
11931ac4a90aSdjm 			skdebug(__func__, "get RKs for %s slot %zu failed: %s",
119401bb7af0Sdjm 			    sk->path, i, fido_strerr(r));
11951ac4a90aSdjm 			goto out;
11961ac4a90aSdjm 		}
11971ac4a90aSdjm 		nrk = fido_credman_rk_count(rk);
11981ac4a90aSdjm 		skdebug(__func__, "RP \"%s\" has %zu resident keys",
11991ac4a90aSdjm 		    fido_credman_rp_id(rp, i), nrk);
12001ac4a90aSdjm 
12011ac4a90aSdjm 		/* Iterate over resident keys for this RP ID */
12021ac4a90aSdjm 		for (j = 0; j < nrk; j++) {
12031ac4a90aSdjm 			if ((cred = fido_credman_rk(rk, j)) == NULL) {
12041ac4a90aSdjm 				skdebug(__func__, "no RK in slot %zu", j);
12051ac4a90aSdjm 				continue;
12061ac4a90aSdjm 			}
1207991d5a20Sdjm 			if ((user_name = fido_cred_user_name(cred)) == NULL)
1208991d5a20Sdjm 				user_name = "";
1209991d5a20Sdjm 			user_id = fido_cred_user_id_ptr(cred);
1210991d5a20Sdjm 			user_id_len = fido_cred_user_id_len(cred);
1211991d5a20Sdjm 			skdebug(__func__, "Device %s RP \"%s\" user \"%s\" "
1212991d5a20Sdjm 			    "uidlen %zu slot %zu: type %d flags 0x%02x "
1213991d5a20Sdjm 			    "prot 0x%02x", sk->path, rp_id, user_name,
1214991d5a20Sdjm 			    user_id_len, j, fido_cred_type(cred),
12151f63d3c4Sdjm 			    fido_cred_flags(cred), fido_cred_prot(cred));
12161ac4a90aSdjm 
12171ac4a90aSdjm 			/* build response entry */
12181ac4a90aSdjm 			if ((srk = calloc(1, sizeof(*srk))) == NULL ||
12191ac4a90aSdjm 			    (srk->key.key_handle = calloc(1,
12201ac4a90aSdjm 			    fido_cred_id_len(cred))) == NULL ||
1221991d5a20Sdjm 			    (srk->application = strdup(rp_id)) == NULL ||
1222991d5a20Sdjm 			    (user_id_len > 0 &&
1223991d5a20Sdjm 			     (srk->user_id = calloc(1, user_id_len)) == NULL)) {
12241ac4a90aSdjm 				skdebug(__func__, "alloc sk_resident_key");
12251ac4a90aSdjm 				goto out;
12261ac4a90aSdjm 			}
12271ac4a90aSdjm 
12281ac4a90aSdjm 			srk->key.key_handle_len = fido_cred_id_len(cred);
122915a2cdb6Sdjm 			memcpy(srk->key.key_handle, fido_cred_id_ptr(cred),
12301ac4a90aSdjm 			    srk->key.key_handle_len);
1231991d5a20Sdjm 			srk->user_id_len = user_id_len;
1232991d5a20Sdjm 			if (srk->user_id_len != 0)
1233991d5a20Sdjm 				memcpy(srk->user_id, user_id, srk->user_id_len);
12341ac4a90aSdjm 
12351ac4a90aSdjm 			switch (fido_cred_type(cred)) {
12361ac4a90aSdjm 			case COSE_ES256:
1237b0297854Sdjm 				srk->alg = SSH_SK_ECDSA;
12381ac4a90aSdjm 				break;
12391ac4a90aSdjm 			case COSE_EDDSA:
1240b0297854Sdjm 				srk->alg = SSH_SK_ED25519;
12411ac4a90aSdjm 				break;
12421ac4a90aSdjm 			default:
12431ac4a90aSdjm 				skdebug(__func__, "unsupported key type %d",
12441ac4a90aSdjm 				    fido_cred_type(cred));
1245b0297854Sdjm 				goto out; /* XXX free rk and continue */
12461ac4a90aSdjm 			}
12471ac4a90aSdjm 
124818b3d906Sdjm 			if (fido_cred_prot(cred) == FIDO_CRED_PROT_UV_REQUIRED
124918b3d906Sdjm 			    && internal_uv == -1)
125015a2cdb6Sdjm 				srk->flags |=  SSH_SK_USER_VERIFICATION_REQD;
125115a2cdb6Sdjm 
12521ac4a90aSdjm 			if ((r = pack_public_key(srk->alg, cred,
12531ac4a90aSdjm 			    &srk->key)) != 0) {
12541ac4a90aSdjm 				skdebug(__func__, "pack public key failed");
12551ac4a90aSdjm 				goto out;
12561ac4a90aSdjm 			}
12571ac4a90aSdjm 			/* append */
12581ac4a90aSdjm 			if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1,
12591ac4a90aSdjm 			    sizeof(**rksp))) == NULL) {
12601ac4a90aSdjm 				skdebug(__func__, "alloc rksp");
12611ac4a90aSdjm 				goto out;
12621ac4a90aSdjm 			}
12631ac4a90aSdjm 			*rksp = tmp;
12641ac4a90aSdjm 			(*rksp)[(*nrksp)++] = srk;
12651ac4a90aSdjm 			srk = NULL;
12661ac4a90aSdjm 		}
12671ac4a90aSdjm 	}
12681ac4a90aSdjm 	/* Success */
1269480af03fSdjm 	ret = 0;
12701ac4a90aSdjm  out:
12711ac4a90aSdjm 	if (srk != NULL) {
12721ac4a90aSdjm 		free(srk->application);
12731ac4a90aSdjm 		freezero(srk->key.public_key, srk->key.public_key_len);
12741ac4a90aSdjm 		freezero(srk->key.key_handle, srk->key.key_handle_len);
1275991d5a20Sdjm 		freezero(srk->user_id, srk->user_id_len);
12761ac4a90aSdjm 		freezero(srk, sizeof(*srk));
12771ac4a90aSdjm 	}
12781ac4a90aSdjm 	fido_credman_rp_free(&rp);
12791ac4a90aSdjm 	fido_credman_rk_free(&rk);
12801ac4a90aSdjm 	fido_credman_metadata_free(&metadata);
1281480af03fSdjm 	return ret;
12821ac4a90aSdjm }
12831ac4a90aSdjm 
12841ac4a90aSdjm int
sk_load_resident_keys(const char * pin,struct sk_option ** options,struct sk_resident_key *** rksp,size_t * nrksp)1285a0caf565Sdjm sk_load_resident_keys(const char *pin, struct sk_option **options,
12861ac4a90aSdjm     struct sk_resident_key ***rksp, size_t *nrksp)
12871ac4a90aSdjm {
1288480af03fSdjm 	int ret = SSH_SK_ERR_GENERAL, r = -1;
128901bb7af0Sdjm 	size_t i, nrks = 0;
12901ac4a90aSdjm 	struct sk_resident_key **rks = NULL;
129101bb7af0Sdjm 	struct sk_usbhid *sk = NULL;
1292a0caf565Sdjm 	char *device = NULL;
129301bb7af0Sdjm 
12941ac4a90aSdjm 	*rksp = NULL;
12951ac4a90aSdjm 	*nrksp = 0;
12961ac4a90aSdjm 
1297fb0a6bcaSdjm 	fido_init(SSH_FIDO_INIT_ARG);
1298fb0a6bcaSdjm 
1299a0caf565Sdjm 	if (check_sign_load_resident_options(options, &device) != 0)
1300a0caf565Sdjm 		goto out; /* error already logged */
130101bb7af0Sdjm 	if (device != NULL)
130201bb7af0Sdjm 		sk = sk_open(device);
130301bb7af0Sdjm 	else
13045814c2aaSdjm 		sk = sk_probe(NULL, NULL, 0, 1);
130501bb7af0Sdjm 	if (sk == NULL) {
130610f34025Sdjm 		ret = SSH_SK_ERR_DEVICE_NOT_FOUND;
130701bb7af0Sdjm 		skdebug(__func__, "failed to find sk");
130801bb7af0Sdjm 		goto out;
130901bb7af0Sdjm 	}
131001bb7af0Sdjm 	skdebug(__func__, "trying %s", sk->path);
131101bb7af0Sdjm 	if ((r = read_rks(sk, pin, &rks, &nrks)) != 0) {
131201bb7af0Sdjm 		skdebug(__func__, "read_rks failed for %s", sk->path);
1313a0caf565Sdjm 		ret = r;
1314a0caf565Sdjm 		goto out;
1315a0caf565Sdjm 	}
1316a0caf565Sdjm 	/* success, unless we have no keys but a specific error */
1317a0caf565Sdjm 	if (nrks > 0 || ret == SSH_SK_ERR_GENERAL)
1318480af03fSdjm 		ret = 0;
13191ac4a90aSdjm 	*rksp = rks;
13201ac4a90aSdjm 	*nrksp = nrks;
13211ac4a90aSdjm 	rks = NULL;
13221ac4a90aSdjm 	nrks = 0;
13231ac4a90aSdjm  out:
132401bb7af0Sdjm 	sk_close(sk);
13251ac4a90aSdjm 	for (i = 0; i < nrks; i++) {
13261ac4a90aSdjm 		free(rks[i]->application);
13271ac4a90aSdjm 		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
13281ac4a90aSdjm 		freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
1329991d5a20Sdjm 		freezero(rks[i]->user_id, rks[i]->user_id_len);
13301ac4a90aSdjm 		freezero(rks[i], sizeof(*rks[i]));
13311ac4a90aSdjm 	}
13327a76b30eSdtucker 	free(device);
13331ac4a90aSdjm 	free(rks);
1334480af03fSdjm 	return ret;
13351ac4a90aSdjm }
13361ac4a90aSdjm 
1337