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