xref: /openbsd-src/usr.bin/ssh/ssh-sk-client.c (revision 31c848ccf341175fda2a6424d8d70c6b49a9b720)
1*31c848ccSdjm /* $OpenBSD: ssh-sk-client.c,v 1.12 2022/01/14 03:34:00 djm Exp $ */
215704ed2Sdjm /*
315704ed2Sdjm  * Copyright (c) 2019 Google LLC
415704ed2Sdjm  *
515704ed2Sdjm  * Permission to use, copy, modify, and distribute this software for any
615704ed2Sdjm  * purpose with or without fee is hereby granted, provided that the above
715704ed2Sdjm  * copyright notice and this permission notice appear in all copies.
815704ed2Sdjm  *
915704ed2Sdjm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1015704ed2Sdjm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1115704ed2Sdjm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1215704ed2Sdjm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1315704ed2Sdjm  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1415704ed2Sdjm  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515704ed2Sdjm  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1615704ed2Sdjm  */
1715704ed2Sdjm 
1815704ed2Sdjm #include <sys/types.h>
1915704ed2Sdjm #include <sys/socket.h>
2015704ed2Sdjm #include <sys/wait.h>
2115704ed2Sdjm 
22fb247a26Sdjm #include <fcntl.h>
232db06755Sdjm #include <limits.h>
2415704ed2Sdjm #include <errno.h>
2515704ed2Sdjm #include <signal.h>
2615704ed2Sdjm #include <stdarg.h>
2715704ed2Sdjm #include <stdio.h>
2815704ed2Sdjm #include <stdlib.h>
2915704ed2Sdjm #include <string.h>
3015704ed2Sdjm #include <unistd.h>
3115704ed2Sdjm 
3215704ed2Sdjm #include "log.h"
3315704ed2Sdjm #include "ssherr.h"
3415704ed2Sdjm #include "sshbuf.h"
3515704ed2Sdjm #include "sshkey.h"
3615704ed2Sdjm #include "msg.h"
3715704ed2Sdjm #include "digest.h"
3815704ed2Sdjm #include "pathnames.h"
3915704ed2Sdjm #include "ssh-sk.h"
40e9716d4dSdtucker #include "misc.h"
4115704ed2Sdjm 
4215704ed2Sdjm /* #define DEBUG_SK 1 */
4315704ed2Sdjm 
4415704ed2Sdjm static int
start_helper(int * fdp,pid_t * pidp,void (** osigchldp)(int))4515704ed2Sdjm start_helper(int *fdp, pid_t *pidp, void (**osigchldp)(int))
4615704ed2Sdjm {
4715704ed2Sdjm 	void (*osigchld)(int);
4848e6b99dSdjm 	int oerrno, pair[2];
4915704ed2Sdjm 	pid_t pid;
5015704ed2Sdjm 	char *helper, *verbosity = NULL;
5115704ed2Sdjm 
5215704ed2Sdjm 	*fdp = -1;
5315704ed2Sdjm 	*pidp = 0;
5415704ed2Sdjm 	*osigchldp = SIG_DFL;
5515704ed2Sdjm 
5615704ed2Sdjm 	helper = getenv("SSH_SK_HELPER");
5715704ed2Sdjm 	if (helper == NULL || strlen(helper) == 0)
5815704ed2Sdjm 		helper = _PATH_SSH_SK_HELPER;
59fb247a26Sdjm 	if (access(helper, X_OK) != 0) {
60fb247a26Sdjm 		oerrno = errno;
6148e6b99dSdjm 		error_f("helper \"%s\" unusable: %s", helper, strerror(errno));
62fb247a26Sdjm 		errno = oerrno;
63fb247a26Sdjm 		return SSH_ERR_SYSTEM_ERROR;
64fb247a26Sdjm 	}
6515704ed2Sdjm #ifdef DEBUG_SK
6615704ed2Sdjm 	verbosity = "-vvv";
6715704ed2Sdjm #endif
6815704ed2Sdjm 
6915704ed2Sdjm 	/* Start helper */
7015704ed2Sdjm 	if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
7115704ed2Sdjm 		error("socketpair: %s", strerror(errno));
7215704ed2Sdjm 		return SSH_ERR_SYSTEM_ERROR;
7315704ed2Sdjm 	}
74e9716d4dSdtucker 	osigchld = ssh_signal(SIGCHLD, SIG_DFL);
7515704ed2Sdjm 	if ((pid = fork()) == -1) {
7615704ed2Sdjm 		oerrno = errno;
7715704ed2Sdjm 		error("fork: %s", strerror(errno));
7815704ed2Sdjm 		close(pair[0]);
7915704ed2Sdjm 		close(pair[1]);
80e9716d4dSdtucker 		ssh_signal(SIGCHLD, osigchld);
8115704ed2Sdjm 		errno = oerrno;
8215704ed2Sdjm 		return SSH_ERR_SYSTEM_ERROR;
8315704ed2Sdjm 	}
8415704ed2Sdjm 	if (pid == 0) {
8515704ed2Sdjm 		if ((dup2(pair[1], STDIN_FILENO) == -1) ||
8615704ed2Sdjm 		    (dup2(pair[1], STDOUT_FILENO) == -1)) {
8748e6b99dSdjm 			error_f("dup2: %s", strerror(errno));
8815704ed2Sdjm 			_exit(1);
8915704ed2Sdjm 		}
9015704ed2Sdjm 		close(pair[0]);
9115704ed2Sdjm 		close(pair[1]);
9215704ed2Sdjm 		closefrom(STDERR_FILENO + 1);
9348e6b99dSdjm 		debug_f("starting %s %s", helper,
9415704ed2Sdjm 		    verbosity == NULL ? "" : verbosity);
9515704ed2Sdjm 		execlp(helper, helper, verbosity, (char *)NULL);
9648e6b99dSdjm 		error_f("execlp: %s", strerror(errno));
9715704ed2Sdjm 		_exit(1);
9815704ed2Sdjm 	}
9915704ed2Sdjm 	close(pair[1]);
10015704ed2Sdjm 
10115704ed2Sdjm 	/* success */
10248e6b99dSdjm 	debug3_f("started pid=%ld", (long)pid);
10315704ed2Sdjm 	*fdp = pair[0];
10415704ed2Sdjm 	*pidp = pid;
10515704ed2Sdjm 	*osigchldp = osigchld;
10615704ed2Sdjm 	return 0;
10715704ed2Sdjm }
10815704ed2Sdjm 
10915704ed2Sdjm static int
reap_helper(pid_t pid)11015704ed2Sdjm reap_helper(pid_t pid)
11115704ed2Sdjm {
11215704ed2Sdjm 	int status, oerrno;
11315704ed2Sdjm 
11448e6b99dSdjm 	debug3_f("pid=%ld", (long)pid);
11515704ed2Sdjm 
11615704ed2Sdjm 	errno = 0;
11715704ed2Sdjm 	while (waitpid(pid, &status, 0) == -1) {
11815704ed2Sdjm 		if (errno == EINTR) {
11915704ed2Sdjm 			errno = 0;
12015704ed2Sdjm 			continue;
12115704ed2Sdjm 		}
12215704ed2Sdjm 		oerrno = errno;
12348e6b99dSdjm 		error_f("waitpid: %s", strerror(errno));
12415704ed2Sdjm 		errno = oerrno;
12515704ed2Sdjm 		return SSH_ERR_SYSTEM_ERROR;
12615704ed2Sdjm 	}
12715704ed2Sdjm 	if (!WIFEXITED(status)) {
12848e6b99dSdjm 		error_f("helper exited abnormally");
12915704ed2Sdjm 		return SSH_ERR_AGENT_FAILURE;
13015704ed2Sdjm 	} else if (WEXITSTATUS(status) != 0) {
13148e6b99dSdjm 		error_f("helper exited with non-zero exit status");
13215704ed2Sdjm 		return SSH_ERR_AGENT_FAILURE;
13315704ed2Sdjm 	}
13415704ed2Sdjm 	return 0;
13515704ed2Sdjm }
13615704ed2Sdjm 
13715704ed2Sdjm static int
client_converse(struct sshbuf * msg,struct sshbuf ** respp,u_int type)138a769387cSdjm client_converse(struct sshbuf *msg, struct sshbuf **respp, u_int type)
13915704ed2Sdjm {
140a769387cSdjm 	int oerrno, fd, r2, ll, r = SSH_ERR_INTERNAL_ERROR;
141a769387cSdjm 	u_int rtype, rerr;
14215704ed2Sdjm 	pid_t pid;
14315704ed2Sdjm 	u_char version;
14415704ed2Sdjm 	void (*osigchld)(int);
145a769387cSdjm 	struct sshbuf *req = NULL, *resp = NULL;
14615704ed2Sdjm 	*respp = NULL;
14715704ed2Sdjm 
14815704ed2Sdjm 	if ((r = start_helper(&fd, &pid, &osigchld)) != 0)
14915704ed2Sdjm 		return r;
15015704ed2Sdjm 
151a769387cSdjm 	if ((req = sshbuf_new()) == NULL || (resp = sshbuf_new()) == NULL) {
15215704ed2Sdjm 		r = SSH_ERR_ALLOC_FAIL;
15315704ed2Sdjm 		goto out;
15415704ed2Sdjm 	}
155a769387cSdjm 	/* Request preamble: type, log_on_stderr, log_level */
156a769387cSdjm 	ll = log_level_get();
157a769387cSdjm 	if ((r = sshbuf_put_u32(req, type)) != 0 ||
158a769387cSdjm 	    (r = sshbuf_put_u8(req, log_is_on_stderr() != 0)) != 0 ||
159a769387cSdjm 	    (r = sshbuf_put_u32(req, ll < 0 ? 0 : ll)) != 0 ||
160a769387cSdjm 	    (r = sshbuf_putb(req, msg)) != 0) {
16148e6b99dSdjm 		error_fr(r, "compose");
162a769387cSdjm 		goto out;
163a769387cSdjm 	}
16415704ed2Sdjm 	if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) {
16548e6b99dSdjm 		error_fr(r, "send");
16615704ed2Sdjm 		goto out;
16715704ed2Sdjm 	}
16815704ed2Sdjm 	if ((r = ssh_msg_recv(fd, resp)) != 0) {
16948e6b99dSdjm 		error_fr(r, "receive");
17015704ed2Sdjm 		goto out;
17115704ed2Sdjm 	}
17215704ed2Sdjm 	if ((r = sshbuf_get_u8(resp, &version)) != 0) {
17348e6b99dSdjm 		error_fr(r, "parse version");
17415704ed2Sdjm 		goto out;
17515704ed2Sdjm 	}
17615704ed2Sdjm 	if (version != SSH_SK_HELPER_VERSION) {
17748e6b99dSdjm 		error_f("unsupported version: got %u, expected %u",
17848e6b99dSdjm 		    version, SSH_SK_HELPER_VERSION);
17915704ed2Sdjm 		r = SSH_ERR_INVALID_FORMAT;
18015704ed2Sdjm 		goto out;
18115704ed2Sdjm 	}
182a769387cSdjm 	if ((r = sshbuf_get_u32(resp, &rtype)) != 0) {
18348e6b99dSdjm 		error_fr(r, "parse message type");
1842db06755Sdjm 		goto out;
1852db06755Sdjm 	}
186a769387cSdjm 	if (rtype == SSH_SK_HELPER_ERROR) {
1872db06755Sdjm 		if ((r = sshbuf_get_u32(resp, &rerr)) != 0) {
18848e6b99dSdjm 			error_fr(r, "parse");
1892db06755Sdjm 			goto out;
1902db06755Sdjm 		}
19148e6b99dSdjm 		debug_f("helper returned error -%u", rerr);
1922db06755Sdjm 		/* OpenSSH error values are negative; encoded as -err on wire */
1932db06755Sdjm 		if (rerr == 0 || rerr >= INT_MAX)
1942db06755Sdjm 			r = SSH_ERR_INTERNAL_ERROR;
1952db06755Sdjm 		else
1962db06755Sdjm 			r = -(int)rerr;
1972db06755Sdjm 		goto out;
198a769387cSdjm 	} else if (rtype != type) {
19948e6b99dSdjm 		error_f("helper returned incorrect message type %u, "
20048e6b99dSdjm 		    "expecting %u", rtype, type);
2012db06755Sdjm 		r = SSH_ERR_INTERNAL_ERROR;
2022db06755Sdjm 		goto out;
2032db06755Sdjm 	}
20415704ed2Sdjm 	/* success */
20515704ed2Sdjm 	r = 0;
20615704ed2Sdjm  out:
20715704ed2Sdjm 	oerrno = errno;
20815704ed2Sdjm 	close(fd);
20915704ed2Sdjm 	if ((r2 = reap_helper(pid)) != 0) {
21015704ed2Sdjm 		if (r == 0) {
21115704ed2Sdjm 			r = r2;
21215704ed2Sdjm 			oerrno = errno;
21315704ed2Sdjm 		}
21415704ed2Sdjm 	}
21515704ed2Sdjm 	if (r == 0) {
21615704ed2Sdjm 		*respp = resp;
21715704ed2Sdjm 		resp = NULL;
21815704ed2Sdjm 	}
219a769387cSdjm 	sshbuf_free(req);
22015704ed2Sdjm 	sshbuf_free(resp);
221e9716d4dSdtucker 	ssh_signal(SIGCHLD, osigchld);
22215704ed2Sdjm 	errno = oerrno;
22315704ed2Sdjm 	return r;
22415704ed2Sdjm 
22515704ed2Sdjm }
22615704ed2Sdjm 
22715704ed2Sdjm int
sshsk_sign(const char * provider,struct sshkey * key,u_char ** sigp,size_t * lenp,const u_char * data,size_t datalen,u_int compat,const char * pin)22815704ed2Sdjm sshsk_sign(const char *provider, struct sshkey *key,
22915704ed2Sdjm     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
2302db06755Sdjm     u_int compat, const char *pin)
23115704ed2Sdjm {
23215704ed2Sdjm 	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
23315704ed2Sdjm 	struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
23415704ed2Sdjm 
23515704ed2Sdjm 	*sigp = NULL;
23615704ed2Sdjm 	*lenp = 0;
23715704ed2Sdjm 
23815704ed2Sdjm 	if ((kbuf = sshbuf_new()) == NULL ||
23915704ed2Sdjm 	    (req = sshbuf_new()) == NULL) {
24015704ed2Sdjm 		r = SSH_ERR_ALLOC_FAIL;
24115704ed2Sdjm 		goto out;
24215704ed2Sdjm 	}
24315704ed2Sdjm 
24415704ed2Sdjm 	if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
24548e6b99dSdjm 		error_fr(r, "encode key");
24615704ed2Sdjm 		goto out;
24715704ed2Sdjm 	}
248a769387cSdjm 	if ((r = sshbuf_put_stringb(req, kbuf)) != 0 ||
24915704ed2Sdjm 	    (r = sshbuf_put_cstring(req, provider)) != 0 ||
25015704ed2Sdjm 	    (r = sshbuf_put_string(req, data, datalen)) != 0 ||
25115704ed2Sdjm 	    (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
2522db06755Sdjm 	    (r = sshbuf_put_u32(req, compat)) != 0 ||
2532db06755Sdjm 	    (r = sshbuf_put_cstring(req, pin)) != 0) {
25448e6b99dSdjm 		error_fr(r, "compose");
25515704ed2Sdjm 		goto out;
25615704ed2Sdjm 	}
25715704ed2Sdjm 
2582db06755Sdjm 	if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
25915704ed2Sdjm 		goto out;
26015704ed2Sdjm 
26115704ed2Sdjm 	if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
26248e6b99dSdjm 		error_fr(r, "parse signature");
26315704ed2Sdjm 		r = SSH_ERR_INVALID_FORMAT;
26415704ed2Sdjm 		goto out;
26515704ed2Sdjm 	}
26615704ed2Sdjm 	if (sshbuf_len(resp) != 0) {
26748e6b99dSdjm 		error_f("trailing data in response");
26815704ed2Sdjm 		r = SSH_ERR_INVALID_FORMAT;
26915704ed2Sdjm 		goto out;
27015704ed2Sdjm 	}
27115704ed2Sdjm 	/* success */
27215704ed2Sdjm 	r = 0;
27315704ed2Sdjm  out:
27415704ed2Sdjm 	oerrno = errno;
27515704ed2Sdjm 	if (r != 0) {
27615704ed2Sdjm 		freezero(*sigp, *lenp);
27715704ed2Sdjm 		*sigp = NULL;
27815704ed2Sdjm 		*lenp = 0;
27915704ed2Sdjm 	}
28015704ed2Sdjm 	sshbuf_free(kbuf);
28115704ed2Sdjm 	sshbuf_free(req);
28215704ed2Sdjm 	sshbuf_free(resp);
28315704ed2Sdjm 	errno = oerrno;
28415704ed2Sdjm 	return r;
28515704ed2Sdjm }
28615704ed2Sdjm 
28715704ed2Sdjm int
sshsk_enroll(int type,const char * provider_path,const char * device,const char * application,const char * userid,uint8_t flags,const char * pin,struct sshbuf * challenge_buf,struct sshkey ** keyp,struct sshbuf * attest)288a0caf565Sdjm sshsk_enroll(int type, const char *provider_path, const char *device,
289a0caf565Sdjm     const char *application, const char *userid, uint8_t flags,
290a0caf565Sdjm     const char *pin, struct sshbuf *challenge_buf,
2912db06755Sdjm     struct sshkey **keyp, struct sshbuf *attest)
29215704ed2Sdjm {
29315704ed2Sdjm 	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
29415704ed2Sdjm 	struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
29515704ed2Sdjm 	struct sshkey *key = NULL;
29615704ed2Sdjm 
29715704ed2Sdjm 	*keyp = NULL;
29815704ed2Sdjm 	if (attest != NULL)
29915704ed2Sdjm 		sshbuf_reset(attest);
30015704ed2Sdjm 
30115704ed2Sdjm 	if (type < 0)
30215704ed2Sdjm 		return SSH_ERR_INVALID_ARGUMENT;
30315704ed2Sdjm 
30415704ed2Sdjm 	if ((abuf = sshbuf_new()) == NULL ||
30515704ed2Sdjm 	    (kbuf = sshbuf_new()) == NULL ||
30615704ed2Sdjm 	    (req = sshbuf_new()) == NULL) {
30715704ed2Sdjm 		r = SSH_ERR_ALLOC_FAIL;
30815704ed2Sdjm 		goto out;
30915704ed2Sdjm 	}
31015704ed2Sdjm 
311a769387cSdjm 	if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
31215704ed2Sdjm 	    (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
313a0caf565Sdjm 	    (r = sshbuf_put_cstring(req, device)) != 0 ||
31415704ed2Sdjm 	    (r = sshbuf_put_cstring(req, application)) != 0 ||
315a0caf565Sdjm 	    (r = sshbuf_put_cstring(req, userid)) != 0 ||
31615704ed2Sdjm 	    (r = sshbuf_put_u8(req, flags)) != 0 ||
3172db06755Sdjm 	    (r = sshbuf_put_cstring(req, pin)) != 0 ||
31815704ed2Sdjm 	    (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
31948e6b99dSdjm 		error_fr(r, "compose");
32015704ed2Sdjm 		goto out;
32115704ed2Sdjm 	}
32215704ed2Sdjm 
3232db06755Sdjm 	if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
32415704ed2Sdjm 		goto out;
32515704ed2Sdjm 
32615704ed2Sdjm 	if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
32715704ed2Sdjm 	    (r = sshbuf_get_stringb(resp, abuf)) != 0) {
32848e6b99dSdjm 		error_fr(r, "parse");
32915704ed2Sdjm 		r = SSH_ERR_INVALID_FORMAT;
33015704ed2Sdjm 		goto out;
33115704ed2Sdjm 	}
33215704ed2Sdjm 	if (sshbuf_len(resp) != 0) {
33348e6b99dSdjm 		error_f("trailing data in response");
33415704ed2Sdjm 		r = SSH_ERR_INVALID_FORMAT;
33515704ed2Sdjm 		goto out;
33615704ed2Sdjm 	}
33715704ed2Sdjm 	if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
33848e6b99dSdjm 		error_fr(r, "encode");
33915704ed2Sdjm 		goto out;
34015704ed2Sdjm 	}
34115704ed2Sdjm 	if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
34248e6b99dSdjm 		error_fr(r, "encode attestation information");
34315704ed2Sdjm 		goto out;
34415704ed2Sdjm 	}
34515704ed2Sdjm 
34615704ed2Sdjm 	/* success */
34715704ed2Sdjm 	r = 0;
34815704ed2Sdjm 	*keyp = key;
34915704ed2Sdjm 	key = NULL;
35015704ed2Sdjm  out:
35115704ed2Sdjm 	oerrno = errno;
35215704ed2Sdjm 	sshkey_free(key);
35315704ed2Sdjm 	sshbuf_free(kbuf);
35415704ed2Sdjm 	sshbuf_free(abuf);
35515704ed2Sdjm 	sshbuf_free(req);
35615704ed2Sdjm 	sshbuf_free(resp);
35715704ed2Sdjm 	errno = oerrno;
35815704ed2Sdjm 	return r;
35915704ed2Sdjm }
3609fe3789cSdjm 
361991d5a20Sdjm static void
sshsk_free_resident_key(struct sshsk_resident_key * srk)362991d5a20Sdjm sshsk_free_resident_key(struct sshsk_resident_key *srk)
363991d5a20Sdjm {
364991d5a20Sdjm 	if (srk == NULL)
365991d5a20Sdjm 		return;
366991d5a20Sdjm 	sshkey_free(srk->key);
367991d5a20Sdjm 	freezero(srk->user_id, srk->user_id_len);
368991d5a20Sdjm 	free(srk);
369991d5a20Sdjm }
370991d5a20Sdjm 
371991d5a20Sdjm 
372991d5a20Sdjm void
sshsk_free_resident_keys(struct sshsk_resident_key ** srks,size_t nsrks)373991d5a20Sdjm sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
374991d5a20Sdjm {
375991d5a20Sdjm 	size_t i;
376991d5a20Sdjm 
377991d5a20Sdjm 	if (srks == NULL || nsrks == 0)
378991d5a20Sdjm 		return;
379991d5a20Sdjm 
380991d5a20Sdjm 	for (i = 0; i < nsrks; i++)
381991d5a20Sdjm 		sshsk_free_resident_key(srks[i]);
382991d5a20Sdjm 	free(srks);
383991d5a20Sdjm }
384991d5a20Sdjm 
3859fe3789cSdjm int
sshsk_load_resident(const char * provider_path,const char * device,const char * pin,u_int flags,struct sshsk_resident_key *** srksp,size_t * nsrksp)386a0caf565Sdjm sshsk_load_resident(const char *provider_path, const char *device,
387991d5a20Sdjm     const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
388991d5a20Sdjm     size_t *nsrksp)
3899fe3789cSdjm {
3909fe3789cSdjm 	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
3919fe3789cSdjm 	struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
392991d5a20Sdjm 	struct sshkey *key = NULL;
393991d5a20Sdjm 	struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
394991d5a20Sdjm 	u_char *userid = NULL;
395991d5a20Sdjm 	size_t userid_len = 0, nsrks = 0;
3969fe3789cSdjm 
397991d5a20Sdjm 	*srksp = NULL;
398991d5a20Sdjm 	*nsrksp = 0;
3999fe3789cSdjm 
400*31c848ccSdjm 	if ((kbuf = sshbuf_new()) == NULL ||
4019fe3789cSdjm 	    (req = sshbuf_new()) == NULL) {
4029fe3789cSdjm 		r = SSH_ERR_ALLOC_FAIL;
4039fe3789cSdjm 		goto out;
4049fe3789cSdjm 	}
4059fe3789cSdjm 
406a769387cSdjm 	if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
407a0caf565Sdjm 	    (r = sshbuf_put_cstring(req, device)) != 0 ||
408991d5a20Sdjm 	    (r = sshbuf_put_cstring(req, pin)) != 0 ||
409991d5a20Sdjm 	    (r = sshbuf_put_u32(req, flags)) != 0) {
41048e6b99dSdjm 		error_fr(r, "compose");
4119fe3789cSdjm 		goto out;
4129fe3789cSdjm 	}
4139fe3789cSdjm 
4142db06755Sdjm 	if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
4159fe3789cSdjm 		goto out;
4169fe3789cSdjm 
4179fe3789cSdjm 	while (sshbuf_len(resp) != 0) {
418991d5a20Sdjm 		/* key, comment, user_id */
4199fe3789cSdjm 		if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
420991d5a20Sdjm 		    (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0 ||
421991d5a20Sdjm 		    (r = sshbuf_get_string(resp, &userid, &userid_len)) != 0) {
422991d5a20Sdjm 			error_fr(r, "parse");
4239fe3789cSdjm 			r = SSH_ERR_INVALID_FORMAT;
4249fe3789cSdjm 			goto out;
4259fe3789cSdjm 		}
4269fe3789cSdjm 		if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
42748e6b99dSdjm 			error_fr(r, "decode key");
4289fe3789cSdjm 			goto out;
4299fe3789cSdjm 		}
430991d5a20Sdjm 		if ((srk = calloc(1, sizeof(*srk))) == NULL) {
431991d5a20Sdjm 			error_f("calloc failed");
432991d5a20Sdjm 			goto out;
433991d5a20Sdjm 		}
434991d5a20Sdjm 		srk->key = key;
435991d5a20Sdjm 		key = NULL;
436991d5a20Sdjm 		srk->user_id = userid;
437991d5a20Sdjm 		srk->user_id_len = userid_len;
438991d5a20Sdjm 		userid = NULL;
439991d5a20Sdjm 		userid_len = 0;
440991d5a20Sdjm 		if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
441991d5a20Sdjm 		    sizeof(*srks))) == NULL) {
44248e6b99dSdjm 			error_f("recallocarray keys failed");
4439fe3789cSdjm 			goto out;
4449fe3789cSdjm 		}
445991d5a20Sdjm 		debug_f("srks[%zu]: %s %s uidlen %zu", nsrks,
446991d5a20Sdjm 		    sshkey_type(srk->key), srk->key->sk_application,
447991d5a20Sdjm 		    srk->user_id_len);
448991d5a20Sdjm 		srks = tmp;
449991d5a20Sdjm 		srks[nsrks++] = srk;
450991d5a20Sdjm 		srk = NULL;
4519fe3789cSdjm 	}
4529fe3789cSdjm 
4539fe3789cSdjm 	/* success */
4549fe3789cSdjm 	r = 0;
455991d5a20Sdjm 	*srksp = srks;
456991d5a20Sdjm 	*nsrksp = nsrks;
457991d5a20Sdjm 	srks = NULL;
458991d5a20Sdjm 	nsrks = 0;
4599fe3789cSdjm  out:
4609fe3789cSdjm 	oerrno = errno;
461991d5a20Sdjm 	sshsk_free_resident_key(srk);
462991d5a20Sdjm 	sshsk_free_resident_keys(srks, nsrks);
463991d5a20Sdjm 	freezero(userid, userid_len);
4649fe3789cSdjm 	sshkey_free(key);
4659fe3789cSdjm 	sshbuf_free(kbuf);
4669fe3789cSdjm 	sshbuf_free(req);
4679fe3789cSdjm 	sshbuf_free(resp);
4689fe3789cSdjm 	errno = oerrno;
4699fe3789cSdjm 	return r;
4709fe3789cSdjm }
471