Annotation of src/usr.bin/ssh/ssh-sk-client.c, Revision 1.8
1.8 ! djm 1: /* $OpenBSD: ssh-sk-client.c,v 1.7 2020/01/23 07:10:22 dtucker 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 ||
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: char *fp = NULL;
234: struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
235:
236: *sigp = NULL;
237: *lenp = 0;
238:
239: if ((kbuf = sshbuf_new()) == NULL ||
240: (req = sshbuf_new()) == NULL) {
241: r = SSH_ERR_ALLOC_FAIL;
242: goto out;
243: }
244:
245: if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
1.8 ! djm 246: error_fr(r, "encode key");
1.1 djm 247: goto out;
248: }
1.5 djm 249: if ((r = sshbuf_put_stringb(req, kbuf)) != 0 ||
1.1 djm 250: (r = sshbuf_put_cstring(req, provider)) != 0 ||
251: (r = sshbuf_put_string(req, data, datalen)) != 0 ||
252: (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
1.3 djm 253: (r = sshbuf_put_u32(req, compat)) != 0 ||
254: (r = sshbuf_put_cstring(req, pin)) != 0) {
1.8 ! djm 255: error_fr(r, "compose");
1.1 djm 256: goto out;
257: }
258:
259: if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
260: SSH_FP_DEFAULT)) == NULL) {
1.8 ! djm 261: error_f("sshkey_fingerprint failed");
1.1 djm 262: r = SSH_ERR_ALLOC_FAIL;
263: goto out;
264: }
1.3 djm 265: if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
1.1 djm 266: goto out;
267:
268: if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
1.8 ! djm 269: error_fr(r, "parse signature");
1.1 djm 270: r = SSH_ERR_INVALID_FORMAT;
271: goto out;
272: }
273: if (sshbuf_len(resp) != 0) {
1.8 ! djm 274: error_f("trailing data in response");
1.1 djm 275: r = SSH_ERR_INVALID_FORMAT;
276: goto out;
277: }
278: /* success */
279: r = 0;
280: out:
281: oerrno = errno;
282: if (r != 0) {
283: freezero(*sigp, *lenp);
284: *sigp = NULL;
285: *lenp = 0;
286: }
287: sshbuf_free(kbuf);
288: sshbuf_free(req);
289: sshbuf_free(resp);
290: errno = oerrno;
291: return r;
292: }
293:
294: int
1.4 djm 295: sshsk_enroll(int type, const char *provider_path, const char *device,
296: const char *application, const char *userid, uint8_t flags,
297: const char *pin, struct sshbuf *challenge_buf,
1.3 djm 298: struct sshkey **keyp, struct sshbuf *attest)
1.1 djm 299: {
300: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
301: struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
302: struct sshkey *key = NULL;
303:
304: *keyp = NULL;
305: if (attest != NULL)
306: sshbuf_reset(attest);
307:
308: if (type < 0)
309: return SSH_ERR_INVALID_ARGUMENT;
310:
311: if ((abuf = sshbuf_new()) == NULL ||
312: (kbuf = sshbuf_new()) == NULL ||
313: (req = sshbuf_new()) == NULL) {
314: r = SSH_ERR_ALLOC_FAIL;
315: goto out;
316: }
317:
1.5 djm 318: if ((r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
1.1 djm 319: (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
1.4 djm 320: (r = sshbuf_put_cstring(req, device)) != 0 ||
1.1 djm 321: (r = sshbuf_put_cstring(req, application)) != 0 ||
1.4 djm 322: (r = sshbuf_put_cstring(req, userid)) != 0 ||
1.1 djm 323: (r = sshbuf_put_u8(req, flags)) != 0 ||
1.3 djm 324: (r = sshbuf_put_cstring(req, pin)) != 0 ||
1.1 djm 325: (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
1.8 ! djm 326: error_fr(r, "compose");
1.1 djm 327: goto out;
328: }
329:
1.3 djm 330: if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
1.1 djm 331: goto out;
332:
333: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
334: (r = sshbuf_get_stringb(resp, abuf)) != 0) {
1.8 ! djm 335: error_fr(r, "parse");
1.1 djm 336: r = SSH_ERR_INVALID_FORMAT;
337: goto out;
338: }
339: if (sshbuf_len(resp) != 0) {
1.8 ! djm 340: error_f("trailing data in response");
1.1 djm 341: r = SSH_ERR_INVALID_FORMAT;
342: goto out;
343: }
344: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
1.8 ! djm 345: error_fr(r, "encode");
1.1 djm 346: goto out;
347: }
348: if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
1.8 ! djm 349: error_fr(r, "encode attestation information");
1.1 djm 350: goto out;
351: }
352:
353: /* success */
354: r = 0;
355: *keyp = key;
356: key = NULL;
357: out:
358: oerrno = errno;
359: sshkey_free(key);
360: sshbuf_free(kbuf);
361: sshbuf_free(abuf);
1.2 djm 362: sshbuf_free(req);
363: sshbuf_free(resp);
364: errno = oerrno;
365: return r;
366: }
367:
368: int
1.4 djm 369: sshsk_load_resident(const char *provider_path, const char *device,
370: const char *pin, struct sshkey ***keysp, size_t *nkeysp)
1.2 djm 371: {
372: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
373: struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
374: struct sshkey *key = NULL, **keys = NULL, **tmp;
375: size_t i, nkeys = 0;
376:
377: *keysp = NULL;
378: *nkeysp = 0;
379:
380: if ((resp = sshbuf_new()) == NULL ||
381: (kbuf = sshbuf_new()) == NULL ||
382: (req = sshbuf_new()) == NULL) {
383: r = SSH_ERR_ALLOC_FAIL;
384: goto out;
385: }
386:
1.5 djm 387: if ((r = sshbuf_put_cstring(req, provider_path)) != 0 ||
1.4 djm 388: (r = sshbuf_put_cstring(req, device)) != 0 ||
1.2 djm 389: (r = sshbuf_put_cstring(req, pin)) != 0) {
1.8 ! djm 390: error_fr(r, "compose");
1.2 djm 391: goto out;
392: }
393:
1.3 djm 394: if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
1.2 djm 395: goto out;
396:
397: while (sshbuf_len(resp) != 0) {
398: /* key, comment */
399: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
400: (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0) {
1.8 ! djm 401: error_fr(r, "parse signature");
1.2 djm 402: r = SSH_ERR_INVALID_FORMAT;
403: goto out;
404: }
405: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
1.8 ! djm 406: error_fr(r, "decode key");
1.2 djm 407: goto out;
408: }
409: if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
410: sizeof(*keys))) == NULL) {
1.8 ! djm 411: error_f("recallocarray keys failed");
1.2 djm 412: goto out;
413: }
1.8 ! djm 414: debug_f("keys[%zu]: %s %s", nkeys, sshkey_type(key),
! 415: key->sk_application);
1.2 djm 416: keys = tmp;
417: keys[nkeys++] = key;
418: key = NULL;
419: }
420:
421: /* success */
422: r = 0;
423: *keysp = keys;
424: *nkeysp = nkeys;
425: keys = NULL;
426: nkeys = 0;
427: out:
428: oerrno = errno;
429: for (i = 0; i < nkeys; i++)
430: sshkey_free(keys[i]);
431: free(keys);
432: sshkey_free(key);
433: sshbuf_free(kbuf);
1.1 djm 434: sshbuf_free(req);
435: sshbuf_free(resp);
436: errno = oerrno;
437: return r;
438: }