Annotation of src/usr.bin/ssh/ssh-sk-client.c, Revision 1.3
1.3 ! djm 1: /* $OpenBSD: ssh-sk-client.c,v 1.2 2019/12/30 09:21:59 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
279: sshsk_enroll(int type, const char *provider_path, const char *application,
1.3 ! djm 280: uint8_t flags, const char *pin, struct sshbuf *challenge_buf,
! 281: struct sshkey **keyp, struct sshbuf *attest)
1.1 djm 282: {
283: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
284: struct sshbuf *kbuf = NULL, *abuf = NULL, *req = NULL, *resp = NULL;
285: struct sshkey *key = NULL;
286:
287: *keyp = NULL;
288: if (attest != NULL)
289: sshbuf_reset(attest);
290:
291: if (type < 0)
292: return SSH_ERR_INVALID_ARGUMENT;
293:
294: if ((abuf = sshbuf_new()) == NULL ||
295: (kbuf = sshbuf_new()) == NULL ||
296: (req = sshbuf_new()) == NULL) {
297: r = SSH_ERR_ALLOC_FAIL;
298: goto out;
299: }
300:
301: if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_ENROLL)) != 0 ||
302: (r = sshbuf_put_u32(req, (u_int)type)) != 0 ||
303: (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
304: (r = sshbuf_put_cstring(req, application)) != 0 ||
305: (r = sshbuf_put_u8(req, flags)) != 0 ||
1.3 ! djm 306: (r = sshbuf_put_cstring(req, pin)) != 0 ||
1.1 djm 307: (r = sshbuf_put_stringb(req, challenge_buf)) != 0) {
308: error("%s: compose: %s", __func__, ssh_err(r));
309: goto out;
310: }
311:
1.3 ! djm 312: if ((r = client_converse(req, &resp, SSH_SK_HELPER_ENROLL)) != 0)
1.1 djm 313: goto out;
314:
315: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
316: (r = sshbuf_get_stringb(resp, abuf)) != 0) {
317: error("%s: parse signature: %s", __func__, ssh_err(r));
318: r = SSH_ERR_INVALID_FORMAT;
319: goto out;
320: }
321: if (sshbuf_len(resp) != 0) {
322: error("%s: trailing data in response", __func__);
323: r = SSH_ERR_INVALID_FORMAT;
324: goto out;
325: }
326: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
327: error("Unable to parse private key: %s", ssh_err(r));
328: goto out;
329: }
330: if (attest != NULL && (r = sshbuf_putb(attest, abuf)) != 0) {
331: error("%s: buffer error: %s", __func__, ssh_err(r));
332: goto out;
333: }
334:
335: /* success */
336: r = 0;
337: *keyp = key;
338: key = NULL;
339: out:
340: oerrno = errno;
341: sshkey_free(key);
342: sshbuf_free(kbuf);
343: sshbuf_free(abuf);
1.2 djm 344: sshbuf_free(req);
345: sshbuf_free(resp);
346: errno = oerrno;
347: return r;
348: }
349:
350: int
351: sshsk_load_resident(const char *provider_path, const char *pin,
352: struct sshkey ***keysp, size_t *nkeysp)
353: {
354: int oerrno, r = SSH_ERR_INTERNAL_ERROR;
355: struct sshbuf *kbuf = NULL, *req = NULL, *resp = NULL;
356: struct sshkey *key = NULL, **keys = NULL, **tmp;
357: size_t i, nkeys = 0;
358:
359: *keysp = NULL;
360: *nkeysp = 0;
361:
362: if ((resp = sshbuf_new()) == NULL ||
363: (kbuf = sshbuf_new()) == NULL ||
364: (req = sshbuf_new()) == NULL) {
365: r = SSH_ERR_ALLOC_FAIL;
366: goto out;
367: }
368:
369: if ((r = sshbuf_put_u32(req, SSH_SK_HELPER_LOAD_RESIDENT)) != 0 ||
370: (r = sshbuf_put_cstring(req, provider_path)) != 0 ||
371: (r = sshbuf_put_cstring(req, pin)) != 0) {
372: error("%s: compose: %s", __func__, ssh_err(r));
373: goto out;
374: }
375:
1.3 ! djm 376: if ((r = client_converse(req, &resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0)
1.2 djm 377: goto out;
378:
379: while (sshbuf_len(resp) != 0) {
380: /* key, comment */
381: if ((r = sshbuf_get_stringb(resp, kbuf)) != 0 ||
382: (r = sshbuf_get_cstring(resp, NULL, NULL)) != 0) {
383: error("%s: parse signature: %s", __func__, ssh_err(r));
384: r = SSH_ERR_INVALID_FORMAT;
385: goto out;
386: }
387: if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) {
388: error("Unable to parse private key: %s", ssh_err(r));
389: goto out;
390: }
391: if ((tmp = recallocarray(keys, nkeys, nkeys + 1,
392: sizeof(*keys))) == NULL) {
393: error("%s: recallocarray keys failed", __func__);
394: goto out;
395: }
1.3 ! djm 396: debug("%s: keys[%zu]: %s %s", __func__,
! 397: nkeys, sshkey_type(key), key->sk_application);
1.2 djm 398: keys = tmp;
399: keys[nkeys++] = key;
400: key = NULL;
401: }
402:
403: /* success */
404: r = 0;
405: *keysp = keys;
406: *nkeysp = nkeys;
407: keys = NULL;
408: nkeys = 0;
409: out:
410: oerrno = errno;
411: for (i = 0; i < nkeys; i++)
412: sshkey_free(keys[i]);
413: free(keys);
414: sshkey_free(key);
415: sshbuf_free(kbuf);
1.1 djm 416: sshbuf_free(req);
417: sshbuf_free(resp);
418: errno = oerrno;
419: return r;
420: }