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