xref: /openbsd-src/usr.bin/ssh/ssh-sk.c (revision 5411e7691687441b2e28282ca3a1c531c6b949c4)
1*5411e769Sdjm /* $OpenBSD: ssh-sk.c,v 1.41 2024/08/15 00:51:51 djm Exp $ */
277045ccdSdjm /*
377045ccdSdjm  * Copyright (c) 2019 Google LLC
477045ccdSdjm  *
577045ccdSdjm  * Permission to use, copy, modify, and distribute this software for any
677045ccdSdjm  * purpose with or without fee is hereby granted, provided that the above
777045ccdSdjm  * copyright notice and this permission notice appear in all copies.
877045ccdSdjm  *
977045ccdSdjm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1077045ccdSdjm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1177045ccdSdjm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1277045ccdSdjm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1377045ccdSdjm  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1477045ccdSdjm  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1577045ccdSdjm  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1677045ccdSdjm  */
1777045ccdSdjm 
1877045ccdSdjm /* #define DEBUG_SK 1 */
1977045ccdSdjm 
2077045ccdSdjm #include <dlfcn.h>
2177045ccdSdjm #include <stddef.h>
2277045ccdSdjm #include <stdint.h>
2377045ccdSdjm #include <string.h>
2477045ccdSdjm #include <stdio.h>
2577045ccdSdjm 
26f8cd6cb1Snaddy #ifdef WITH_OPENSSL
2777045ccdSdjm #include <openssl/objects.h>
2877045ccdSdjm #include <openssl/ec.h>
29*5411e769Sdjm #include <openssl/evp.h>
30f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
3177045ccdSdjm 
3277045ccdSdjm #include "log.h"
3377045ccdSdjm #include "misc.h"
3477045ccdSdjm #include "sshbuf.h"
3577045ccdSdjm #include "sshkey.h"
3677045ccdSdjm #include "ssherr.h"
3777045ccdSdjm #include "digest.h"
3877045ccdSdjm 
3977045ccdSdjm #include "ssh-sk.h"
4077045ccdSdjm #include "sk-api.h"
41af4c80eeSmarkus #include "crypto_api.h"
4277045ccdSdjm 
4377045ccdSdjm struct sshsk_provider {
4477045ccdSdjm 	char *path;
4577045ccdSdjm 	void *dlhandle;
4677045ccdSdjm 
4777045ccdSdjm 	/* Return the version of the middleware API */
4877045ccdSdjm 	uint32_t (*sk_api_version)(void);
4977045ccdSdjm 
5077045ccdSdjm 	/* Enroll a U2F key (private key generation) */
51a3dd6837Smarkus 	int (*sk_enroll)(int alg, const uint8_t *challenge,
52a3dd6837Smarkus 	    size_t challenge_len, const char *application, uint8_t flags,
53a0caf565Sdjm 	    const char *pin, struct sk_option **opts,
54a0caf565Sdjm 	    struct sk_enroll_response **enroll_response);
5577045ccdSdjm 
5677045ccdSdjm 	/* Sign a challenge */
57a3dd6837Smarkus 	int (*sk_sign)(int alg, const uint8_t *message, size_t message_len,
5877045ccdSdjm 	    const char *application,
5977045ccdSdjm 	    const uint8_t *key_handle, size_t key_handle_len,
60a0caf565Sdjm 	    uint8_t flags, const char *pin, struct sk_option **opts,
612db06755Sdjm 	    struct sk_sign_response **sign_response);
621ac4a90aSdjm 
631ac4a90aSdjm 	/* Enumerate resident keys */
64a0caf565Sdjm 	int (*sk_load_resident_keys)(const char *pin, struct sk_option **opts,
651ac4a90aSdjm 	    struct sk_resident_key ***rks, size_t *nrks);
6677045ccdSdjm };
6777045ccdSdjm 
68094c80e0Sdjm /* Built-in version */
69094c80e0Sdjm int ssh_sk_enroll(int alg, const uint8_t *challenge,
70094c80e0Sdjm     size_t challenge_len, const char *application, uint8_t flags,
71a0caf565Sdjm     const char *pin, struct sk_option **opts,
72a0caf565Sdjm     struct sk_enroll_response **enroll_response);
73094c80e0Sdjm int ssh_sk_sign(int alg, const uint8_t *message, size_t message_len,
74094c80e0Sdjm     const char *application,
75094c80e0Sdjm     const uint8_t *key_handle, size_t key_handle_len,
76a0caf565Sdjm     uint8_t flags, const char *pin, struct sk_option **opts,
77a0caf565Sdjm     struct sk_sign_response **sign_response);
78a0caf565Sdjm int ssh_sk_load_resident_keys(const char *pin, struct sk_option **opts,
791ac4a90aSdjm     struct sk_resident_key ***rks, size_t *nrks);
80094c80e0Sdjm 
8177045ccdSdjm static void
8277045ccdSdjm sshsk_free(struct sshsk_provider *p)
8377045ccdSdjm {
8477045ccdSdjm 	if (p == NULL)
8577045ccdSdjm 		return;
8677045ccdSdjm 	free(p->path);
8777045ccdSdjm 	if (p->dlhandle != NULL)
8877045ccdSdjm 		dlclose(p->dlhandle);
8977045ccdSdjm 	free(p);
9077045ccdSdjm }
9177045ccdSdjm 
9277045ccdSdjm static struct sshsk_provider *
9377045ccdSdjm sshsk_open(const char *path)
9477045ccdSdjm {
9577045ccdSdjm 	struct sshsk_provider *ret = NULL;
9677045ccdSdjm 	uint32_t version;
9777045ccdSdjm 
9850c28975Sdjm 	if (path == NULL || *path == '\0') {
9950c28975Sdjm 		error("No FIDO SecurityKeyProvider specified");
10050c28975Sdjm 		return NULL;
10150c28975Sdjm 	}
10277045ccdSdjm 	if ((ret = calloc(1, sizeof(*ret))) == NULL) {
10348e6b99dSdjm 		error_f("calloc failed");
10477045ccdSdjm 		return NULL;
10577045ccdSdjm 	}
10677045ccdSdjm 	if ((ret->path = strdup(path)) == NULL) {
10748e6b99dSdjm 		error_f("strdup failed");
10877045ccdSdjm 		goto fail;
10977045ccdSdjm 	}
110094c80e0Sdjm 	/* Skip the rest if we're using the linked in middleware */
111094c80e0Sdjm 	if (strcasecmp(ret->path, "internal") == 0) {
112094c80e0Sdjm 		ret->sk_enroll = ssh_sk_enroll;
113094c80e0Sdjm 		ret->sk_sign = ssh_sk_sign;
1141ac4a90aSdjm 		ret->sk_load_resident_keys = ssh_sk_load_resident_keys;
115094c80e0Sdjm 		return ret;
116094c80e0Sdjm 	}
117f8f5a6b0Sdjm 	if (lib_contains_symbol(path, "sk_api_version") != 0) {
118f8f5a6b0Sdjm 		error("provider %s is not an OpenSSH FIDO library", path);
119f8f5a6b0Sdjm 		goto fail;
120f8f5a6b0Sdjm 	}
12177045ccdSdjm 	if ((ret->dlhandle = dlopen(path, RTLD_NOW)) == NULL) {
1225f47a660Snaddy 		error("Provider \"%s\" dlopen failed: %s", path, dlerror());
12377045ccdSdjm 		goto fail;
12477045ccdSdjm 	}
12577045ccdSdjm 	if ((ret->sk_api_version = dlsym(ret->dlhandle,
12677045ccdSdjm 	    "sk_api_version")) == NULL) {
127f8f5a6b0Sdjm 		fatal("Provider \"%s\" dlsym(sk_api_version) failed: %s",
1285f47a660Snaddy 		    path, dlerror());
12977045ccdSdjm 	}
13077045ccdSdjm 	version = ret->sk_api_version();
13148e6b99dSdjm 	debug_f("provider %s implements version 0x%08lx", ret->path,
13248e6b99dSdjm 	    (u_long)version);
13377045ccdSdjm 	if ((version & SSH_SK_VERSION_MAJOR_MASK) != SSH_SK_VERSION_MAJOR) {
1345f47a660Snaddy 		error("Provider \"%s\" implements unsupported "
13529322520Sdjm 		    "version 0x%08lx (supported: 0x%08lx)",
13629322520Sdjm 		    path, (u_long)version, (u_long)SSH_SK_VERSION_MAJOR);
13777045ccdSdjm 		goto fail;
13877045ccdSdjm 	}
13977045ccdSdjm 	if ((ret->sk_enroll = dlsym(ret->dlhandle, "sk_enroll")) == NULL) {
1405f47a660Snaddy 		error("Provider %s dlsym(sk_enroll) failed: %s",
1415f47a660Snaddy 		    path, dlerror());
14277045ccdSdjm 		goto fail;
14377045ccdSdjm 	}
14477045ccdSdjm 	if ((ret->sk_sign = dlsym(ret->dlhandle, "sk_sign")) == NULL) {
1455f47a660Snaddy 		error("Provider \"%s\" dlsym(sk_sign) failed: %s",
14677045ccdSdjm 		    path, dlerror());
14777045ccdSdjm 		goto fail;
14877045ccdSdjm 	}
1491ac4a90aSdjm 	if ((ret->sk_load_resident_keys = dlsym(ret->dlhandle,
1501ac4a90aSdjm 	    "sk_load_resident_keys")) == NULL) {
1515f47a660Snaddy 		error("Provider \"%s\" dlsym(sk_load_resident_keys) "
1525f47a660Snaddy 		    "failed: %s", path, dlerror());
1531ac4a90aSdjm 		goto fail;
1541ac4a90aSdjm 	}
15577045ccdSdjm 	/* success */
15677045ccdSdjm 	return ret;
15777045ccdSdjm fail:
15877045ccdSdjm 	sshsk_free(ret);
15977045ccdSdjm 	return NULL;
16077045ccdSdjm }
16177045ccdSdjm 
16277045ccdSdjm static void
16377045ccdSdjm sshsk_free_enroll_response(struct sk_enroll_response *r)
16477045ccdSdjm {
16577045ccdSdjm 	if (r == NULL)
16677045ccdSdjm 		return;
16777045ccdSdjm 	freezero(r->key_handle, r->key_handle_len);
16877045ccdSdjm 	freezero(r->public_key, r->public_key_len);
16977045ccdSdjm 	freezero(r->signature, r->signature_len);
17077045ccdSdjm 	freezero(r->attestation_cert, r->attestation_cert_len);
171ee0a8761Sdjm 	freezero(r->authdata, r->authdata_len);
17277045ccdSdjm 	freezero(r, sizeof(*r));
17333e48bcdSdjm }
17477045ccdSdjm 
17577045ccdSdjm static void
17677045ccdSdjm sshsk_free_sign_response(struct sk_sign_response *r)
17777045ccdSdjm {
17877045ccdSdjm 	if (r == NULL)
17977045ccdSdjm 		return;
18077045ccdSdjm 	freezero(r->sig_r, r->sig_r_len);
18177045ccdSdjm 	freezero(r->sig_s, r->sig_s_len);
18277045ccdSdjm 	freezero(r, sizeof(*r));
18333e48bcdSdjm }
18477045ccdSdjm 
185f8cd6cb1Snaddy #ifdef WITH_OPENSSL
186e1f7e2e4Smarkus /* Assemble key from response */
187e1f7e2e4Smarkus static int
188e1f7e2e4Smarkus sshsk_ecdsa_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
189e1f7e2e4Smarkus {
190e1f7e2e4Smarkus 	struct sshkey *key = NULL;
191e1f7e2e4Smarkus 	struct sshbuf *b = NULL;
192*5411e769Sdjm 	EC_KEY *ecdsa = NULL;
193e1f7e2e4Smarkus 	EC_POINT *q = NULL;
194*5411e769Sdjm 	const EC_GROUP *g = NULL;
195e1f7e2e4Smarkus 	int r;
196e1f7e2e4Smarkus 
197e1f7e2e4Smarkus 	*keyp = NULL;
198e1f7e2e4Smarkus 	if ((key = sshkey_new(KEY_ECDSA_SK)) == NULL) {
19948e6b99dSdjm 		error_f("sshkey_new failed");
200e1f7e2e4Smarkus 		r = SSH_ERR_ALLOC_FAIL;
201e1f7e2e4Smarkus 		goto out;
202e1f7e2e4Smarkus 	}
203e1f7e2e4Smarkus 	key->ecdsa_nid = NID_X9_62_prime256v1;
204*5411e769Sdjm 	if ((ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid)) == NULL ||
205*5411e769Sdjm 	    (g = EC_KEY_get0_group(ecdsa)) == NULL ||
206*5411e769Sdjm 	    (q = EC_POINT_new(g)) == NULL ||
207e1f7e2e4Smarkus 	    (b = sshbuf_new()) == NULL) {
20848e6b99dSdjm 		error_f("allocation failed");
209e1f7e2e4Smarkus 		r = SSH_ERR_ALLOC_FAIL;
210e1f7e2e4Smarkus 		goto out;
211e1f7e2e4Smarkus 	}
212e1f7e2e4Smarkus 	if ((r = sshbuf_put_string(b,
213e1f7e2e4Smarkus 	    resp->public_key, resp->public_key_len)) != 0) {
21448e6b99dSdjm 		error_fr(r, "sshbuf_put_string");
215e1f7e2e4Smarkus 		goto out;
216e1f7e2e4Smarkus 	}
217*5411e769Sdjm 	if ((r = sshbuf_get_ec(b, q, g)) != 0) {
21848e6b99dSdjm 		error_fr(r, "parse");
219e1f7e2e4Smarkus 		r = SSH_ERR_INVALID_FORMAT;
220e1f7e2e4Smarkus 		goto out;
221e1f7e2e4Smarkus 	}
222*5411e769Sdjm 	if (sshkey_ec_validate_public(g, q) != 0) {
2235f47a660Snaddy 		error("Authenticator returned invalid ECDSA key");
224e1f7e2e4Smarkus 		r = SSH_ERR_KEY_INVALID_EC_VALUE;
225e1f7e2e4Smarkus 		goto out;
226e1f7e2e4Smarkus 	}
227*5411e769Sdjm 	if (EC_KEY_set_public_key(ecdsa, q) != 1) {
228e1f7e2e4Smarkus 		/* XXX assume it is a allocation error */
22948e6b99dSdjm 		error_f("allocation failed");
230e1f7e2e4Smarkus 		r = SSH_ERR_ALLOC_FAIL;
231e1f7e2e4Smarkus 		goto out;
232e1f7e2e4Smarkus 	}
233*5411e769Sdjm 	if ((key->pkey = EVP_PKEY_new()) == NULL) {
234*5411e769Sdjm 		error_f("allocation failed");
235*5411e769Sdjm 		r = SSH_ERR_ALLOC_FAIL;
236*5411e769Sdjm 		goto out;
237*5411e769Sdjm 	}
238*5411e769Sdjm 	if (EVP_PKEY_set1_EC_KEY(key->pkey, ecdsa) != 1) {
239*5411e769Sdjm 		error_f("Assigning EC_KEY failed");
240*5411e769Sdjm 		r = SSH_ERR_LIBCRYPTO_ERROR;
241*5411e769Sdjm 		goto out;
242*5411e769Sdjm 	}
243e1f7e2e4Smarkus 	/* success */
244e1f7e2e4Smarkus 	*keyp = key;
245e1f7e2e4Smarkus 	key = NULL; /* transferred */
246e1f7e2e4Smarkus 	r = 0;
247e1f7e2e4Smarkus  out:
248e1f7e2e4Smarkus 	sshkey_free(key);
249e1f7e2e4Smarkus 	sshbuf_free(b);
250*5411e769Sdjm 	EC_KEY_free(ecdsa);
251*5411e769Sdjm 	EC_POINT_free(q);
252e1f7e2e4Smarkus 	return r;
253e1f7e2e4Smarkus }
254f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
255e1f7e2e4Smarkus 
256af4c80eeSmarkus static int
257af4c80eeSmarkus sshsk_ed25519_assemble(struct sk_enroll_response *resp, struct sshkey **keyp)
258af4c80eeSmarkus {
259af4c80eeSmarkus 	struct sshkey *key = NULL;
260af4c80eeSmarkus 	int r;
261af4c80eeSmarkus 
262af4c80eeSmarkus 	*keyp = NULL;
263af4c80eeSmarkus 	if (resp->public_key_len != ED25519_PK_SZ) {
26448e6b99dSdjm 		error_f("invalid size: %zu", resp->public_key_len);
265af4c80eeSmarkus 		r = SSH_ERR_INVALID_FORMAT;
266af4c80eeSmarkus 		goto out;
267af4c80eeSmarkus 	}
268af4c80eeSmarkus 	if ((key = sshkey_new(KEY_ED25519_SK)) == NULL) {
26948e6b99dSdjm 		error_f("sshkey_new failed");
270af4c80eeSmarkus 		r = SSH_ERR_ALLOC_FAIL;
271af4c80eeSmarkus 		goto out;
272af4c80eeSmarkus 	}
273af4c80eeSmarkus 	if ((key->ed25519_pk = malloc(ED25519_PK_SZ)) == NULL) {
27448e6b99dSdjm 		error_f("malloc failed");
275af4c80eeSmarkus 		r = SSH_ERR_ALLOC_FAIL;
276af4c80eeSmarkus 		goto out;
277af4c80eeSmarkus 	}
278af4c80eeSmarkus 	memcpy(key->ed25519_pk, resp->public_key, ED25519_PK_SZ);
279af4c80eeSmarkus 	/* success */
280af4c80eeSmarkus 	*keyp = key;
281af4c80eeSmarkus 	key = NULL; /* transferred */
282af4c80eeSmarkus 	r = 0;
283af4c80eeSmarkus  out:
284af4c80eeSmarkus 	sshkey_free(key);
285af4c80eeSmarkus 	return r;
286af4c80eeSmarkus }
287af4c80eeSmarkus 
288cb85020cSdjm static int
289cb85020cSdjm sshsk_key_from_response(int alg, const char *application, uint8_t flags,
290cb85020cSdjm     struct sk_enroll_response *resp, struct sshkey **keyp)
291cb85020cSdjm {
292cb85020cSdjm 	struct sshkey *key = NULL;
293cb85020cSdjm 	int r = SSH_ERR_INTERNAL_ERROR;
294cb85020cSdjm 
295cb85020cSdjm 	*keyp = NULL;
296cb85020cSdjm 
297cb85020cSdjm 	/* Check response validity */
2981ac4a90aSdjm 	if (resp->public_key == NULL || resp->key_handle == NULL) {
29948e6b99dSdjm 		error_f("sk_enroll response invalid");
300cb85020cSdjm 		r = SSH_ERR_INVALID_FORMAT;
301cb85020cSdjm 		goto out;
302cb85020cSdjm 	}
303cb85020cSdjm 	switch (alg) {
304cb85020cSdjm #ifdef WITH_OPENSSL
305cb85020cSdjm 	case SSH_SK_ECDSA:
306cb85020cSdjm 		if ((r = sshsk_ecdsa_assemble(resp, &key)) != 0)
307cb85020cSdjm 			goto out;
308cb85020cSdjm 		break;
309cb85020cSdjm #endif /* WITH_OPENSSL */
310cb85020cSdjm 	case SSH_SK_ED25519:
311cb85020cSdjm 		if ((r = sshsk_ed25519_assemble(resp, &key)) != 0)
312cb85020cSdjm 			goto out;
313cb85020cSdjm 		break;
314cb85020cSdjm 	default:
31548e6b99dSdjm 		error_f("unsupported algorithm %d", alg);
316cb85020cSdjm 		r = SSH_ERR_INVALID_ARGUMENT;
317cb85020cSdjm 		goto out;
318cb85020cSdjm 	}
319cb85020cSdjm 	key->sk_flags = flags;
320cb85020cSdjm 	if ((key->sk_key_handle = sshbuf_new()) == NULL ||
321cb85020cSdjm 	    (key->sk_reserved = sshbuf_new()) == NULL) {
32248e6b99dSdjm 		error_f("allocation failed");
323cb85020cSdjm 		r = SSH_ERR_ALLOC_FAIL;
324cb85020cSdjm 		goto out;
325cb85020cSdjm 	}
326cb85020cSdjm 	if ((key->sk_application = strdup(application)) == NULL) {
32748e6b99dSdjm 		error_f("strdup application failed");
328cb85020cSdjm 		r = SSH_ERR_ALLOC_FAIL;
329cb85020cSdjm 		goto out;
330cb85020cSdjm 	}
331cb85020cSdjm 	if ((r = sshbuf_put(key->sk_key_handle, resp->key_handle,
332cb85020cSdjm 	    resp->key_handle_len)) != 0) {
33348e6b99dSdjm 		error_fr(r, "put key handle");
334cb85020cSdjm 		goto out;
335cb85020cSdjm 	}
336cb85020cSdjm 	/* success */
337cb85020cSdjm 	r = 0;
338cb85020cSdjm 	*keyp = key;
339cb85020cSdjm 	key = NULL;
340cb85020cSdjm  out:
341cb85020cSdjm 	sshkey_free(key);
342cb85020cSdjm 	return r;
343cb85020cSdjm }
344cb85020cSdjm 
345480af03fSdjm static int
346480af03fSdjm skerr_to_ssherr(int skerr)
347480af03fSdjm {
348480af03fSdjm 	switch (skerr) {
349480af03fSdjm 	case SSH_SK_ERR_UNSUPPORTED:
350480af03fSdjm 		return SSH_ERR_FEATURE_UNSUPPORTED;
351480af03fSdjm 	case SSH_SK_ERR_PIN_REQUIRED:
352480af03fSdjm 		return SSH_ERR_KEY_WRONG_PASSPHRASE;
353b0297854Sdjm 	case SSH_SK_ERR_DEVICE_NOT_FOUND:
354b0297854Sdjm 		return SSH_ERR_DEVICE_NOT_FOUND;
3554fe47877Sdjm 	case SSH_SK_ERR_CREDENTIAL_EXISTS:
3564fe47877Sdjm 		return SSH_ERR_KEY_BAD_PERMISSIONS;
357480af03fSdjm 	case SSH_SK_ERR_GENERAL:
358480af03fSdjm 	default:
359480af03fSdjm 		return SSH_ERR_INVALID_FORMAT;
360480af03fSdjm 	}
361480af03fSdjm }
362480af03fSdjm 
363a0caf565Sdjm static void
364a0caf565Sdjm sshsk_free_options(struct sk_option **opts)
365a0caf565Sdjm {
366a0caf565Sdjm 	size_t i;
367a0caf565Sdjm 
368a0caf565Sdjm 	if (opts == NULL)
369a0caf565Sdjm 		return;
370a0caf565Sdjm 	for (i = 0; opts[i] != NULL; i++) {
371a0caf565Sdjm 		free(opts[i]->name);
372a0caf565Sdjm 		free(opts[i]->value);
373a0caf565Sdjm 		free(opts[i]);
374a0caf565Sdjm 	}
375a0caf565Sdjm 	free(opts);
376a0caf565Sdjm }
377a0caf565Sdjm 
378a0caf565Sdjm static int
379a0caf565Sdjm sshsk_add_option(struct sk_option ***optsp, size_t *noptsp,
380a0caf565Sdjm     const char *name, const char *value, uint8_t required)
381a0caf565Sdjm {
382a0caf565Sdjm 	struct sk_option **opts = *optsp;
383a0caf565Sdjm 	size_t nopts = *noptsp;
384a0caf565Sdjm 
385a0caf565Sdjm 	if ((opts = recallocarray(opts, nopts, nopts + 2, /* extra for NULL */
386a0caf565Sdjm 	    sizeof(*opts))) == NULL) {
38748e6b99dSdjm 		error_f("array alloc failed");
388a0caf565Sdjm 		return SSH_ERR_ALLOC_FAIL;
389a0caf565Sdjm 	}
390a0caf565Sdjm 	*optsp = opts;
391a0caf565Sdjm 	*noptsp = nopts + 1;
392a0caf565Sdjm 	if ((opts[nopts] = calloc(1, sizeof(**opts))) == NULL) {
39348e6b99dSdjm 		error_f("alloc failed");
394a0caf565Sdjm 		return SSH_ERR_ALLOC_FAIL;
395a0caf565Sdjm 	}
396a0caf565Sdjm 	if ((opts[nopts]->name = strdup(name)) == NULL ||
397a0caf565Sdjm 	    (opts[nopts]->value = strdup(value)) == NULL) {
39848e6b99dSdjm 		error_f("alloc failed");
399a0caf565Sdjm 		return SSH_ERR_ALLOC_FAIL;
400a0caf565Sdjm 	}
401a0caf565Sdjm 	opts[nopts]->required = required;
402a0caf565Sdjm 	return 0;
403a0caf565Sdjm }
404a0caf565Sdjm 
405a0caf565Sdjm static int
406a0caf565Sdjm make_options(const char *device, const char *user_id,
407a0caf565Sdjm     struct sk_option ***optsp)
408a0caf565Sdjm {
409a0caf565Sdjm 	struct sk_option **opts = NULL;
410a0caf565Sdjm 	size_t nopts = 0;
411a0caf565Sdjm 	int r, ret = SSH_ERR_INTERNAL_ERROR;
412a0caf565Sdjm 
413a0caf565Sdjm 	if (device != NULL &&
414a0caf565Sdjm 	    (r = sshsk_add_option(&opts, &nopts, "device", device, 0)) != 0) {
415a0caf565Sdjm 		ret = r;
416a0caf565Sdjm 		goto out;
417a0caf565Sdjm 	}
418a0caf565Sdjm 	if (user_id != NULL &&
419a0caf565Sdjm 	    (r = sshsk_add_option(&opts, &nopts, "user", user_id, 0)) != 0) {
420a0caf565Sdjm 		ret = r;
421a0caf565Sdjm 		goto out;
422a0caf565Sdjm 	}
423a0caf565Sdjm 	/* success */
424a0caf565Sdjm 	*optsp = opts;
425a0caf565Sdjm 	opts = NULL;
426a0caf565Sdjm 	nopts = 0;
427a0caf565Sdjm 	ret = 0;
428a0caf565Sdjm  out:
429a0caf565Sdjm 	sshsk_free_options(opts);
430a0caf565Sdjm 	return ret;
431a0caf565Sdjm }
432a0caf565Sdjm 
433ee0a8761Sdjm 
434ee0a8761Sdjm static int
435ee0a8761Sdjm fill_attestation_blob(const struct sk_enroll_response *resp,
436ee0a8761Sdjm     struct sshbuf *attest)
437ee0a8761Sdjm {
438ee0a8761Sdjm 	int r;
439ee0a8761Sdjm 
440ee0a8761Sdjm 	if (attest == NULL)
441ee0a8761Sdjm 		return 0; /* nothing to do */
442ee0a8761Sdjm 	if ((r = sshbuf_put_cstring(attest, "ssh-sk-attest-v01")) != 0 ||
443ee0a8761Sdjm 	    (r = sshbuf_put_string(attest,
444ee0a8761Sdjm 	    resp->attestation_cert, resp->attestation_cert_len)) != 0 ||
445ee0a8761Sdjm 	    (r = sshbuf_put_string(attest,
446ee0a8761Sdjm 	    resp->signature, resp->signature_len)) != 0 ||
447ee0a8761Sdjm 	    (r = sshbuf_put_string(attest,
448ee0a8761Sdjm 	    resp->authdata, resp->authdata_len)) != 0 ||
449ee0a8761Sdjm 	    (r = sshbuf_put_u32(attest, 0)) != 0 || /* resvd flags */
450ee0a8761Sdjm 	    (r = sshbuf_put_string(attest, NULL, 0)) != 0 /* resvd */) {
45148e6b99dSdjm 		error_fr(r, "compose");
452ee0a8761Sdjm 		return r;
453ee0a8761Sdjm 	}
454ee0a8761Sdjm 	/* success */
455ee0a8761Sdjm 	return 0;
456ee0a8761Sdjm }
457ee0a8761Sdjm 
45877045ccdSdjm int
459a0caf565Sdjm sshsk_enroll(int type, const char *provider_path, const char *device,
460a0caf565Sdjm     const char *application, const char *userid, uint8_t flags,
461a0caf565Sdjm     const char *pin, struct sshbuf *challenge_buf,
4622db06755Sdjm     struct sshkey **keyp, struct sshbuf *attest)
46377045ccdSdjm {
46477045ccdSdjm 	struct sshsk_provider *skp = NULL;
46577045ccdSdjm 	struct sshkey *key = NULL;
46677045ccdSdjm 	u_char randchall[32];
46777045ccdSdjm 	const u_char *challenge;
46877045ccdSdjm 	size_t challenge_len;
46977045ccdSdjm 	struct sk_enroll_response *resp = NULL;
470a0caf565Sdjm 	struct sk_option **opts = NULL;
47177045ccdSdjm 	int r = SSH_ERR_INTERNAL_ERROR;
472a3dd6837Smarkus 	int alg;
47377045ccdSdjm 
47448e6b99dSdjm 	debug_f("provider \"%s\", device \"%s\", application \"%s\", "
47548e6b99dSdjm 	    "userid \"%s\", flags 0x%02x, challenge len %zu%s",
476a0caf565Sdjm 	    provider_path, device, application, userid, flags,
477a0caf565Sdjm 	    challenge_buf == NULL ? 0 : sshbuf_len(challenge_buf),
4782db06755Sdjm 	    (pin != NULL && *pin != '\0') ? " with-pin" : "");
479c6495727Sdjm 
48077045ccdSdjm 	*keyp = NULL;
48177045ccdSdjm 	if (attest)
48277045ccdSdjm 		sshbuf_reset(attest);
483a0caf565Sdjm 
484a0caf565Sdjm 	if ((r = make_options(device, userid, &opts)) != 0)
485a0caf565Sdjm 		goto out;
486a0caf565Sdjm 
487af4c80eeSmarkus 	switch (type) {
488f8cd6cb1Snaddy #ifdef WITH_OPENSSL
489af4c80eeSmarkus 	case KEY_ECDSA_SK:
490a3dd6837Smarkus 		alg = SSH_SK_ECDSA;
491a3dd6837Smarkus 		break;
492f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
493af4c80eeSmarkus 	case KEY_ED25519_SK:
494a3dd6837Smarkus 		alg = SSH_SK_ED25519;
495af4c80eeSmarkus 		break;
496af4c80eeSmarkus 	default:
49748e6b99dSdjm 		error_f("unsupported key type");
498af4c80eeSmarkus 		r = SSH_ERR_INVALID_ARGUMENT;
499af4c80eeSmarkus 		goto out;
500af4c80eeSmarkus 	}
50177045ccdSdjm 	if (provider_path == NULL) {
50248e6b99dSdjm 		error_f("missing provider");
50377045ccdSdjm 		r = SSH_ERR_INVALID_ARGUMENT;
50477045ccdSdjm 		goto out;
50577045ccdSdjm 	}
50677045ccdSdjm 	if (application == NULL || *application == '\0') {
50748e6b99dSdjm 		error_f("missing application");
50877045ccdSdjm 		r = SSH_ERR_INVALID_ARGUMENT;
50977045ccdSdjm 		goto out;
51077045ccdSdjm 	}
51177045ccdSdjm 	if (challenge_buf == NULL) {
51248e6b99dSdjm 		debug_f("using random challenge");
51377045ccdSdjm 		arc4random_buf(randchall, sizeof(randchall));
51477045ccdSdjm 		challenge = randchall;
51577045ccdSdjm 		challenge_len = sizeof(randchall);
51677045ccdSdjm 	} else if (sshbuf_len(challenge_buf) == 0) {
51777045ccdSdjm 		error("Missing enrollment challenge");
51877045ccdSdjm 		r = SSH_ERR_INVALID_ARGUMENT;
51977045ccdSdjm 		goto out;
52077045ccdSdjm 	} else {
52177045ccdSdjm 		challenge = sshbuf_ptr(challenge_buf);
52277045ccdSdjm 		challenge_len = sshbuf_len(challenge_buf);
52348e6b99dSdjm 		debug3_f("using explicit challenge len=%zd", challenge_len);
52477045ccdSdjm 	}
52577045ccdSdjm 	if ((skp = sshsk_open(provider_path)) == NULL) {
52677045ccdSdjm 		r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
52777045ccdSdjm 		goto out;
52877045ccdSdjm 	}
52977045ccdSdjm 	/* XXX validate flags? */
53077045ccdSdjm 	/* enroll key */
531a3dd6837Smarkus 	if ((r = skp->sk_enroll(alg, challenge, challenge_len, application,
532a0caf565Sdjm 	    flags, pin, opts, &resp)) != 0) {
53348e6b99dSdjm 		debug_f("provider \"%s\" failure %d", provider_path, r);
534480af03fSdjm 		r = skerr_to_ssherr(r);
53577045ccdSdjm 		goto out;
53677045ccdSdjm 	}
537cb85020cSdjm 
53818b3d906Sdjm 	if ((r = sshsk_key_from_response(alg, application, resp->flags,
539cb85020cSdjm 	    resp, &key)) != 0)
54077045ccdSdjm 		goto out;
541cb85020cSdjm 
54277045ccdSdjm 	/* Optionally fill in the attestation information */
543ee0a8761Sdjm 	if ((r = fill_attestation_blob(resp, attest)) != 0)
54477045ccdSdjm 		goto out;
545ee0a8761Sdjm 
54677045ccdSdjm 	/* success */
54777045ccdSdjm 	*keyp = key;
54877045ccdSdjm 	key = NULL; /* transferred */
54977045ccdSdjm 	r = 0;
55077045ccdSdjm  out:
551a0caf565Sdjm 	sshsk_free_options(opts);
55277045ccdSdjm 	sshsk_free(skp);
55377045ccdSdjm 	sshkey_free(key);
55477045ccdSdjm 	sshsk_free_enroll_response(resp);
55577045ccdSdjm 	explicit_bzero(randchall, sizeof(randchall));
55677045ccdSdjm 	return r;
55777045ccdSdjm }
55877045ccdSdjm 
559f8cd6cb1Snaddy #ifdef WITH_OPENSSL
560f4a621d5Smarkus static int
56137ada6ffSmarkus sshsk_ecdsa_sig(struct sk_sign_response *resp, struct sshbuf *sig)
562f4a621d5Smarkus {
563f4a621d5Smarkus 	struct sshbuf *inner_sig = NULL;
564f4a621d5Smarkus 	int r = SSH_ERR_INTERNAL_ERROR;
565f4a621d5Smarkus 
56688d9b866Smarkus 	/* Check response validity */
567a777987dSmarkus 	if (resp->sig_r == NULL || resp->sig_s == NULL) {
56848e6b99dSdjm 		error_f("sk_sign response invalid");
56988d9b866Smarkus 		r = SSH_ERR_INVALID_FORMAT;
57088d9b866Smarkus 		goto out;
57188d9b866Smarkus 	}
572f4a621d5Smarkus 	if ((inner_sig = sshbuf_new()) == NULL) {
573f4a621d5Smarkus 		r = SSH_ERR_ALLOC_FAIL;
574f4a621d5Smarkus 		goto out;
575f4a621d5Smarkus 	}
57637ada6ffSmarkus 	/* Prepare and append inner signature object */
577f4a621d5Smarkus 	if ((r = sshbuf_put_bignum2_bytes(inner_sig,
578f4a621d5Smarkus 	    resp->sig_r, resp->sig_r_len)) != 0 ||
579f4a621d5Smarkus 	    (r = sshbuf_put_bignum2_bytes(inner_sig,
580039d6aaeSdjm 	    resp->sig_s, resp->sig_s_len)) != 0) {
58148e6b99dSdjm 		error_fr(r, "compose inner");
582f4a621d5Smarkus 		goto out;
583f4a621d5Smarkus 	}
584039d6aaeSdjm 	if ((r = sshbuf_put_stringb(sig, inner_sig)) != 0 ||
585039d6aaeSdjm 	    (r = sshbuf_put_u8(sig, resp->flags)) != 0 ||
586039d6aaeSdjm 	    (r = sshbuf_put_u32(sig, resp->counter)) != 0) {
58748e6b99dSdjm 		error_fr(r, "compose");
58837ada6ffSmarkus 		goto out;
58937ada6ffSmarkus 	}
590f4a621d5Smarkus #ifdef DEBUG_SK
591f4a621d5Smarkus 	fprintf(stderr, "%s: sig_r:\n", __func__);
592f4a621d5Smarkus 	sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
593f4a621d5Smarkus 	fprintf(stderr, "%s: sig_s:\n", __func__);
594f4a621d5Smarkus 	sshbuf_dump_data(resp->sig_s, resp->sig_s_len, stderr);
59537ada6ffSmarkus 	fprintf(stderr, "%s: inner:\n", __func__);
59637ada6ffSmarkus 	sshbuf_dump(inner_sig, stderr);
597dfd98029Smarkus #endif
598dfd98029Smarkus 	r = 0;
599dfd98029Smarkus  out:
600dfd98029Smarkus 	sshbuf_free(inner_sig);
601dfd98029Smarkus 	return r;
602dfd98029Smarkus }
603f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
604dfd98029Smarkus 
605dfd98029Smarkus static int
60637ada6ffSmarkus sshsk_ed25519_sig(struct sk_sign_response *resp, struct sshbuf *sig)
607dfd98029Smarkus {
608dfd98029Smarkus 	int r = SSH_ERR_INTERNAL_ERROR;
609dfd98029Smarkus 
61088d9b866Smarkus 	/* Check response validity */
61188d9b866Smarkus 	if (resp->sig_r == NULL) {
61248e6b99dSdjm 		error_f("sk_sign response invalid");
61388d9b866Smarkus 		r = SSH_ERR_INVALID_FORMAT;
61488d9b866Smarkus 		goto out;
61588d9b866Smarkus 	}
61637ada6ffSmarkus 	if ((r = sshbuf_put_string(sig,
617dfd98029Smarkus 	    resp->sig_r, resp->sig_r_len)) != 0 ||
61837ada6ffSmarkus 	    (r = sshbuf_put_u8(sig, resp->flags)) != 0 ||
61937ada6ffSmarkus 	    (r = sshbuf_put_u32(sig, resp->counter)) != 0) {
62048e6b99dSdjm 		error_fr(r, "compose");
621dfd98029Smarkus 		goto out;
622dfd98029Smarkus 	}
623dfd98029Smarkus #ifdef DEBUG_SK
624dfd98029Smarkus 	fprintf(stderr, "%s: sig_r:\n", __func__);
625dfd98029Smarkus 	sshbuf_dump_data(resp->sig_r, resp->sig_r_len, stderr);
626f4a621d5Smarkus #endif
627f4a621d5Smarkus 	r = 0;
628f4a621d5Smarkus  out:
62923d69673Smarkus 	return r;
630f4a621d5Smarkus }
631f4a621d5Smarkus 
63277045ccdSdjm int
6334852100aSdjm sshsk_sign(const char *provider_path, struct sshkey *key,
63477045ccdSdjm     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
6352db06755Sdjm     u_int compat, const char *pin)
63677045ccdSdjm {
63777045ccdSdjm 	struct sshsk_provider *skp = NULL;
63877045ccdSdjm 	int r = SSH_ERR_INTERNAL_ERROR;
639a3dd6837Smarkus 	int type, alg;
64077045ccdSdjm 	struct sk_sign_response *resp = NULL;
64177045ccdSdjm 	struct sshbuf *inner_sig = NULL, *sig = NULL;
642a0caf565Sdjm 	struct sk_option **opts = NULL;
64377045ccdSdjm 
64448e6b99dSdjm 	debug_f("provider \"%s\", key %s, flags 0x%02x%s",
6452db06755Sdjm 	    provider_path, sshkey_type(key), key->sk_flags,
6462db06755Sdjm 	    (pin != NULL && *pin != '\0') ? " with-pin" : "");
647c6495727Sdjm 
64877045ccdSdjm 	if (sigp != NULL)
64977045ccdSdjm 		*sigp = NULL;
65077045ccdSdjm 	if (lenp != NULL)
65177045ccdSdjm 		*lenp = 0;
652dfd98029Smarkus 	type = sshkey_type_plain(key->type);
653dfd98029Smarkus 	switch (type) {
654f8cd6cb1Snaddy #ifdef WITH_OPENSSL
655dfd98029Smarkus 	case KEY_ECDSA_SK:
656a3dd6837Smarkus 		alg = SSH_SK_ECDSA;
657a3dd6837Smarkus 		break;
658f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
659dfd98029Smarkus 	case KEY_ED25519_SK:
660a3dd6837Smarkus 		alg = SSH_SK_ED25519;
661dfd98029Smarkus 		break;
662dfd98029Smarkus 	default:
663dfd98029Smarkus 		return SSH_ERR_INVALID_ARGUMENT;
664dfd98029Smarkus 	}
66577045ccdSdjm 	if (provider_path == NULL ||
66677045ccdSdjm 	    key->sk_key_handle == NULL ||
66777045ccdSdjm 	    key->sk_application == NULL || *key->sk_application == '\0') {
66877045ccdSdjm 		r = SSH_ERR_INVALID_ARGUMENT;
66977045ccdSdjm 		goto out;
67077045ccdSdjm 	}
67177045ccdSdjm 	if ((skp = sshsk_open(provider_path)) == NULL) {
67277045ccdSdjm 		r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
67377045ccdSdjm 		goto out;
67477045ccdSdjm 	}
6750b5f6ed5Sdjm #ifdef DEBUG_SK
6760b5f6ed5Sdjm 	fprintf(stderr, "%s: sk_flags = 0x%02x, sk_application = \"%s\"\n",
6770b5f6ed5Sdjm 	    __func__, key->sk_flags, key->sk_application);
6780b5f6ed5Sdjm 	fprintf(stderr, "%s: sk_key_handle:\n", __func__);
6790b5f6ed5Sdjm 	sshbuf_dump(key->sk_key_handle, stderr);
6800b5f6ed5Sdjm #endif
6813ce2af41Sdjm 	if ((r = skp->sk_sign(alg, data, datalen, key->sk_application,
68277045ccdSdjm 	    sshbuf_ptr(key->sk_key_handle), sshbuf_len(key->sk_key_handle),
683a0caf565Sdjm 	    key->sk_flags, pin, opts, &resp)) != 0) {
68448e6b99dSdjm 		debug_f("sk_sign failed with code %d", r);
685480af03fSdjm 		r = skerr_to_ssherr(r);
68677045ccdSdjm 		goto out;
68777045ccdSdjm 	}
68837ada6ffSmarkus 	/* Assemble signature */
689f4a621d5Smarkus 	if ((sig = sshbuf_new()) == NULL) {
69077045ccdSdjm 		r = SSH_ERR_ALLOC_FAIL;
69177045ccdSdjm 		goto out;
69277045ccdSdjm 	}
69337ada6ffSmarkus 	if ((r = sshbuf_put_cstring(sig, sshkey_ssh_name_plain(key))) != 0) {
69448e6b99dSdjm 		error_fr(r, "compose outer");
69577045ccdSdjm 		goto out;
69677045ccdSdjm 	}
69737ada6ffSmarkus 	switch (type) {
698f8cd6cb1Snaddy #ifdef WITH_OPENSSL
69937ada6ffSmarkus 	case KEY_ECDSA_SK:
70037ada6ffSmarkus 		if ((r = sshsk_ecdsa_sig(resp, sig)) != 0)
70137ada6ffSmarkus 			goto out;
70237ada6ffSmarkus 		break;
703f8cd6cb1Snaddy #endif /* WITH_OPENSSL */
70437ada6ffSmarkus 	case KEY_ED25519_SK:
70537ada6ffSmarkus 		if ((r = sshsk_ed25519_sig(resp, sig)) != 0)
70637ada6ffSmarkus 			goto out;
70737ada6ffSmarkus 		break;
70837ada6ffSmarkus 	}
70977045ccdSdjm #ifdef DEBUG_SK
710dfd98029Smarkus 	fprintf(stderr, "%s: sig_flags = 0x%02x, sig_counter = %u\n",
711dfd98029Smarkus 	    __func__, resp->flags, resp->counter);
7126239c001Sdjm 	fprintf(stderr, "%s: data to sign:\n", __func__);
7136239c001Sdjm 	sshbuf_dump_data(data, datalen, stderr);
71477045ccdSdjm 	fprintf(stderr, "%s: sigbuf:\n", __func__);
71577045ccdSdjm 	sshbuf_dump(sig, stderr);
71677045ccdSdjm #endif
71777045ccdSdjm 	if (sigp != NULL) {
71877045ccdSdjm 		if ((*sigp = malloc(sshbuf_len(sig))) == NULL) {
71977045ccdSdjm 			r = SSH_ERR_ALLOC_FAIL;
72077045ccdSdjm 			goto out;
72177045ccdSdjm 		}
72277045ccdSdjm 		memcpy(*sigp, sshbuf_ptr(sig), sshbuf_len(sig));
72377045ccdSdjm 	}
72477045ccdSdjm 	if (lenp != NULL)
72577045ccdSdjm 		*lenp = sshbuf_len(sig);
72677045ccdSdjm 	/* success */
72777045ccdSdjm 	r = 0;
72877045ccdSdjm  out:
729a0caf565Sdjm 	sshsk_free_options(opts);
73077045ccdSdjm 	sshsk_free(skp);
73177045ccdSdjm 	sshsk_free_sign_response(resp);
73277045ccdSdjm 	sshbuf_free(sig);
73377045ccdSdjm 	sshbuf_free(inner_sig);
73477045ccdSdjm 	return r;
73577045ccdSdjm }
7361ac4a90aSdjm 
7371ac4a90aSdjm static void
7381ac4a90aSdjm sshsk_free_sk_resident_keys(struct sk_resident_key **rks, size_t nrks)
7391ac4a90aSdjm {
7401ac4a90aSdjm 	size_t i;
7411ac4a90aSdjm 
7421ac4a90aSdjm 	if (nrks == 0 || rks == NULL)
7431ac4a90aSdjm 		return;
7441ac4a90aSdjm 	for (i = 0; i < nrks; i++) {
7451ac4a90aSdjm 		free(rks[i]->application);
7460081f855Sdjm 		freezero(rks[i]->user_id, rks[i]->user_id_len);
7471ac4a90aSdjm 		freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len);
7481ac4a90aSdjm 		freezero(rks[i]->key.public_key, rks[i]->key.public_key_len);
7491ac4a90aSdjm 		freezero(rks[i]->key.signature, rks[i]->key.signature_len);
7501ac4a90aSdjm 		freezero(rks[i]->key.attestation_cert,
7511ac4a90aSdjm 		    rks[i]->key.attestation_cert_len);
7521ac4a90aSdjm 		freezero(rks[i], sizeof(**rks));
7531ac4a90aSdjm 	}
7541ac4a90aSdjm 	free(rks);
7551ac4a90aSdjm }
7561ac4a90aSdjm 
757991d5a20Sdjm static void
758991d5a20Sdjm sshsk_free_resident_key(struct sshsk_resident_key *srk)
759991d5a20Sdjm {
760991d5a20Sdjm 	if (srk == NULL)
761991d5a20Sdjm 		return;
762991d5a20Sdjm 	sshkey_free(srk->key);
763991d5a20Sdjm 	freezero(srk->user_id, srk->user_id_len);
764991d5a20Sdjm 	free(srk);
765991d5a20Sdjm }
766991d5a20Sdjm 
767991d5a20Sdjm 
768991d5a20Sdjm void
769991d5a20Sdjm sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
770991d5a20Sdjm {
771991d5a20Sdjm 	size_t i;
772991d5a20Sdjm 
773991d5a20Sdjm 	if (srks == NULL || nsrks == 0)
774991d5a20Sdjm 		return;
775991d5a20Sdjm 
776991d5a20Sdjm 	for (i = 0; i < nsrks; i++)
777991d5a20Sdjm 		sshsk_free_resident_key(srks[i]);
778991d5a20Sdjm 	free(srks);
779991d5a20Sdjm }
780991d5a20Sdjm 
7811ac4a90aSdjm int
782a0caf565Sdjm sshsk_load_resident(const char *provider_path, const char *device,
783991d5a20Sdjm     const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
784991d5a20Sdjm     size_t *nsrksp)
7851ac4a90aSdjm {
7861ac4a90aSdjm 	struct sshsk_provider *skp = NULL;
7871ac4a90aSdjm 	int r = SSH_ERR_INTERNAL_ERROR;
7881ac4a90aSdjm 	struct sk_resident_key **rks = NULL;
789991d5a20Sdjm 	size_t i, nrks = 0, nsrks = 0;
790991d5a20Sdjm 	struct sshkey *key = NULL;
791991d5a20Sdjm 	struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
792991d5a20Sdjm 	uint8_t sk_flags;
793a0caf565Sdjm 	struct sk_option **opts = NULL;
7941ac4a90aSdjm 
79548e6b99dSdjm 	debug_f("provider \"%s\"%s", provider_path,
7961ac4a90aSdjm 	    (pin != NULL && *pin != '\0') ? ", have-pin": "");
7971ac4a90aSdjm 
798991d5a20Sdjm 	if (srksp == NULL || nsrksp == NULL)
7991ac4a90aSdjm 		return SSH_ERR_INVALID_ARGUMENT;
800991d5a20Sdjm 	*srksp = NULL;
801991d5a20Sdjm 	*nsrksp = 0;
8021ac4a90aSdjm 
803a0caf565Sdjm 	if ((r = make_options(device, NULL, &opts)) != 0)
804a0caf565Sdjm 		goto out;
8051ac4a90aSdjm 	if ((skp = sshsk_open(provider_path)) == NULL) {
8061ac4a90aSdjm 		r = SSH_ERR_INVALID_FORMAT; /* XXX sshsk_open return code? */
8071ac4a90aSdjm 		goto out;
8081ac4a90aSdjm 	}
809a0caf565Sdjm 	if ((r = skp->sk_load_resident_keys(pin, opts, &rks, &nrks)) != 0) {
8105f47a660Snaddy 		error("Provider \"%s\" returned failure %d", provider_path, r);
811480af03fSdjm 		r = skerr_to_ssherr(r);
8121ac4a90aSdjm 		goto out;
8131ac4a90aSdjm 	}
8141ac4a90aSdjm 	for (i = 0; i < nrks; i++) {
815991d5a20Sdjm 		debug3_f("rk %zu: slot %zu, alg %d, app \"%s\", uidlen %zu",
816991d5a20Sdjm 		    i, rks[i]->slot, rks[i]->alg, rks[i]->application,
817991d5a20Sdjm 		    rks[i]->user_id_len);
8181ac4a90aSdjm 		/* XXX need better filter here */
8191ac4a90aSdjm 		if (strncmp(rks[i]->application, "ssh:", 4) != 0)
8201ac4a90aSdjm 			continue;
8211ac4a90aSdjm 		switch (rks[i]->alg) {
8221ac4a90aSdjm 		case SSH_SK_ECDSA:
8231ac4a90aSdjm 		case SSH_SK_ED25519:
8241ac4a90aSdjm 			break;
8251ac4a90aSdjm 		default:
8261ac4a90aSdjm 			continue;
8271ac4a90aSdjm 		}
828991d5a20Sdjm 		sk_flags = SSH_SK_USER_PRESENCE_REQD|SSH_SK_RESIDENT_KEY;
82915a2cdb6Sdjm 		if ((rks[i]->flags & SSH_SK_USER_VERIFICATION_REQD))
830991d5a20Sdjm 			sk_flags |= SSH_SK_USER_VERIFICATION_REQD;
8311ac4a90aSdjm 		if ((r = sshsk_key_from_response(rks[i]->alg,
832991d5a20Sdjm 		    rks[i]->application, sk_flags, &rks[i]->key, &key)) != 0)
8331ac4a90aSdjm 			goto out;
834991d5a20Sdjm 		if ((srk = calloc(1, sizeof(*srk))) == NULL) {
835991d5a20Sdjm 			error_f("calloc failed");
836991d5a20Sdjm 			r = SSH_ERR_ALLOC_FAIL;
837991d5a20Sdjm 			goto out;
838991d5a20Sdjm 		}
839991d5a20Sdjm 		srk->key = key;
840991d5a20Sdjm 		key = NULL; /* transferred */
841991d5a20Sdjm 		if ((srk->user_id = calloc(1, rks[i]->user_id_len)) == NULL) {
842991d5a20Sdjm 			error_f("calloc failed");
843991d5a20Sdjm 			r = SSH_ERR_ALLOC_FAIL;
844991d5a20Sdjm 			goto out;
845991d5a20Sdjm 		}
846991d5a20Sdjm 		memcpy(srk->user_id, rks[i]->user_id, rks[i]->user_id_len);
847991d5a20Sdjm 		srk->user_id_len = rks[i]->user_id_len;
848991d5a20Sdjm 		if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
8491ac4a90aSdjm 		    sizeof(*tmp))) == NULL) {
85048e6b99dSdjm 			error_f("recallocarray failed");
8511ac4a90aSdjm 			r = SSH_ERR_ALLOC_FAIL;
8521ac4a90aSdjm 			goto out;
8531ac4a90aSdjm 		}
854991d5a20Sdjm 		srks = tmp;
855991d5a20Sdjm 		srks[nsrks++] = srk;
856991d5a20Sdjm 		srk = NULL;
8571ac4a90aSdjm 		/* XXX synthesise comment */
8581ac4a90aSdjm 	}
8591ac4a90aSdjm 	/* success */
860991d5a20Sdjm 	*srksp = srks;
861991d5a20Sdjm 	*nsrksp = nsrks;
862991d5a20Sdjm 	srks = NULL;
863991d5a20Sdjm 	nsrks = 0;
8641ac4a90aSdjm 	r = 0;
8651ac4a90aSdjm  out:
866a0caf565Sdjm 	sshsk_free_options(opts);
8671ac4a90aSdjm 	sshsk_free(skp);
8681ac4a90aSdjm 	sshsk_free_sk_resident_keys(rks, nrks);
8691ac4a90aSdjm 	sshkey_free(key);
870991d5a20Sdjm 	sshsk_free_resident_key(srk);
871991d5a20Sdjm 	sshsk_free_resident_keys(srks, nsrks);
8721ac4a90aSdjm 	return r;
8731ac4a90aSdjm }
8741ac4a90aSdjm 
875