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