blob: 9121570dc7c5ab667d785f18de8fe8d20898067d [file] [log] [blame]
djm@openbsd.org57b181e2020-01-10 23:43:26 +00001/* $OpenBSD: ssh-sk-client.c,v 1.5 2020/01/10 23:43:26 djm Exp $ */
djm@openbsd.orgd2143472019-12-13 20:16:56 +00002/*
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
Damien Millera33ab162019-12-14 09:15:06 +110018#include "includes.h"
19
djm@openbsd.orgd2143472019-12-13 20:16:56 +000020#include <sys/types.h>
21#include <sys/socket.h>
22#include <sys/wait.h>
23
djm@openbsd.orgc54cd182019-12-30 09:23:28 +000024#include <limits.h>
djm@openbsd.orgd2143472019-12-13 20:16:56 +000025#include <errno.h>
26#include <signal.h>
27#include <stdarg.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <unistd.h>
32
33#include "log.h"
34#include "ssherr.h"
35#include "sshbuf.h"
36#include "sshkey.h"
37#include "msg.h"
38#include "digest.h"
39#include "pathnames.h"
40#include "ssh-sk.h"
41
42/* #define DEBUG_SK 1 */
43
44static int
45start_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#ifdef DEBUG_SK
60 verbosity = "-vvv";
61#endif
62
63 /* Start helper */
64 if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) {
65 error("socketpair: %s", strerror(errno));
66 return SSH_ERR_SYSTEM_ERROR;
67 }
68 osigchld = signal(SIGCHLD, SIG_DFL);
69 if ((pid = fork()) == -1) {
70 oerrno = errno;
71 error("fork: %s", strerror(errno));
72 close(pair[0]);
73 close(pair[1]);
74 signal(SIGCHLD, osigchld);
75 errno = oerrno;
76 return SSH_ERR_SYSTEM_ERROR;
77 }
78 if (pid == 0) {
79 if ((dup2(pair[1], STDIN_FILENO) == -1) ||
80 (dup2(pair[1], STDOUT_FILENO) == -1)) {
81 error("%s: dup2: %s", __func__, ssh_err(r));
82 _exit(1);
83 }
84 close(pair[0]);
85 close(pair[1]);
86 closefrom(STDERR_FILENO + 1);
87 debug("%s: starting %s %s", __func__, helper,
88 verbosity == NULL ? "" : verbosity);
89 execlp(helper, helper, verbosity, (char *)NULL);
90 error("%s: execlp: %s", __func__, strerror(errno));
91 _exit(1);
92 }
93 close(pair[1]);
94
95 /* success */
96 debug3("%s: started pid=%ld", __func__, (long)pid);
97 *fdp = pair[0];
98 *pidp = pid;
99 *osigchldp = osigchld;
100 return 0;
101}
102
103static int
104reap_helper(pid_t pid)
105{
106 int status, oerrno;
107
108 debug3("%s: pid=%ld", __func__, (long)pid);
109
110 errno = 0;
111 while (waitpid(pid, &status, 0) == -1) {
112 if (errno == EINTR) {
113 errno = 0;
114 continue;
115 }
116 oerrno = errno;
117 error("%s: waitpid: %s", __func__, strerror(errno));
118 errno = oerrno;
119 return SSH_ERR_SYSTEM_ERROR;
120 }
121 if (!WIFEXITED(status)) {
122 error("%s: helper exited abnormally", __func__);
123 return SSH_ERR_AGENT_FAILURE;
124 } else if (WEXITSTATUS(status) != 0) {
125 error("%s: helper exited with non-zero exit status", __func__);
126 return SSH_ERR_AGENT_FAILURE;
127 }
128 return 0;
129}
130
131static int
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000132client_converse(struct sshbuf *msg, struct sshbuf **respp, u_int type)
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000133{
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000134 int oerrno, fd, r2, ll, r = SSH_ERR_INTERNAL_ERROR;
135 u_int rtype, rerr;
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000136 pid_t pid;
137 u_char version;
138 void (*osigchld)(int);
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000139 struct sshbuf *req = NULL, *resp = NULL;
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000140 *respp = NULL;
141
142 if ((r = start_helper(&fd, &pid, &osigchld)) != 0)
143 return r;
144
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000145 if ((req = sshbuf_new()) == NULL || (resp = sshbuf_new()) == NULL) {
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000146 r = SSH_ERR_ALLOC_FAIL;
147 goto out;
148 }
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000149 /* Request preamble: type, log_on_stderr, log_level */
150 ll = log_level_get();
151 if ((r = sshbuf_put_u32(req, type)) != 0 ||
152 (r = sshbuf_put_u8(req, log_is_on_stderr() != 0)) != 0 ||
153 (r = sshbuf_put_u32(req, ll < 0 ? 0 : ll)) != 0 ||
154 (r = sshbuf_putb(req, msg)) != 0) {
155 error("%s: build: %s", __func__, ssh_err(r));
156 goto out;
157 }
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000158 if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) {
159 error("%s: send: %s", __func__, ssh_err(r));
160 goto out;
161 }
162 if ((r = ssh_msg_recv(fd, resp)) != 0) {
163 error("%s: receive: %s", __func__, ssh_err(r));
164 goto out;
165 }
166 if ((r = sshbuf_get_u8(resp, &version)) != 0) {
167 error("%s: parse version: %s", __func__, ssh_err(r));
168 goto out;
169 }
170 if (version != SSH_SK_HELPER_VERSION) {
171 error("%s: unsupported version: got %u, expected %u",
172 __func__, version, SSH_SK_HELPER_VERSION);
173 r = SSH_ERR_INVALID_FORMAT;
174 goto out;
175 }
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000176 if ((r = sshbuf_get_u32(resp, &rtype)) != 0) {
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000177 error("%s: parse message type: %s", __func__, ssh_err(r));
178 goto out;
179 }
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000180 if (rtype == SSH_SK_HELPER_ERROR) {
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000181 if ((r = sshbuf_get_u32(resp, &rerr)) != 0) {
182 error("%s: parse error: %s", __func__, ssh_err(r));
183 goto out;
184 }
185 debug("%s: helper returned error -%u", __func__, rerr);
186 /* OpenSSH error values are negative; encoded as -err on wire */
187 if (rerr == 0 || rerr >= INT_MAX)
188 r = SSH_ERR_INTERNAL_ERROR;
189 else
190 r = -(int)rerr;
191 goto out;
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000192 } else if (rtype != type) {
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000193 error("%s: helper returned incorrect message type %u, "
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000194 "expecting %u", __func__, rtype, type);
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000195 r = SSH_ERR_INTERNAL_ERROR;
196 goto out;
197 }
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000198 /* success */
199 r = 0;
200 out:
201 oerrno = errno;
202 close(fd);
203 if ((r2 = reap_helper(pid)) != 0) {
204 if (r == 0) {
205 r = r2;
206 oerrno = errno;
207 }
208 }
209 if (r == 0) {
210 *respp = resp;
211 resp = NULL;
212 }
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000213 sshbuf_free(req);
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000214 sshbuf_free(resp);
215 signal(SIGCHLD, osigchld);
216 errno = oerrno;
217 return r;
218
219}
220
221int
222sshsk_sign(const char *provider, struct sshkey *key,
223 u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000224 u_int compat, const char *pin)
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000225{
226 int oerrno, r = SSH_ERR_INTERNAL_ERROR;
227 char *fp = NULL;
228 struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
229
230 *sigp = NULL;
231 *lenp = 0;
232
Damien Miller92449902019-12-14 09:21:46 +1100233#ifndef ENABLE_SK
234 return SSH_ERR_KEY_TYPE_UNKNOWN;
235#endif
236
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000237 if ((kbuf = sshbuf_new()) == NULL ||
238 (req = sshbuf_new()) == NULL) {
239 r = SSH_ERR_ALLOC_FAIL;
240 goto out;
241 }
242
243 if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
244 error("%s: serialize private key: %s", __func__, ssh_err(r));
245 goto out;
246 }
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000247 if ((r = sshbuf_put_stringb(req, kbuf)) != 0 ||
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000248 (r = sshbuf_put_cstring(req, provider)) != 0 ||
249 (r = sshbuf_put_string(req, data, datalen)) != 0 ||
250 (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000251 (r = sshbuf_put_u32(req, compat)) != 0 ||
252 (r = sshbuf_put_cstring(req, pin)) != 0) {
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000253 error("%s: compose: %s", __func__, ssh_err(r));
254 goto out;
255 }
256
257 if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
258 SSH_FP_DEFAULT)) == NULL) {
259 error("%s: sshkey_fingerprint failed", __func__);
260 r = SSH_ERR_ALLOC_FAIL;
261 goto out;
262 }
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000263 if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000264 goto out;
265
266 if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
267 error("%s: parse signature: %s", __func__, ssh_err(r));
268 r = SSH_ERR_INVALID_FORMAT;
269 goto out;
270 }
271 if (sshbuf_len(resp) != 0) {
272 error("%s: trailing data in response", __func__);
273 r = SSH_ERR_INVALID_FORMAT;
274 goto out;
275 }
276 /* success */
277 r = 0;
278 out:
279 oerrno = errno;
280 if (r != 0) {
281 freezero(*sigp, *lenp);
282 *sigp = NULL;
283 *lenp = 0;
284 }
285 sshbuf_free(kbuf);
286 sshbuf_free(req);
287 sshbuf_free(resp);
288 errno = oerrno;
289 return r;
290}
291
292int
djm@openbsd.orgc312ca02020-01-06 02:00:46 +0000293sshsk_enroll(int type, const char *provider_path, const char *device,
294 const char *application, const char *userid, uint8_t flags,
295 const char *pin, struct sshbuf *challenge_buf,
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000296 struct sshkey **keyp, struct sshbuf *attest)
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000297{
298 int oerrno, r = SSH_ERR_INTERNAL_ERROR;
299 struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
300 struct sshkey *key = NULL;
301
302 *keyp = NULL;
303 if (attest != NULL)
304 sshbuf_reset(attest);
305
Damien Miller92449902019-12-14 09:21:46 +1100306#ifndef ENABLE_SK
307 return SSH_ERR_KEY_TYPE_UNKNOWN;
308#endif
309
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000310 if (type < 0)
311 return SSH_ERR_INVALID_ARGUMENT;
312
313 if ((abuf = sshbuf_new()) == NULL ||
314 (kbuf = sshbuf_new()) == NULL ||
315 (req = sshbuf_new()) == NULL) {
316 r = SSH_ERR_ALLOC_FAIL;
317 goto out;
318 }
319
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000320 if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000321 (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
djm@openbsd.orgc312ca02020-01-06 02:00:46 +0000322 (r = sshbuf_put_cstring(req, device)) != 0 ||
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000323 (r = sshbuf_put_cstring(req, application)) != 0 ||
djm@openbsd.orgc312ca02020-01-06 02:00:46 +0000324 (r = sshbuf_put_cstring(req, userid)) != 0 ||
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000325 (r = sshbuf_put_u8(req, flags)) != 0 ||
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000326 (r = sshbuf_put_cstring(req, pin)) != 0 ||
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000327 (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
328 error("%s: compose: %s", __func__, ssh_err(r));
329 goto out;
330 }
331
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000332 if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
djm@openbsd.orgd2143472019-12-13 20:16:56 +0000333 goto out;
334
335 if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
336 (r = sshbuf_get_stringb(resp, abuf)) != 0) {
337 error("%s: parse signature: %s", __func__, ssh_err(r));
338 r = SSH_ERR_INVALID_FORMAT;
339 goto out;
340 }
341 if (sshbuf_len(resp) != 0) {
342 error("%s: trailing data in response", __func__);
343 r = SSH_ERR_INVALID_FORMAT;
344 goto out;
345 }
346 if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
347 error("Unable to parse private key: %s", ssh_err(r));
348 goto out;
349 }
350 if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
351 error("%s: buffer error: %s", __func__, ssh_err(r));
352 goto out;
353 }
354
355 /* success */
356 r = 0;
357 *keyp = key;
358 key = NULL;
359 out:
360 oerrno = errno;
361 sshkey_free(key);
362 sshbuf_free(kbuf);
363 sshbuf_free(abuf);
364 sshbuf_free(req);
365 sshbuf_free(resp);
366 errno = oerrno;
367 return r;
368}
djm@openbsd.org27753a82019-12-30 09:21:59 +0000369
370int
djm@openbsd.orgc312ca02020-01-06 02:00:46 +0000371sshsk_load_resident(const char *provider_path, const char *device,
372 const char *pin, struct sshkey ***keysp, size_t *nkeysp)
djm@openbsd.org27753a82019-12-30 09:21:59 +0000373{
374 int oerrno, r = SSH_ERR_INTERNAL_ERROR;
375 struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
376 struct sshkey *key = NULL, **keys = NULL, **tmp;
377 size_t i, nkeys = 0;
378
379 *keysp = NULL;
380 *nkeysp = 0;
381
382 if ((resp = sshbuf_new()) == NULL ||
383 (kbuf = sshbuf_new()) == NULL ||
384 (req = sshbuf_new()) == NULL) {
385 r = SSH_ERR_ALLOC_FAIL;
386 goto out;
387 }
388
djm@openbsd.org57b181e2020-01-10 23:43:26 +0000389 if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
djm@openbsd.orgc312ca02020-01-06 02:00:46 +0000390 (r = sshbuf_put_cstring(req, device)) != 0 ||
djm@openbsd.org27753a82019-12-30 09:21:59 +0000391 (r = sshbuf_put_cstring(req, pin)) != 0) {
392 error("%s: compose: %s", __func__, ssh_err(r));
393 goto out;
394 }
395
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000396 if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
djm@openbsd.org27753a82019-12-30 09:21:59 +0000397 goto out;
398
399 while (sshbuf_len(resp) != 0) {
400 /* key, comment */
401 if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
402 (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0) {
403 error("%s: parse signature: %s", __func__, ssh_err(r));
404 r = SSH_ERR_INVALID_FORMAT;
405 goto out;
406 }
407 if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
408 error("Unable to parse private key: %s", ssh_err(r));
409 goto out;
410 }
411 if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
412 sizeof(*keys))) == NULL) {
413 error("%s: recallocarray keys failed", __func__);
414 goto out;
415 }
djm@openbsd.orgc54cd182019-12-30 09:23:28 +0000416 debug("%s: keys[%zu]: %s %s", __func__,
417 nkeys, sshkey_type(key), key->sk_application);
djm@openbsd.org27753a82019-12-30 09:21:59 +0000418 keys = tmp;
419 keys[nkeys++] = key;
420 key = NULL;
421 }
422
423 /* success */
424 r = 0;
425 *keysp = keys;
426 *nkeysp = nkeys;
427 keys = NULL;
428 nkeys = 0;
429 out:
430 oerrno = errno;
431 for (i = 0; i < nkeys; i++)
432 sshkey_free(keys[i]);
433 free(keys);
434 sshkey_free(key);
435 sshbuf_free(kbuf);
436 sshbuf_free(req);
437 sshbuf_free(resp);
438 errno = oerrno;
439 return r;
440}