1/* $OpenBSD: ssh-sk-client.c,v 1.12 2022/01/14 03:34:00 djm Exp $ */
2/*
3 * Copyright (c) 2019 Google LLC
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include "includes.h"
19
20#include <sys/types.h>
21#include <sys/socket.h>
22#include <sys/wait.h>
23
24#include <fcntl.h>
25#include <limits.h>
26#include <errno.h>
27#include <signal.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <unistd.h>
33
34#include "log.h"
35#include "ssherr.h"
36#include "sshbuf.h"
37#include "sshkey.h"
38#include "msg.h"
39#include "digest.h"
40#include "pathnames.h"
41#include "ssh-sk.h"
42#include "misc.h"
43
44/* #define DEBUG_SK 1 */
45
46static int
47start_helper(int *fdp, pid_t *pidp, void (**osigchldp)(int))
48{
49	void (*osigchld)(int);
50	int oerrno, pair[2];
51	pid_t pid;
52	char *helper, *verbosity = NULL;
53
54	*fdp = -1;
55	*pidp = 0;
56	*osigchldp = SIG_DFL;
57
58	helper = getenv("SSH_SK_HELPER");
59	if (helper == NULL || strlen(helper) == 0)
60		helper = _PATH_SSH_SK_HELPER;
61	if (access(helper, X_OK) != 0) {
62		oerrno = errno;
63		error_f("helper \"%s\" unusable: %s", helper, strerror(errno));
64		errno = oerrno;
65		return SSH_ERR_SYSTEM_ERROR;
66	}
67#ifdef DEBUG_SK
68	verbosity = "-vvv";
69#endif
70
71	/* Start helper */
72	if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
73		error("socketpair: %s", strerror(errno));
74		return SSH_ERR_SYSTEM_ERROR;
75	}
76	osigchld = ssh_signal(SIGCHLD, SIG_DFL);
77	if ((pid = fork()) == -1) {
78		oerrno = errno;
79		error("fork: %s", strerror(errno));
80		close(pair[0]);
81		close(pair[1]);
82		ssh_signal(SIGCHLD, osigchld);
83		errno = oerrno;
84		return SSH_ERR_SYSTEM_ERROR;
85	}
86	if (pid == 0) {
87		if ((dup2(pair[1], STDIN_FILENO) == -1) ||
88		    (dup2(pair[1], STDOUT_FILENO) == -1)) {
89			error_f("dup2: %s", strerror(errno));
90			_exit(1);
91		}
92		close(pair[0]);
93		close(pair[1]);
94		closefrom(STDERR_FILENO + 1);
95		debug_f("starting %s %s", helper,
96		    verbosity == NULL ? "" : verbosity);
97		execlp(helper, helper, verbosity, (char *)NULL);
98		error_f("execlp: %s", strerror(errno));
99		_exit(1);
100	}
101	close(pair[1]);
102
103	/* success */
104	debug3_f("started pid=%ld", (long)pid);
105	*fdp = pair[0];
106	*pidp = pid;
107	*osigchldp = osigchld;
108	return 0;
109}
110
111static int
112reap_helper(pid_t pid)
113{
114	int status, oerrno;
115
116	debug3_f("pid=%ld", (long)pid);
117
118	errno = 0;
119	while (waitpid(pid, &status, 0) == -1) {
120		if (errno == EINTR) {
121			errno = 0;
122			continue;
123		}
124		oerrno = errno;
125		error_f("waitpid: %s", strerror(errno));
126		errno = oerrno;
127		return SSH_ERR_SYSTEM_ERROR;
128	}
129	if (!WIFEXITED(status)) {
130		error_f("helper exited abnormally");
131		return SSH_ERR_AGENT_FAILURE;
132	} else if (WEXITSTATUS(status) != 0) {
133		error_f("helper exited with non-zero exit status");
134		return SSH_ERR_AGENT_FAILURE;
135	}
136	return 0;
137}
138
139static int
140client_converse(struct sshbuf *msg, struct sshbuf **respp, u_int type)
141{
142	int oerrno, fd, r2, ll, r = SSH_ERR_INTERNAL_ERROR;
143	u_int rtype, rerr;
144	pid_t pid;
145	u_char version;
146	void (*osigchld)(int);
147	struct sshbuf *req = NULL, *resp = NULL;
148	*respp = NULL;
149
150	if ((r = start_helper(&fd, &pid, &osigchld)) != 0)
151		return r;
152
153	if ((req = sshbuf_new()) == NULL || (resp = sshbuf_new()) == NULL) {
154		r = SSH_ERR_ALLOC_FAIL;
155		goto out;
156	}
157	/* Request preamble: type, log_on_stderr, log_level */
158	ll = log_level_get();
159	if ((r = sshbuf_put_u32(req, type)) != 0 ||
160	    (r = sshbuf_put_u8(req, log_is_on_stderr() != 0)) != 0 ||
161	    (r = sshbuf_put_u32(req, ll < 0 ? 0 : ll)) != 0 ||
162	    (r = sshbuf_putb(req, msg)) != 0) {
163		error_fr(r, "compose");
164		goto out;
165	}
166	if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) {
167		error_fr(r, "send");
168		goto out;
169	}
170	if ((r = ssh_msg_recv(fd, resp)) != 0) {
171		error_fr(r, "receive");
172		goto out;
173	}
174	if ((r = sshbuf_get_u8(resp, &version)) != 0) {
175		error_fr(r, "parse version");
176		goto out;
177	}
178	if (version != SSH_SK_HELPER_VERSION) {
179		error_f("unsupported version: got %u, expected %u",
180		    version, SSH_SK_HELPER_VERSION);
181		r = SSH_ERR_INVALID_FORMAT;
182		goto out;
183	}
184	if ((r = sshbuf_get_u32(resp, &rtype)) != 0) {
185		error_fr(r, "parse message type");
186		goto out;
187	}
188	if (rtype == SSH_SK_HELPER_ERROR) {
189		if ((r = sshbuf_get_u32(resp, &rerr)) != 0) {
190			error_fr(r, "parse");
191			goto out;
192		}
193		debug_f("helper returned error -%u", rerr);
194		/* OpenSSH error values are negative; encoded as -err on wire */
195		if (rerr == 0 || rerr >= INT_MAX)
196			r = SSH_ERR_INTERNAL_ERROR;
197		else
198			r = -(int)rerr;
199		goto out;
200	} else if (rtype != type) {
201		error_f("helper returned incorrect message type %u, "
202		    "expecting %u", rtype, type);
203		r = SSH_ERR_INTERNAL_ERROR;
204		goto out;
205	}
206	/* success */
207	r = 0;
208 out:
209	oerrno = errno;
210	close(fd);
211	if ((r2 = reap_helper(pid)) != 0) {
212		if (r == 0) {
213			r = r2;
214			oerrno = errno;
215		}
216	}
217	if (r == 0) {
218		*respp = resp;
219		resp = NULL;
220	}
221	sshbuf_free(req);
222	sshbuf_free(resp);
223	ssh_signal(SIGCHLD, osigchld);
224	errno = oerrno;
225	return r;
226
227}
228
229int
230sshsk_sign(const char *provider, struct sshkey *key,
231    u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
232    u_int compat, const char *pin)
233{
234	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
235	struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
236
237	*sigp = NULL;
238	*lenp = 0;
239
240#ifndef ENABLE_SK
241	return SSH_ERR_KEY_TYPE_UNKNOWN;
242#endif
243
244	if ((kbuf = sshbuf_new()) == NULL ||
245	    (req = sshbuf_new()) == NULL) {
246		r = SSH_ERR_ALLOC_FAIL;
247		goto out;
248	}
249
250	if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
251		error_fr(r, "encode key");
252		goto out;
253	}
254	if ((r = sshbuf_put_stringb(req, kbuf)) != 0 ||
255	    (r = sshbuf_put_cstring(req, provider)) != 0 ||
256	    (r = sshbuf_put_string(req, data, datalen)) != 0 ||
257	    (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
258	    (r = sshbuf_put_u32(req, compat)) != 0 ||
259	    (r = sshbuf_put_cstring(req, pin)) != 0) {
260		error_fr(r, "compose");
261		goto out;
262	}
263
264	if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
265		goto out;
266
267	if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
268		error_fr(r, "parse signature");
269		r = SSH_ERR_INVALID_FORMAT;
270		goto out;
271	}
272	if (sshbuf_len(resp) != 0) {
273		error_f("trailing data in response");
274		r = SSH_ERR_INVALID_FORMAT;
275		goto out;
276	}
277	/* success */
278	r = 0;
279 out:
280	oerrno = errno;
281	if (r != 0) {
282		freezero(*sigp, *lenp);
283		*sigp = NULL;
284		*lenp = 0;
285	}
286	sshbuf_free(kbuf);
287	sshbuf_free(req);
288	sshbuf_free(resp);
289	errno = oerrno;
290	return r;
291}
292
293int
294sshsk_enroll(int type, const char *provider_path, const char *device,
295    const char *application, const char *userid, uint8_t flags,
296    const char *pin, struct sshbuf *challenge_buf,
297    struct sshkey **keyp, struct sshbuf *attest)
298{
299	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
300	struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
301	struct sshkey *key = NULL;
302
303	*keyp = NULL;
304	if (attest != NULL)
305		sshbuf_reset(attest);
306
307#ifndef ENABLE_SK
308	return SSH_ERR_KEY_TYPE_UNKNOWN;
309#endif
310
311	if (type < 0)
312		return SSH_ERR_INVALID_ARGUMENT;
313
314	if ((abuf = sshbuf_new()) == NULL ||
315	    (kbuf = sshbuf_new()) == NULL ||
316	    (req = sshbuf_new()) == NULL) {
317		r = SSH_ERR_ALLOC_FAIL;
318		goto out;
319	}
320
321	if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
322	    (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
323	    (r = sshbuf_put_cstring(req, device)) != 0 ||
324	    (r = sshbuf_put_cstring(req, application)) != 0 ||
325	    (r = sshbuf_put_cstring(req, userid)) != 0 ||
326	    (r = sshbuf_put_u8(req, flags)) != 0 ||
327	    (r = sshbuf_put_cstring(req, pin)) != 0 ||
328	    (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
329		error_fr(r, "compose");
330		goto out;
331	}
332
333	if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
334		goto out;
335
336	if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
337	    (r = sshbuf_get_stringb(resp, abuf)) != 0) {
338		error_fr(r, "parse");
339		r = SSH_ERR_INVALID_FORMAT;
340		goto out;
341	}
342	if (sshbuf_len(resp) != 0) {
343		error_f("trailing data in response");
344		r = SSH_ERR_INVALID_FORMAT;
345		goto out;
346	}
347	if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
348		error_fr(r, "encode");
349		goto out;
350	}
351	if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
352		error_fr(r, "encode attestation information");
353		goto out;
354	}
355
356	/* success */
357	r = 0;
358	*keyp = key;
359	key = NULL;
360 out:
361	oerrno = errno;
362	sshkey_free(key);
363	sshbuf_free(kbuf);
364	sshbuf_free(abuf);
365	sshbuf_free(req);
366	sshbuf_free(resp);
367	errno = oerrno;
368	return r;
369}
370
371static void
372sshsk_free_resident_key(struct sshsk_resident_key *srk)
373{
374	if (srk == NULL)
375		return;
376	sshkey_free(srk->key);
377	freezero(srk->user_id, srk->user_id_len);
378	free(srk);
379}
380
381
382void
383sshsk_free_resident_keys(struct sshsk_resident_key **srks, size_t nsrks)
384{
385	size_t i;
386
387	if (srks == NULL || nsrks == 0)
388		return;
389
390	for (i = 0; i < nsrks; i++)
391		sshsk_free_resident_key(srks[i]);
392	free(srks);
393}
394
395int
396sshsk_load_resident(const char *provider_path, const char *device,
397    const char *pin, u_int flags, struct sshsk_resident_key ***srksp,
398    size_t *nsrksp)
399{
400	int oerrno, r = SSH_ERR_INTERNAL_ERROR;
401	struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
402	struct sshkey *key = NULL;
403	struct sshsk_resident_key *srk = NULL, **srks = NULL, **tmp;
404	u_char *userid = NULL;
405	size_t userid_len = 0, nsrks = 0;
406
407	*srksp = NULL;
408	*nsrksp = 0;
409
410	if ((kbuf = sshbuf_new()) == NULL ||
411	    (req = sshbuf_new()) == NULL) {
412		r = SSH_ERR_ALLOC_FAIL;
413		goto out;
414	}
415
416	if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
417	    (r = sshbuf_put_cstring(req, device)) != 0 ||
418	    (r = sshbuf_put_cstring(req, pin)) != 0 ||
419	    (r = sshbuf_put_u32(req, flags)) != 0) {
420		error_fr(r, "compose");
421		goto out;
422	}
423
424	if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
425		goto out;
426
427	while (sshbuf_len(resp) != 0) {
428		/* key, comment, user_id */
429		if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
430		    (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0 ||
431		    (r = sshbuf_get_string(resp, &userid, &userid_len)) != 0) {
432			error_fr(r, "parse");
433			r = SSH_ERR_INVALID_FORMAT;
434			goto out;
435		}
436		if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
437			error_fr(r, "decode key");
438			goto out;
439		}
440		if ((srk = calloc(1, sizeof(*srk))) == NULL) {
441			error_f("calloc failed");
442			goto out;
443		}
444		srk->key = key;
445		key = NULL;
446		srk->user_id = userid;
447		srk->user_id_len = userid_len;
448		userid = NULL;
449		userid_len = 0;
450		if ((tmp = recallocarray(srks, nsrks, nsrks + 1,
451		    sizeof(*srks))) == NULL) {
452			error_f("recallocarray keys failed");
453			goto out;
454		}
455		debug_f("srks[%zu]: %s %s uidlen %zu", nsrks,
456		    sshkey_type(srk->key), srk->key->sk_application,
457		    srk->user_id_len);
458		srks = tmp;
459		srks[nsrks++] = srk;
460		srk = NULL;
461	}
462
463	/* success */
464	r = 0;
465	*srksp = srks;
466	*nsrksp = nsrks;
467	srks = NULL;
468	nsrks = 0;
469 out:
470	oerrno = errno;
471	sshsk_free_resident_key(srk);
472	sshsk_free_resident_keys(srks, nsrks);
473	freezero(userid, userid_len);
474	sshkey_free(key);
475	sshbuf_free(kbuf);
476	sshbuf_free(req);
477	sshbuf_free(resp);
478	errno = oerrno;
479	return r;
480}
481