1/* $OpenBSD: ssh-sk-helper.c,v 1.14 2022/12/04 11:03:11 dtucker 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/*
19 * This is a tiny program used to isolate the address space used for
20 * security key middleware signing operations from ssh-agent. It is similar
21 * to ssh-pkcs11-helper.c but considerably simpler as the operations for
22 * security keys are stateless.
23 *
24 * Please crank SSH_SK_HELPER_VERSION in sshkey.h for any incompatible
25 * protocol changes.
26 */
27
28#include "includes.h"
29
30#include <limits.h>
31#include <stdarg.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <errno.h>
37
38#include "xmalloc.h"
39#include "log.h"
40#include "sshkey.h"
41#include "authfd.h"
42#include "misc.h"
43#include "sshbuf.h"
44#include "msg.h"
45#include "uidswap.h"
46#include "ssherr.h"
47#include "ssh-sk.h"
48
49#ifdef ENABLE_SK
50extern char *__progname;
51
52static struct sshbuf *reply_error(int r, char *fmt, ...)
53    __attribute__((__format__ (printf, 2, 3)));
54
55static struct sshbuf *
56reply_error(int r, char *fmt, ...)
57{
58	char *msg;
59	va_list ap;
60	struct sshbuf *resp;
61
62	va_start(ap, fmt);
63	xvasprintf(&msg, fmt, ap);
64	va_end(ap);
65	debug("%s: %s", __progname, msg);
66	free(msg);
67
68	if (r >= 0)
69		fatal_f("invalid error code %d", r);
70
71	if ((resp = sshbuf_new()) == NULL)
72		fatal("%s: sshbuf_new failed", __progname);
73	if (sshbuf_put_u32(resp, SSH_SK_HELPER_ERROR) != 0 ||
74	    sshbuf_put_u32(resp, (u_int)-r) != 0)
75		fatal("%s: buffer error", __progname);
76	return resp;
77}
78
79/* If the specified string is zero length, then free it and replace with NULL */
80static void
81null_empty(char **s)
82{
83	if (s == NULL || *s == NULL || **s != '\0')
84		return;
85
86	free(*s);
87	*s = NULL;
88}
89
90static struct sshbuf *
91process_sign(struct sshbuf *req)
92{
93	int r = SSH_ERR_INTERNAL_ERROR;
94	struct sshbuf *resp, *kbuf;
95	struct sshkey *key = NULL;
96	uint32_t compat;
97	const u_char *message;
98	u_char *sig = NULL;
99	size_t msglen, siglen = 0;
100	char *provider = NULL, *pin = NULL;
101
102	if ((r = sshbuf_froms(req, &kbuf)) != 0 ||
103	    (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
104	    (r = sshbuf_get_string_direct(req, &message, &msglen)) != 0 ||
105	    (r = sshbuf_get_cstring(req, NULL, NULL)) != 0 || /* alg */
106	    (r = sshbuf_get_u32(req, &compat)) != 0 ||
107	    (r = sshbuf_get_cstring(req, &pin, NULL)) != 0)
108		fatal_r(r, "%s: parse", __progname);
109	if (sshbuf_len(req) != 0)
110		fatal("%s: trailing data in request", __progname);
111
112	if ((r = sshkey_private_deserialize(kbuf, &key)) != 0)
113		fatal_r(r, "%s: Unable to parse private key", __progname);
114	if (!sshkey_is_sk(key)) {
115		fatal("%s: Unsupported key type %s",
116		    __progname, sshkey_ssh_name(key));
117	}
118
119	debug_f("ready to sign with key %s, provider %s: "
120	    "msg len %zu, compat 0x%lx", sshkey_type(key),
121	    provider, msglen, (u_long)compat);
122
123	null_empty(&pin);
124
125	if ((r = sshsk_sign(provider, key, &sig, &siglen,
126	    message, msglen, compat, pin)) != 0) {
127		resp = reply_error(r, "Signing failed: %s", ssh_err(r));
128		goto out;
129	}
130
131	if ((resp = sshbuf_new()) == NULL)
132		fatal("%s: sshbuf_new failed", __progname);
133
134	if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_SIGN)) != 0 ||
135	    (r = sshbuf_put_string(resp, sig, siglen)) != 0)
136		fatal_r(r, "%s: compose", __progname);
137 out:
138	sshkey_free(key);
139	sshbuf_free(kbuf);
140	free(provider);
141	if (sig != NULL)
142		freezero(sig, siglen);
143	if (pin != NULL)
144		freezero(pin, strlen(pin));
145	return resp;
146}
147
148static struct sshbuf *
149process_enroll(struct sshbuf *req)
150{
151	int r;
152	u_int type;
153	char *provider, *application, *pin, *device, *userid;
154	uint8_t flags;
155	struct sshbuf *challenge, *attest, *kbuf, *resp;
156	struct sshkey *key;
157
158	if ((attest = sshbuf_new()) == NULL ||
159	    (kbuf = sshbuf_new()) == NULL)
160		fatal("%s: sshbuf_new failed", __progname);
161
162	if ((r = sshbuf_get_u32(req, &type)) != 0 ||
163	    (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
164	    (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
165	    (r = sshbuf_get_cstring(req, &application, NULL)) != 0 ||
166	    (r = sshbuf_get_cstring(req, &userid, NULL)) != 0 ||
167	    (r = sshbuf_get_u8(req, &flags)) != 0 ||
168	    (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 ||
169	    (r = sshbuf_froms(req, &challenge)) != 0)
170		fatal_r(r, "%s: parse", __progname);
171	if (sshbuf_len(req) != 0)
172		fatal("%s: trailing data in request", __progname);
173
174	if (type > INT_MAX)
175		fatal("%s: bad type %u", __progname, type);
176	if (sshbuf_len(challenge) == 0) {
177		sshbuf_free(challenge);
178		challenge = NULL;
179	}
180	null_empty(&device);
181	null_empty(&userid);
182	null_empty(&pin);
183
184	if ((r = sshsk_enroll((int)type, provider, device, application, userid,
185	    flags, pin, challenge, &key, attest)) != 0) {
186		resp = reply_error(r, "Enrollment failed: %s", ssh_err(r));
187		goto out;
188	}
189
190	if ((resp = sshbuf_new()) == NULL)
191		fatal("%s: sshbuf_new failed", __progname);
192	if ((r = sshkey_private_serialize(key, kbuf)) != 0)
193		fatal_r(r, "%s: encode key", __progname);
194	if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_ENROLL)) != 0 ||
195	    (r = sshbuf_put_stringb(resp, kbuf)) != 0 ||
196	    (r = sshbuf_put_stringb(resp, attest)) != 0)
197		fatal_r(r, "%s: compose", __progname);
198
199 out:
200	sshkey_free(key);
201	sshbuf_free(kbuf);
202	sshbuf_free(attest);
203	sshbuf_free(challenge);
204	free(provider);
205	free(application);
206	if (pin != NULL)
207		freezero(pin, strlen(pin));
208
209	return resp;
210}
211
212static struct sshbuf *
213process_load_resident(struct sshbuf *req)
214{
215	int r;
216	char *provider, *pin, *device;
217	struct sshbuf *kbuf, *resp;
218	struct sshsk_resident_key **srks = NULL;
219	size_t nsrks = 0, i;
220	u_int flags;
221
222	if ((kbuf = sshbuf_new()) == NULL)
223		fatal("%s: sshbuf_new failed", __progname);
224
225	if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 ||
226	    (r = sshbuf_get_cstring(req, &device, NULL)) != 0 ||
227	    (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 ||
228	    (r = sshbuf_get_u32(req, &flags)) != 0)
229		fatal_r(r, "%s: parse", __progname);
230	if (sshbuf_len(req) != 0)
231		fatal("%s: trailing data in request", __progname);
232
233	null_empty(&device);
234	null_empty(&pin);
235
236	if ((r = sshsk_load_resident(provider, device, pin, flags,
237	    &srks, &nsrks)) != 0) {
238		resp = reply_error(r, "sshsk_load_resident failed: %s",
239		    ssh_err(r));
240		goto out;
241	}
242
243	if ((resp = sshbuf_new()) == NULL)
244		fatal("%s: sshbuf_new failed", __progname);
245
246	if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
247		fatal_r(r, "%s: compose", __progname);
248
249	for (i = 0; i < nsrks; i++) {
250		debug_f("key %zu %s %s uidlen %zu", i,
251		    sshkey_type(srks[i]->key), srks[i]->key->sk_application,
252		    srks[i]->user_id_len);
253		sshbuf_reset(kbuf);
254		if ((r = sshkey_private_serialize(srks[i]->key, kbuf)) != 0)
255			fatal_r(r, "%s: encode key", __progname);
256		if ((r = sshbuf_put_stringb(resp, kbuf)) != 0 ||
257		    (r = sshbuf_put_cstring(resp, "")) != 0 || /* comment */
258		    (r = sshbuf_put_string(resp, srks[i]->user_id,
259		    srks[i]->user_id_len)) != 0)
260			fatal_r(r, "%s: compose key", __progname);
261	}
262
263 out:
264	sshsk_free_resident_keys(srks, nsrks);
265	sshbuf_free(kbuf);
266	free(provider);
267	free(device);
268	if (pin != NULL)
269		freezero(pin, strlen(pin));
270	return resp;
271}
272
273int
274main(int argc, char **argv)
275{
276	SyslogFacility log_facility = SYSLOG_FACILITY_AUTH;
277	LogLevel log_level = SYSLOG_LEVEL_ERROR;
278	struct sshbuf *req, *resp;
279	int in, out, ch, r, vflag = 0;
280	u_int rtype, ll = 0;
281	uint8_t version, log_stderr = 0;
282
283	sanitise_stdfd();
284	log_init(__progname, log_level, log_facility, log_stderr);
285
286	while ((ch = getopt(argc, argv, "v")) != -1) {
287		switch (ch) {
288		case 'v':
289			vflag = 1;
290			if (log_level == SYSLOG_LEVEL_ERROR)
291				log_level = SYSLOG_LEVEL_DEBUG1;
292			else if (log_level < SYSLOG_LEVEL_DEBUG3)
293				log_level++;
294			break;
295		default:
296			fprintf(stderr, "usage: %s [-v]\n", __progname);
297			exit(1);
298		}
299	}
300	log_init(__progname, log_level, log_facility, vflag);
301
302	/*
303	 * Rearrange our file descriptors a little; we don't trust the
304	 * providers not to fiddle with stdin/out.
305	 */
306	closefrom(STDERR_FILENO + 1);
307	if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1)
308		fatal("%s: dup: %s", __progname, strerror(errno));
309	close(STDIN_FILENO);
310	close(STDOUT_FILENO);
311	sanitise_stdfd(); /* resets to /dev/null */
312
313	if ((req = sshbuf_new()) == NULL)
314		fatal("%s: sshbuf_new failed", __progname);
315	if (ssh_msg_recv(in, req) < 0)
316		fatal("ssh_msg_recv failed");
317	close(in);
318	debug_f("received message len %zu", sshbuf_len(req));
319
320	if ((r = sshbuf_get_u8(req, &version)) != 0)
321		fatal_r(r, "%s: parse version", __progname);
322	if (version != SSH_SK_HELPER_VERSION) {
323		fatal("unsupported version: received %d, expected %d",
324		    version, SSH_SK_HELPER_VERSION);
325	}
326
327	if ((r = sshbuf_get_u32(req, &rtype)) != 0 ||
328	    (r = sshbuf_get_u8(req, &log_stderr)) != 0 ||
329	    (r = sshbuf_get_u32(req, &ll)) != 0)
330		fatal_r(r, "%s: parse", __progname);
331
332	if (!vflag && log_level_name((LogLevel)ll) != NULL)
333		log_init(__progname, (LogLevel)ll, log_facility, log_stderr);
334
335	switch (rtype) {
336	case SSH_SK_HELPER_SIGN:
337		resp = process_sign(req);
338		break;
339	case SSH_SK_HELPER_ENROLL:
340		resp = process_enroll(req);
341		break;
342	case SSH_SK_HELPER_LOAD_RESIDENT:
343		resp = process_load_resident(req);
344		break;
345	default:
346		fatal("%s: unsupported request type %u", __progname, rtype);
347	}
348	sshbuf_free(req);
349	debug_f("reply len %zu", sshbuf_len(resp));
350
351	if (ssh_msg_send(out, SSH_SK_HELPER_VERSION, resp) == -1)
352		fatal("ssh_msg_send failed");
353	sshbuf_free(resp);
354	close(out);
355
356	return (0);
357}
358#else /* ENABLE_SK */
359#include <stdio.h>
360
361int
362main(int argc, char **argv)
363{
364	fprintf(stderr, "ssh-sk-helper: disabled at compile time\n");
365	return -1;
366}
367#endif /* ENABLE_SK */
368