Annotation of src/usr.bin/ssh/ssh-sk-client.c, Revision 1.4
1.4 ! djm 1: /* $OpenBSD: ssh-sk-client.c,v 1.3 2019/12/30 09:23:28 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.3 djm 130: client_converse(struct sshbuf *req, struct sshbuf **respp, u_int msg)
1.1 djm 131: {
132: int oerrno, fd, r2, r = SSH_ERR_INTERNAL_ERROR;
1.3 djm 133: u_int rmsg, rerr;
1.1 djm 134: pid_t pid;
135: u_char version;
136: void (*osigchld)(int);
137: struct sshbuf *resp = NULL;
138: *respp = NULL;
139:
140: if ((r = start_helper(&fd, &pid, &osigchld)) != 0)
141: return r;
142:
143: if ((resp = sshbuf_new()) == NULL) {
144: r = SSH_ERR_ALLOC_FAIL;
145: goto out;
146: }
147:
148: if ((r = ssh_msg_send(fd, SSH_SK_HELPER_VERSION, req)) != 0) {
149: error("%s: send: %s", __func__, ssh_err(r));
150: goto out;
151: }
152: if ((r = ssh_msg_recv(fd, resp)) != 0) {
153: error("%s: receive: %s", __func__, ssh_err(r));
154: goto out;
155: }
156: if ((r = sshbuf_get_u8(resp, &version)) != 0) {
157: error("%s: parse version: %s", __func__, ssh_err(r));
158: goto out;
159: }
160: if (version != SSH_SK_HELPER_VERSION) {
161: error("%s: unsupported version: got %u, expected %u",
162: __func__, version, SSH_SK_HELPER_VERSION);
163: r = SSH_ERR_INVALID_FORMAT;
164: goto out;
165: }
1.3 djm 166: if ((r = sshbuf_get_u32(resp, &rmsg)) != 0) {
167: error("%s: parse message type: %s", __func__, ssh_err(r));
168: goto out;
169: }
170: if (rmsg == SSH_SK_HELPER_ERROR) {
171: if ((r = sshbuf_get_u32(resp, &rerr)) != 0) {
172: error("%s: parse error: %s", __func__, ssh_err(r));
173: goto out;
174: }
175: debug("%s: helper returned error -%u", __func__, rerr);
176: /* OpenSSH error values are negative; encoded as -err on wire */
177: if (rerr == 0 || rerr >= INT_MAX)
178: r = SSH_ERR_INTERNAL_ERROR;
179: else
180: r = -(int)rerr;
181: goto out;
182: } else if (rmsg != msg) {
183: error("%s: helper returned incorrect message type %u, "
184: "expecting %u", __func__, rmsg, msg);
185: r = SSH_ERR_INTERNAL_ERROR;
186: goto out;
187: }
1.1 djm 188: /* success */
189: r = 0;
190: out:
191: oerrno = errno;
192: close(fd);
193: if ((r2 = reap_helper(pid)) != 0) {
194: if (r == 0) {
195: r = r2;
196: oerrno = errno;
197: }
198: }
199: if (r == 0) {
200: *respp = resp;
201: resp = NULL;
202: }
203: sshbuf_free(resp);
204: signal(SIGCHLD, osigchld);
205: errno = oerrno;
206: return r;
207:
208: }
209:
210: int
211: sshsk_sign(const char *provider, struct sshkey *key,
212: u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
1.3 djm 213: u_int compat, const char *pin)
1.1 djm 214: {
215: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
216: char *fp = NULL;
217: struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
218:
219: *sigp = NULL;
220: *lenp = 0;
221:
222: if ((kbuf = sshbuf_new()) == NULL ||
223: (req = sshbuf_new()) == NULL) {
224: r = SSH_ERR_ALLOC_FAIL;
225: goto out;
226: }
227:
228: if ((r = sshkey_private_serialize(key, kbuf)) != 0) {
229: error("%s: serialize private key: %s", __func__, ssh_err(r));
230: goto out;
231: }
232: if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_SIGN)) != 0 ||
233: (r = sshbuf_put_stringb(req, kbuf)) != 0 ||
234: (r = sshbuf_put_cstring(req, provider)) != 0 ||
235: (r = sshbuf_put_string(req, data, datalen)) != 0 ||
236: (r = sshbuf_put_cstring(req, NULL)) != 0 || /* alg */
1.3 djm 237: (r = sshbuf_put_u32(req, compat)) != 0 ||
238: (r = sshbuf_put_cstring(req, pin)) != 0) {
1.1 djm 239: error("%s: compose: %s", __func__, ssh_err(r));
240: goto out;
241: }
242:
243: if ((fp = sshkey_fingerprint(key, SSH_FP_HASH_DEFAULT,
244: SSH_FP_DEFAULT)) == NULL) {
245: error("%s: sshkey_fingerprint failed", __func__);
246: r = SSH_ERR_ALLOC_FAIL;
247: goto out;
248: }
1.3 djm 249: if ((r = client_converse(req, &resp, SSH_SK_HELPER_SIGN)) != 0)
1.1 djm 250: goto out;
251:
252: if ((r = sshbuf_get_string(resp, sigp, lenp)) != 0) {
253: error("%s: parse signature: %s", __func__, ssh_err(r));
254: r = SSH_ERR_INVALID_FORMAT;
255: goto out;
256: }
257: if (sshbuf_len(resp) != 0) {
258: error("%s: trailing data in response", __func__);
259: r = SSH_ERR_INVALID_FORMAT;
260: goto out;
261: }
262: /* success */
263: r = 0;
264: out:
265: oerrno = errno;
266: if (r != 0) {
267: freezero(*sigp, *lenp);
268: *sigp = NULL;
269: *lenp = 0;
270: }
271: sshbuf_free(kbuf);
272: sshbuf_free(req);
273: sshbuf_free(resp);
274: errno = oerrno;
275: return r;
276: }
277:
278: int
1.4 ! djm 279: sshsk_enroll(int type, const char *provider_path, const char *device,
! 280: const char *application, const char *userid, uint8_t flags,
! 281: const char *pin, struct sshbuf *challenge_buf,
1.3 djm 282: struct sshkey **keyp, struct sshbuf *attest)
1.1 djm 283: {
284: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
285: struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
286: struct sshkey *key = NULL;
287:
288: *keyp = NULL;
289: if (attest != NULL)
290: sshbuf_reset(attest);
291:
292: if (type < 0)
293: return SSH_ERR_INVALID_ARGUMENT;
294:
295: if ((abuf = sshbuf_new()) == NULL ||
296: (kbuf = sshbuf_new()) == NULL ||
297: (req = sshbuf_new()) == NULL) {
298: r = SSH_ERR_ALLOC_FAIL;
299: goto out;
300: }
301:
302: if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_ENROLL)) != 0 ||
303: (r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
304: (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
1.4 ! djm 305: (r = sshbuf_put_cstring(req, device)) != 0 ||
1.1 djm 306: (r = sshbuf_put_cstring(req, application)) != 0 ||
1.4 ! djm 307: (r = sshbuf_put_cstring(req, userid)) != 0 ||
1.1 djm 308: (r = sshbuf_put_u8(req, flags)) != 0 ||
1.3 djm 309: (r = sshbuf_put_cstring(req, pin)) != 0 ||
1.1 djm 310: (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
311: error("%s: compose: %s", __func__, ssh_err(r));
312: goto out;
313: }
314:
1.3 djm 315: if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
1.1 djm 316: goto out;
317:
318: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
319: (r = sshbuf_get_stringb(resp, abuf)) != 0) {
320: error("%s: parse signature: %s", __func__, ssh_err(r));
321: r = SSH_ERR_INVALID_FORMAT;
322: goto out;
323: }
324: if (sshbuf_len(resp) != 0) {
325: error("%s: trailing data in response", __func__);
326: r = SSH_ERR_INVALID_FORMAT;
327: goto out;
328: }
329: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
330: error("Unable to parse private key: %s", ssh_err(r));
331: goto out;
332: }
333: if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
334: error("%s: buffer error: %s", __func__, ssh_err(r));
335: goto out;
336: }
337:
338: /* success */
339: r = 0;
340: *keyp = key;
341: key = NULL;
342: out:
343: oerrno = errno;
344: sshkey_free(key);
345: sshbuf_free(kbuf);
346: sshbuf_free(abuf);
1.2 djm 347: sshbuf_free(req);
348: sshbuf_free(resp);
349: errno = oerrno;
350: return r;
351: }
352:
353: int
1.4 ! djm 354: sshsk_load_resident(const char *provider_path, const char *device,
! 355: const char *pin, struct sshkey ***keysp, size_t *nkeysp)
1.2 djm 356: {
357: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
358: struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
359: struct sshkey *key = NULL, **keys = NULL, **tmp;
360: size_t i, nkeys = 0;
361:
362: *keysp = NULL;
363: *nkeysp = 0;
364:
365: if ((resp = sshbuf_new()) == NULL ||
366: (kbuf = sshbuf_new()) == NULL ||
367: (req = sshbuf_new()) == NULL) {
368: r = SSH_ERR_ALLOC_FAIL;
369: goto out;
370: }
371:
372: if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_LOAD_RESIDENT)) != 0 ||
373: (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
1.4 ! djm 374: (r = sshbuf_put_cstring(req, device)) != 0 ||
1.2 djm 375: (r = sshbuf_put_cstring(req, pin)) != 0) {
376: error("%s: compose: %s", __func__, ssh_err(r));
377: goto out;
378: }
379:
1.3 djm 380: if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
1.2 djm 381: goto out;
382:
383: while (sshbuf_len(resp) != 0) {
384: /* key, comment */
385: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
386: (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0) {
387: error("%s: parse signature: %s", __func__, ssh_err(r));
388: r = SSH_ERR_INVALID_FORMAT;
389: goto out;
390: }
391: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
392: error("Unable to parse private key: %s", ssh_err(r));
393: goto out;
394: }
395: if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
396: sizeof(*keys))) == NULL) {
397: error("%s: recallocarray keys failed", __func__);
398: goto out;
399: }
1.3 djm 400: debug("%s: keys[%zu]: %s %s", __func__,
401: nkeys, sshkey_type(key), key->sk_application);
1.2 djm 402: keys = tmp;
403: keys[nkeys++] = key;
404: key = NULL;
405: }
406:
407: /* success */
408: r = 0;
409: *keysp = keys;
410: *nkeysp = nkeys;
411: keys = NULL;
412: nkeys = 0;
413: out:
414: oerrno = errno;
415: for (i = 0; i < nkeys; i++)
416: sshkey_free(keys[i]);
417: free(keys);
418: sshkey_free(key);
419: sshbuf_free(kbuf);
1.1 djm 420: sshbuf_free(req);
421: sshbuf_free(resp);
422: errno = oerrno;
423: return r;
424: }