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