diff options
Diffstat (limited to 'ssh-sk-helper.c')
-rw-r--r-- | ssh-sk-helper.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/ssh-sk-helper.c b/ssh-sk-helper.c new file mode 100644 index 000000000..2f93ad716 --- /dev/null +++ b/ssh-sk-helper.c | |||
@@ -0,0 +1,360 @@ | |||
1 | /* $OpenBSD: ssh-sk-helper.c,v 1.9 2020/01/25 23:13:09 djm Exp $ */ | ||
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 | /* | ||
19 | * This is a tiny program used to isolate the address space used for | ||
20 | * security key middleware signing operations from ssh-agent. It is similar | ||
21 | * to ssh-pkcs11-helper.c but considerably simpler as the operations for | ||
22 | * security keys are stateless. | ||
23 | * | ||
24 | * Please crank SSH_SK_HELPER_VERSION in sshkey.h for any incompatible | ||
25 | * protocol changes. | ||
26 | */ | ||
27 | |||
28 | #include "includes.h" | ||
29 | |||
30 | #include <limits.h> | ||
31 | #include <stdarg.h> | ||
32 | #include <stdio.h> | ||
33 | #include <stdlib.h> | ||
34 | #include <string.h> | ||
35 | #include <unistd.h> | ||
36 | #include <errno.h> | ||
37 | |||
38 | #include "xmalloc.h" | ||
39 | #include "log.h" | ||
40 | #include "sshkey.h" | ||
41 | #include "authfd.h" | ||
42 | #include "misc.h" | ||
43 | #include "sshbuf.h" | ||
44 | #include "msg.h" | ||
45 | #include "uidswap.h" | ||
46 | #include "sshkey.h" | ||
47 | #include "ssherr.h" | ||
48 | #include "ssh-sk.h" | ||
49 | |||
50 | #ifdef ENABLE_SK | ||
51 | extern char *__progname; | ||
52 | |||
53 | static struct sshbuf *reply_error(int r, char *fmt, ...) | ||
54 | __attribute__((__format__ (printf, 2, 3))); | ||
55 | |||
56 | static struct sshbuf * | ||
57 | reply_error(int r, char *fmt, ...) | ||
58 | { | ||
59 | char *msg; | ||
60 | va_list ap; | ||
61 | struct sshbuf *resp; | ||
62 | |||
63 | va_start(ap, fmt); | ||
64 | xvasprintf(&msg, fmt, ap); | ||
65 | va_end(ap); | ||
66 | debug("%s: %s", __progname, msg); | ||
67 | free(msg); | ||
68 | |||
69 | if (r >= 0) | ||
70 | fatal("%s: invalid error code %d", __func__, r); | ||
71 | |||
72 | if ((resp = sshbuf_new()) == NULL) | ||
73 | fatal("%s: sshbuf_new failed", __progname); | ||
74 | if (sshbuf_put_u32(resp, SSH_SK_HELPER_ERROR) != 0 || | ||
75 | sshbuf_put_u32(resp, (u_int)-r) != 0) | ||
76 | fatal("%s: buffer error", __progname); | ||
77 | return resp; | ||
78 | } | ||
79 | |||
80 | /* If the specified string is zero length, then free it and replace with NULL */ | ||
81 | static void | ||
82 | null_empty(char **s) | ||
83 | { | ||
84 | if (s == NULL || *s == NULL || **s != '\0') | ||
85 | return; | ||
86 | |||
87 | free(*s); | ||
88 | *s = NULL; | ||
89 | } | ||
90 | |||
91 | static struct sshbuf * | ||
92 | process_sign(struct sshbuf *req) | ||
93 | { | ||
94 | int r = SSH_ERR_INTERNAL_ERROR; | ||
95 | struct sshbuf *resp, *kbuf; | ||
96 | struct sshkey *key; | ||
97 | uint32_t compat; | ||
98 | const u_char *message; | ||
99 | u_char *sig; | ||
100 | size_t msglen, siglen; | ||
101 | char *provider, *pin; | ||
102 | |||
103 | if ((r = sshbuf_froms(req, &kbuf)) != 0 || | ||
104 | (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || | ||
105 | (r = sshbuf_get_string_direct(req, &message, &msglen)) != 0 || | ||
106 | (r = sshbuf_get_cstring(req, NULL, NULL)) != 0 || /* alg */ | ||
107 | (r = sshbuf_get_u32(req, &compat)) != 0 || | ||
108 | (r = sshbuf_get_cstring(req, &pin, NULL)) != 0) | ||
109 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
110 | if (sshbuf_len(req) != 0) | ||
111 | fatal("%s: trailing data in request", __progname); | ||
112 | |||
113 | if ((r = sshkey_private_deserialize(kbuf, &key)) != 0) | ||
114 | fatal("Unable to parse private key: %s", ssh_err(r)); | ||
115 | if (!sshkey_is_sk(key)) | ||
116 | fatal("Unsupported key type %s", sshkey_ssh_name(key)); | ||
117 | |||
118 | debug("%s: ready to sign with key %s, provider %s: " | ||
119 | "msg len %zu, compat 0x%lx", __progname, sshkey_type(key), | ||
120 | provider, msglen, (u_long)compat); | ||
121 | |||
122 | null_empty(&pin); | ||
123 | |||
124 | if ((r = sshsk_sign(provider, key, &sig, &siglen, | ||
125 | message, msglen, compat, pin)) != 0) { | ||
126 | resp = reply_error(r, "Signing failed: %s", ssh_err(r)); | ||
127 | goto out; | ||
128 | } | ||
129 | |||
130 | if ((resp = sshbuf_new()) == NULL) | ||
131 | fatal("%s: sshbuf_new failed", __progname); | ||
132 | |||
133 | if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_SIGN)) != 0 || | ||
134 | (r = sshbuf_put_string(resp, sig, siglen)) != 0) | ||
135 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
136 | out: | ||
137 | sshbuf_free(kbuf); | ||
138 | free(provider); | ||
139 | if (pin != NULL) | ||
140 | freezero(pin, strlen(pin)); | ||
141 | return resp; | ||
142 | } | ||
143 | |||
144 | static struct sshbuf * | ||
145 | process_enroll(struct sshbuf *req) | ||
146 | { | ||
147 | int r; | ||
148 | u_int type; | ||
149 | char *provider, *application, *pin, *device, *userid; | ||
150 | uint8_t flags; | ||
151 | struct sshbuf *challenge, *attest, *kbuf, *resp; | ||
152 | struct sshkey *key; | ||
153 | |||
154 | if ((attest = sshbuf_new()) == NULL || | ||
155 | (kbuf = sshbuf_new()) == NULL) | ||
156 | fatal("%s: sshbuf_new failed", __progname); | ||
157 | |||
158 | if ((r = sshbuf_get_u32(req, &type)) != 0 || | ||
159 | (r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || | ||
160 | (r = sshbuf_get_cstring(req, &device, NULL)) != 0 || | ||
161 | (r = sshbuf_get_cstring(req, &application, NULL)) != 0 || | ||
162 | (r = sshbuf_get_cstring(req, &userid, NULL)) != 0 || | ||
163 | (r = sshbuf_get_u8(req, &flags)) != 0 || | ||
164 | (r = sshbuf_get_cstring(req, &pin, NULL)) != 0 || | ||
165 | (r = sshbuf_froms(req, &challenge)) != 0) | ||
166 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
167 | if (sshbuf_len(req) != 0) | ||
168 | fatal("%s: trailing data in request", __progname); | ||
169 | |||
170 | if (type > INT_MAX) | ||
171 | fatal("%s: bad type %u", __progname, type); | ||
172 | if (sshbuf_len(challenge) == 0) { | ||
173 | sshbuf_free(challenge); | ||
174 | challenge = NULL; | ||
175 | } | ||
176 | null_empty(&device); | ||
177 | null_empty(&userid); | ||
178 | null_empty(&pin); | ||
179 | |||
180 | if ((r = sshsk_enroll((int)type, provider, device, application, userid, | ||
181 | flags, pin, challenge, &key, attest)) != 0) { | ||
182 | resp = reply_error(r, "Enrollment failed: %s", ssh_err(r)); | ||
183 | goto out; | ||
184 | } | ||
185 | |||
186 | if ((resp = sshbuf_new()) == NULL) | ||
187 | fatal("%s: sshbuf_new failed", __progname); | ||
188 | if ((r = sshkey_private_serialize(key, kbuf)) != 0) | ||
189 | fatal("%s: serialize private key: %s", __progname, ssh_err(r)); | ||
190 | if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_ENROLL)) != 0 || | ||
191 | (r = sshbuf_put_stringb(resp, kbuf)) != 0 || | ||
192 | (r = sshbuf_put_stringb(resp, attest)) != 0) | ||
193 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
194 | |||
195 | out: | ||
196 | sshkey_free(key); | ||
197 | sshbuf_free(kbuf); | ||
198 | sshbuf_free(attest); | ||
199 | sshbuf_free(challenge); | ||
200 | free(provider); | ||
201 | free(application); | ||
202 | if (pin != NULL) | ||
203 | freezero(pin, strlen(pin)); | ||
204 | |||
205 | return resp; | ||
206 | } | ||
207 | |||
208 | static struct sshbuf * | ||
209 | process_load_resident(struct sshbuf *req) | ||
210 | { | ||
211 | int r; | ||
212 | char *provider, *pin, *device; | ||
213 | struct sshbuf *kbuf, *resp; | ||
214 | struct sshkey **keys = NULL; | ||
215 | size_t nkeys = 0, i; | ||
216 | |||
217 | if ((kbuf = sshbuf_new()) == NULL) | ||
218 | fatal("%s: sshbuf_new failed", __progname); | ||
219 | |||
220 | if ((r = sshbuf_get_cstring(req, &provider, NULL)) != 0 || | ||
221 | (r = sshbuf_get_cstring(req, &device, NULL)) != 0 || | ||
222 | (r = sshbuf_get_cstring(req, &pin, NULL)) != 0) | ||
223 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
224 | if (sshbuf_len(req) != 0) | ||
225 | fatal("%s: trailing data in request", __progname); | ||
226 | |||
227 | null_empty(&device); | ||
228 | null_empty(&pin); | ||
229 | |||
230 | if ((r = sshsk_load_resident(provider, device, pin, | ||
231 | &keys, &nkeys)) != 0) { | ||
232 | resp = reply_error(r, " sshsk_load_resident failed: %s", | ||
233 | ssh_err(r)); | ||
234 | goto out; | ||
235 | } | ||
236 | |||
237 | if ((resp = sshbuf_new()) == NULL) | ||
238 | fatal("%s: sshbuf_new failed", __progname); | ||
239 | |||
240 | if ((r = sshbuf_put_u32(resp, SSH_SK_HELPER_LOAD_RESIDENT)) != 0) | ||
241 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
242 | |||
243 | for (i = 0; i < nkeys; i++) { | ||
244 | debug("%s: key %zu %s %s", __func__, i, | ||
245 | sshkey_type(keys[i]), keys[i]->sk_application); | ||
246 | sshbuf_reset(kbuf); | ||
247 | if ((r = sshkey_private_serialize(keys[i], kbuf)) != 0) | ||
248 | fatal("%s: serialize private key: %s", | ||
249 | __progname, ssh_err(r)); | ||
250 | if ((r = sshbuf_put_stringb(resp, kbuf)) != 0 || | ||
251 | (r = sshbuf_put_cstring(resp, "")) != 0) /* comment */ | ||
252 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
253 | } | ||
254 | |||
255 | out: | ||
256 | for (i = 0; i < nkeys; i++) | ||
257 | sshkey_free(keys[i]); | ||
258 | free(keys); | ||
259 | sshbuf_free(kbuf); | ||
260 | free(provider); | ||
261 | if (pin != NULL) | ||
262 | freezero(pin, strlen(pin)); | ||
263 | return resp; | ||
264 | } | ||
265 | |||
266 | int | ||
267 | main(int argc, char **argv) | ||
268 | { | ||
269 | SyslogFacility log_facility = SYSLOG_FACILITY_AUTH; | ||
270 | LogLevel log_level = SYSLOG_LEVEL_ERROR; | ||
271 | struct sshbuf *req, *resp; | ||
272 | int in, out, ch, r, vflag = 0; | ||
273 | u_int rtype, ll = 0; | ||
274 | uint8_t version, log_stderr = 0; | ||
275 | |||
276 | sanitise_stdfd(); | ||
277 | log_init(__progname, log_level, log_facility, log_stderr); | ||
278 | |||
279 | while ((ch = getopt(argc, argv, "v")) != -1) { | ||
280 | switch (ch) { | ||
281 | case 'v': | ||
282 | vflag = 1; | ||
283 | if (log_level == SYSLOG_LEVEL_ERROR) | ||
284 | log_level = SYSLOG_LEVEL_DEBUG1; | ||
285 | else if (log_level < SYSLOG_LEVEL_DEBUG3) | ||
286 | log_level++; | ||
287 | break; | ||
288 | default: | ||
289 | fprintf(stderr, "usage: %s [-v]\n", __progname); | ||
290 | exit(1); | ||
291 | } | ||
292 | } | ||
293 | log_init(__progname, log_level, log_facility, vflag); | ||
294 | |||
295 | /* | ||
296 | * Rearrange our file descriptors a little; we don't trust the | ||
297 | * providers not to fiddle with stdin/out. | ||
298 | */ | ||
299 | closefrom(STDERR_FILENO + 1); | ||
300 | if ((in = dup(STDIN_FILENO)) == -1 || (out = dup(STDOUT_FILENO)) == -1) | ||
301 | fatal("%s: dup: %s", __progname, strerror(errno)); | ||
302 | close(STDIN_FILENO); | ||
303 | close(STDOUT_FILENO); | ||
304 | sanitise_stdfd(); /* resets to /dev/null */ | ||
305 | |||
306 | if ((req = sshbuf_new()) == NULL) | ||
307 | fatal("%s: sshbuf_new failed", __progname); | ||
308 | if (ssh_msg_recv(in, req) < 0) | ||
309 | fatal("ssh_msg_recv failed"); | ||
310 | close(in); | ||
311 | debug("%s: received message len %zu", __progname, sshbuf_len(req)); | ||
312 | |||
313 | if ((r = sshbuf_get_u8(req, &version)) != 0) | ||
314 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
315 | if (version != SSH_SK_HELPER_VERSION) { | ||
316 | fatal("unsupported version: received %d, expected %d", | ||
317 | version, SSH_SK_HELPER_VERSION); | ||
318 | } | ||
319 | |||
320 | if ((r = sshbuf_get_u32(req, &rtype)) != 0 || | ||
321 | (r = sshbuf_get_u8(req, &log_stderr)) != 0 || | ||
322 | (r = sshbuf_get_u32(req, &ll)) != 0) | ||
323 | fatal("%s: buffer error: %s", __progname, ssh_err(r)); | ||
324 | |||
325 | if (!vflag && log_level_name((LogLevel)ll) != NULL) | ||
326 | log_init(__progname, (LogLevel)ll, log_facility, log_stderr); | ||
327 | |||
328 | switch (rtype) { | ||
329 | case SSH_SK_HELPER_SIGN: | ||
330 | resp = process_sign(req); | ||
331 | break; | ||
332 | case SSH_SK_HELPER_ENROLL: | ||
333 | resp = process_enroll(req); | ||
334 | break; | ||
335 | case SSH_SK_HELPER_LOAD_RESIDENT: | ||
336 | resp = process_load_resident(req); | ||
337 | break; | ||
338 | default: | ||
339 | fatal("%s: unsupported request type %u", __progname, rtype); | ||
340 | } | ||
341 | sshbuf_free(req); | ||
342 | debug("%s: reply len %zu", __progname, sshbuf_len(resp)); | ||
343 | |||
344 | if (ssh_msg_send(out, SSH_SK_HELPER_VERSION, resp) == -1) | ||
345 | fatal("ssh_msg_send failed"); | ||
346 | sshbuf_free(resp); | ||
347 | close(out); | ||
348 | |||
349 | return (0); | ||
350 | } | ||
351 | #else /* ENABLE_SK */ | ||
352 | #include <stdio.h> | ||
353 | |||
354 | int | ||
355 | main(int argc, char **argv) | ||
356 | { | ||
357 | fprintf(stderr, "ssh-sk-helper: disabled at compile time\n"); | ||
358 | return -1; | ||
359 | } | ||
360 | #endif /* ENABLE_SK */ | ||