diff options
Diffstat (limited to 'sk-usbhid.c')
-rw-r--r-- | sk-usbhid.c | 1024 |
1 files changed, 1024 insertions, 0 deletions
diff --git a/sk-usbhid.c b/sk-usbhid.c new file mode 100644 index 000000000..ad83054ad --- /dev/null +++ b/sk-usbhid.c | |||
@@ -0,0 +1,1024 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2019 Markus Friedl | ||
3 | * | ||
4 | * Permission to use, copy, modify, and distribute this software for any | ||
5 | * purpose with or without fee is hereby granted, provided that the above | ||
6 | * copyright notice and this permission notice appear in all copies. | ||
7 | * | ||
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
15 | */ | ||
16 | |||
17 | #include "includes.h" | ||
18 | |||
19 | #ifdef ENABLE_SK_INTERNAL | ||
20 | |||
21 | #include <stdint.h> | ||
22 | #include <stdlib.h> | ||
23 | #include <string.h> | ||
24 | #include <stdio.h> | ||
25 | #include <stddef.h> | ||
26 | #include <stdarg.h> | ||
27 | |||
28 | #ifdef WITH_OPENSSL | ||
29 | #include <openssl/opensslv.h> | ||
30 | #include <openssl/crypto.h> | ||
31 | #include <openssl/bn.h> | ||
32 | #include <openssl/ec.h> | ||
33 | #include <openssl/ecdsa.h> | ||
34 | #endif /* WITH_OPENSSL */ | ||
35 | |||
36 | #include <fido.h> | ||
37 | #include <fido/credman.h> | ||
38 | |||
39 | #ifndef SK_STANDALONE | ||
40 | # include "log.h" | ||
41 | # include "xmalloc.h" | ||
42 | /* | ||
43 | * If building as part of OpenSSH, then rename exported functions. | ||
44 | * This must be done before including sk-api.h. | ||
45 | */ | ||
46 | # define sk_api_version ssh_sk_api_version | ||
47 | # define sk_enroll ssh_sk_enroll | ||
48 | # define sk_sign ssh_sk_sign | ||
49 | # define sk_load_resident_keys ssh_sk_load_resident_keys | ||
50 | #endif /* !SK_STANDALONE */ | ||
51 | |||
52 | #include "sk-api.h" | ||
53 | |||
54 | /* #define SK_DEBUG 1 */ | ||
55 | |||
56 | #define MAX_FIDO_DEVICES 256 | ||
57 | |||
58 | /* Compatibility with OpenSSH 1.0.x */ | ||
59 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) | ||
60 | #define ECDSA_SIG_get0(sig, pr, ps) \ | ||
61 | do { \ | ||
62 | (*pr) = sig->r; \ | ||
63 | (*ps) = sig->s; \ | ||
64 | } while (0) | ||
65 | #endif | ||
66 | |||
67 | /* Return the version of the middleware API */ | ||
68 | uint32_t sk_api_version(void); | ||
69 | |||
70 | /* Enroll a U2F key (private key generation) */ | ||
71 | int sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, | ||
72 | const char *application, uint8_t flags, const char *pin, | ||
73 | struct sk_option **options, struct sk_enroll_response **enroll_response); | ||
74 | |||
75 | /* Sign a challenge */ | ||
76 | int sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, | ||
77 | const char *application, const uint8_t *key_handle, size_t key_handle_len, | ||
78 | uint8_t flags, const char *pin, struct sk_option **options, | ||
79 | struct sk_sign_response **sign_response); | ||
80 | |||
81 | /* Load resident keys */ | ||
82 | int sk_load_resident_keys(const char *pin, struct sk_option **options, | ||
83 | struct sk_resident_key ***rks, size_t *nrks); | ||
84 | |||
85 | static void skdebug(const char *func, const char *fmt, ...) | ||
86 | __attribute__((__format__ (printf, 2, 3))); | ||
87 | |||
88 | static void | ||
89 | skdebug(const char *func, const char *fmt, ...) | ||
90 | { | ||
91 | #if !defined(SK_STANDALONE) | ||
92 | char *msg; | ||
93 | va_list ap; | ||
94 | |||
95 | va_start(ap, fmt); | ||
96 | xvasprintf(&msg, fmt, ap); | ||
97 | va_end(ap); | ||
98 | debug("%s: %s", func, msg); | ||
99 | free(msg); | ||
100 | #elif defined(SK_DEBUG) | ||
101 | va_list ap; | ||
102 | |||
103 | va_start(ap, fmt); | ||
104 | fprintf(stderr, "%s: ", func); | ||
105 | vfprintf(stderr, fmt, ap); | ||
106 | fputc('\n', stderr); | ||
107 | va_end(ap); | ||
108 | #else | ||
109 | (void)func; /* XXX */ | ||
110 | (void)fmt; /* XXX */ | ||
111 | #endif | ||
112 | } | ||
113 | |||
114 | uint32_t | ||
115 | sk_api_version(void) | ||
116 | { | ||
117 | return SSH_SK_VERSION_MAJOR; | ||
118 | } | ||
119 | |||
120 | /* Select the first identified FIDO device attached to the system */ | ||
121 | static char * | ||
122 | pick_first_device(void) | ||
123 | { | ||
124 | char *ret = NULL; | ||
125 | fido_dev_info_t *devlist = NULL; | ||
126 | size_t olen = 0; | ||
127 | int r; | ||
128 | const fido_dev_info_t *di; | ||
129 | |||
130 | if ((devlist = fido_dev_info_new(1)) == NULL) { | ||
131 | skdebug(__func__, "fido_dev_info_new failed"); | ||
132 | goto out; | ||
133 | } | ||
134 | if ((r = fido_dev_info_manifest(devlist, 1, &olen)) != FIDO_OK) { | ||
135 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
136 | fido_strerr(r)); | ||
137 | goto out; | ||
138 | } | ||
139 | if (olen != 1) { | ||
140 | skdebug(__func__, "fido_dev_info_manifest bad len %zu", olen); | ||
141 | goto out; | ||
142 | } | ||
143 | di = fido_dev_info_ptr(devlist, 0); | ||
144 | if ((ret = strdup(fido_dev_info_path(di))) == NULL) { | ||
145 | skdebug(__func__, "fido_dev_info_path failed"); | ||
146 | goto out; | ||
147 | } | ||
148 | out: | ||
149 | fido_dev_info_free(&devlist, 1); | ||
150 | return ret; | ||
151 | } | ||
152 | |||
153 | /* Check if the specified key handle exists on a given device. */ | ||
154 | static int | ||
155 | try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len, | ||
156 | const char *application, const uint8_t *key_handle, size_t key_handle_len) | ||
157 | { | ||
158 | fido_assert_t *assert = NULL; | ||
159 | int r = FIDO_ERR_INTERNAL; | ||
160 | |||
161 | if ((assert = fido_assert_new()) == NULL) { | ||
162 | skdebug(__func__, "fido_assert_new failed"); | ||
163 | goto out; | ||
164 | } | ||
165 | if ((r = fido_assert_set_clientdata_hash(assert, message, | ||
166 | message_len)) != FIDO_OK) { | ||
167 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", | ||
168 | fido_strerr(r)); | ||
169 | goto out; | ||
170 | } | ||
171 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { | ||
172 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); | ||
173 | goto out; | ||
174 | } | ||
175 | if ((r = fido_assert_allow_cred(assert, key_handle, | ||
176 | key_handle_len)) != FIDO_OK) { | ||
177 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); | ||
178 | goto out; | ||
179 | } | ||
180 | if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) { | ||
181 | skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); | ||
182 | goto out; | ||
183 | } | ||
184 | r = fido_dev_get_assert(dev, assert, NULL); | ||
185 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); | ||
186 | if (r == FIDO_ERR_USER_PRESENCE_REQUIRED) { | ||
187 | /* U2F tokens may return this */ | ||
188 | r = FIDO_OK; | ||
189 | } | ||
190 | out: | ||
191 | fido_assert_free(&assert); | ||
192 | |||
193 | return r != FIDO_OK ? -1 : 0; | ||
194 | } | ||
195 | |||
196 | /* Iterate over configured devices looking for a specific key handle */ | ||
197 | static fido_dev_t * | ||
198 | find_device(const char *path, const uint8_t *message, size_t message_len, | ||
199 | const char *application, const uint8_t *key_handle, size_t key_handle_len) | ||
200 | { | ||
201 | fido_dev_info_t *devlist = NULL; | ||
202 | fido_dev_t *dev = NULL; | ||
203 | size_t devlist_len = 0, i; | ||
204 | int r; | ||
205 | |||
206 | if (path != NULL) { | ||
207 | if ((dev = fido_dev_new()) == NULL) { | ||
208 | skdebug(__func__, "fido_dev_new failed"); | ||
209 | return NULL; | ||
210 | } | ||
211 | if ((r = fido_dev_open(dev, path)) != FIDO_OK) { | ||
212 | skdebug(__func__, "fido_dev_open failed"); | ||
213 | fido_dev_free(&dev); | ||
214 | return NULL; | ||
215 | } | ||
216 | return dev; | ||
217 | } | ||
218 | |||
219 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | ||
220 | skdebug(__func__, "fido_dev_info_new failed"); | ||
221 | goto out; | ||
222 | } | ||
223 | if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, | ||
224 | &devlist_len)) != FIDO_OK) { | ||
225 | skdebug(__func__, "fido_dev_info_manifest: %s", fido_strerr(r)); | ||
226 | goto out; | ||
227 | } | ||
228 | |||
229 | skdebug(__func__, "found %zu device(s)", devlist_len); | ||
230 | |||
231 | for (i = 0; i < devlist_len; i++) { | ||
232 | const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); | ||
233 | |||
234 | if (di == NULL) { | ||
235 | skdebug(__func__, "fido_dev_info_ptr %zu failed", i); | ||
236 | continue; | ||
237 | } | ||
238 | if ((path = fido_dev_info_path(di)) == NULL) { | ||
239 | skdebug(__func__, "fido_dev_info_path %zu failed", i); | ||
240 | continue; | ||
241 | } | ||
242 | skdebug(__func__, "trying device %zu: %s", i, path); | ||
243 | if ((dev = fido_dev_new()) == NULL) { | ||
244 | skdebug(__func__, "fido_dev_new failed"); | ||
245 | continue; | ||
246 | } | ||
247 | if ((r = fido_dev_open(dev, path)) != FIDO_OK) { | ||
248 | skdebug(__func__, "fido_dev_open failed"); | ||
249 | fido_dev_free(&dev); | ||
250 | continue; | ||
251 | } | ||
252 | if (try_device(dev, message, message_len, application, | ||
253 | key_handle, key_handle_len) == 0) { | ||
254 | skdebug(__func__, "found key"); | ||
255 | break; | ||
256 | } | ||
257 | fido_dev_close(dev); | ||
258 | fido_dev_free(&dev); | ||
259 | } | ||
260 | |||
261 | out: | ||
262 | if (devlist != NULL) | ||
263 | fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); | ||
264 | |||
265 | return dev; | ||
266 | } | ||
267 | |||
268 | #ifdef WITH_OPENSSL | ||
269 | /* | ||
270 | * The key returned via fido_cred_pubkey_ptr() is in affine coordinates, | ||
271 | * but the API expects a SEC1 octet string. | ||
272 | */ | ||
273 | static int | ||
274 | pack_public_key_ecdsa(const fido_cred_t *cred, | ||
275 | struct sk_enroll_response *response) | ||
276 | { | ||
277 | const uint8_t *ptr; | ||
278 | BIGNUM *x = NULL, *y = NULL; | ||
279 | EC_POINT *q = NULL; | ||
280 | EC_GROUP *g = NULL; | ||
281 | int ret = -1; | ||
282 | |||
283 | response->public_key = NULL; | ||
284 | response->public_key_len = 0; | ||
285 | |||
286 | if ((x = BN_new()) == NULL || | ||
287 | (y = BN_new()) == NULL || | ||
288 | (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || | ||
289 | (q = EC_POINT_new(g)) == NULL) { | ||
290 | skdebug(__func__, "libcrypto setup failed"); | ||
291 | goto out; | ||
292 | } | ||
293 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { | ||
294 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); | ||
295 | goto out; | ||
296 | } | ||
297 | if (fido_cred_pubkey_len(cred) != 64) { | ||
298 | skdebug(__func__, "bad fido_cred_pubkey_len %zu", | ||
299 | fido_cred_pubkey_len(cred)); | ||
300 | goto out; | ||
301 | } | ||
302 | |||
303 | if (BN_bin2bn(ptr, 32, x) == NULL || | ||
304 | BN_bin2bn(ptr + 32, 32, y) == NULL) { | ||
305 | skdebug(__func__, "BN_bin2bn failed"); | ||
306 | goto out; | ||
307 | } | ||
308 | if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, NULL) != 1) { | ||
309 | skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed"); | ||
310 | goto out; | ||
311 | } | ||
312 | response->public_key_len = EC_POINT_point2oct(g, q, | ||
313 | POINT_CONVERSION_UNCOMPRESSED, NULL, 0, NULL); | ||
314 | if (response->public_key_len == 0 || response->public_key_len > 2048) { | ||
315 | skdebug(__func__, "bad pubkey length %zu", | ||
316 | response->public_key_len); | ||
317 | goto out; | ||
318 | } | ||
319 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
320 | skdebug(__func__, "malloc pubkey failed"); | ||
321 | goto out; | ||
322 | } | ||
323 | if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, | ||
324 | response->public_key, response->public_key_len, NULL) == 0) { | ||
325 | skdebug(__func__, "EC_POINT_point2oct failed"); | ||
326 | goto out; | ||
327 | } | ||
328 | /* success */ | ||
329 | ret = 0; | ||
330 | out: | ||
331 | if (ret != 0 && response->public_key != NULL) { | ||
332 | memset(response->public_key, 0, response->public_key_len); | ||
333 | free(response->public_key); | ||
334 | response->public_key = NULL; | ||
335 | } | ||
336 | EC_POINT_free(q); | ||
337 | EC_GROUP_free(g); | ||
338 | BN_clear_free(x); | ||
339 | BN_clear_free(y); | ||
340 | return ret; | ||
341 | } | ||
342 | #endif /* WITH_OPENSSL */ | ||
343 | |||
344 | static int | ||
345 | pack_public_key_ed25519(const fido_cred_t *cred, | ||
346 | struct sk_enroll_response *response) | ||
347 | { | ||
348 | const uint8_t *ptr; | ||
349 | size_t len; | ||
350 | int ret = -1; | ||
351 | |||
352 | response->public_key = NULL; | ||
353 | response->public_key_len = 0; | ||
354 | |||
355 | if ((len = fido_cred_pubkey_len(cred)) != 32) { | ||
356 | skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len); | ||
357 | goto out; | ||
358 | } | ||
359 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { | ||
360 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); | ||
361 | goto out; | ||
362 | } | ||
363 | response->public_key_len = len; | ||
364 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
365 | skdebug(__func__, "malloc pubkey failed"); | ||
366 | goto out; | ||
367 | } | ||
368 | memcpy(response->public_key, ptr, len); | ||
369 | ret = 0; | ||
370 | out: | ||
371 | if (ret != 0) | ||
372 | free(response->public_key); | ||
373 | return ret; | ||
374 | } | ||
375 | |||
376 | static int | ||
377 | pack_public_key(uint32_t alg, const fido_cred_t *cred, | ||
378 | struct sk_enroll_response *response) | ||
379 | { | ||
380 | switch(alg) { | ||
381 | #ifdef WITH_OPENSSL | ||
382 | case SSH_SK_ECDSA: | ||
383 | return pack_public_key_ecdsa(cred, response); | ||
384 | #endif /* WITH_OPENSSL */ | ||
385 | case SSH_SK_ED25519: | ||
386 | return pack_public_key_ed25519(cred, response); | ||
387 | default: | ||
388 | return -1; | ||
389 | } | ||
390 | } | ||
391 | |||
392 | static int | ||
393 | fidoerr_to_skerr(int fidoerr) | ||
394 | { | ||
395 | switch (fidoerr) { | ||
396 | case FIDO_ERR_UNSUPPORTED_OPTION: | ||
397 | case FIDO_ERR_UNSUPPORTED_ALGORITHM: | ||
398 | return SSH_SK_ERR_UNSUPPORTED; | ||
399 | case FIDO_ERR_PIN_REQUIRED: | ||
400 | case FIDO_ERR_PIN_INVALID: | ||
401 | return SSH_SK_ERR_PIN_REQUIRED; | ||
402 | default: | ||
403 | return -1; | ||
404 | } | ||
405 | } | ||
406 | |||
407 | static int | ||
408 | check_enroll_options(struct sk_option **options, char **devicep, | ||
409 | uint8_t *user_id, size_t user_id_len) | ||
410 | { | ||
411 | size_t i; | ||
412 | |||
413 | if (options == NULL) | ||
414 | return 0; | ||
415 | for (i = 0; options[i] != NULL; i++) { | ||
416 | if (strcmp(options[i]->name, "device") == 0) { | ||
417 | if ((*devicep = strdup(options[i]->value)) == NULL) { | ||
418 | skdebug(__func__, "strdup device failed"); | ||
419 | return -1; | ||
420 | } | ||
421 | skdebug(__func__, "requested device %s", *devicep); | ||
422 | } else if (strcmp(options[i]->name, "user") == 0) { | ||
423 | if (strlcpy(user_id, options[i]->value, user_id_len) >= | ||
424 | user_id_len) { | ||
425 | skdebug(__func__, "user too long"); | ||
426 | return -1; | ||
427 | } | ||
428 | skdebug(__func__, "requested user %s", | ||
429 | (char *)user_id); | ||
430 | } else { | ||
431 | skdebug(__func__, "requested unsupported option %s", | ||
432 | options[i]->name); | ||
433 | if (options[i]->required) { | ||
434 | skdebug(__func__, "unknown required option"); | ||
435 | return -1; | ||
436 | } | ||
437 | } | ||
438 | } | ||
439 | return 0; | ||
440 | } | ||
441 | |||
442 | int | ||
443 | sk_enroll(uint32_t alg, const uint8_t *challenge, size_t challenge_len, | ||
444 | const char *application, uint8_t flags, const char *pin, | ||
445 | struct sk_option **options, struct sk_enroll_response **enroll_response) | ||
446 | { | ||
447 | fido_cred_t *cred = NULL; | ||
448 | fido_dev_t *dev = NULL; | ||
449 | const uint8_t *ptr; | ||
450 | uint8_t user_id[32]; | ||
451 | struct sk_enroll_response *response = NULL; | ||
452 | size_t len; | ||
453 | int cose_alg; | ||
454 | int ret = SSH_SK_ERR_GENERAL; | ||
455 | int r; | ||
456 | char *device = NULL; | ||
457 | |||
458 | #ifdef SK_DEBUG | ||
459 | fido_init(FIDO_DEBUG); | ||
460 | #endif | ||
461 | if (enroll_response == NULL) { | ||
462 | skdebug(__func__, "enroll_response == NULL"); | ||
463 | goto out; | ||
464 | } | ||
465 | memset(user_id, 0, sizeof(user_id)); | ||
466 | if (check_enroll_options(options, &device, | ||
467 | user_id, sizeof(user_id)) != 0) | ||
468 | goto out; /* error already logged */ | ||
469 | |||
470 | *enroll_response = NULL; | ||
471 | switch(alg) { | ||
472 | #ifdef WITH_OPENSSL | ||
473 | case SSH_SK_ECDSA: | ||
474 | cose_alg = COSE_ES256; | ||
475 | break; | ||
476 | #endif /* WITH_OPENSSL */ | ||
477 | case SSH_SK_ED25519: | ||
478 | cose_alg = COSE_EDDSA; | ||
479 | break; | ||
480 | default: | ||
481 | skdebug(__func__, "unsupported key type %d", alg); | ||
482 | goto out; | ||
483 | } | ||
484 | if (device == NULL && (device = pick_first_device()) == NULL) { | ||
485 | ret = SSH_SK_ERR_DEVICE_NOT_FOUND; | ||
486 | skdebug(__func__, "pick_first_device failed"); | ||
487 | goto out; | ||
488 | } | ||
489 | skdebug(__func__, "using device %s", device); | ||
490 | if ((cred = fido_cred_new()) == NULL) { | ||
491 | skdebug(__func__, "fido_cred_new failed"); | ||
492 | goto out; | ||
493 | } | ||
494 | if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { | ||
495 | skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); | ||
496 | goto out; | ||
497 | } | ||
498 | if ((r = fido_cred_set_clientdata_hash(cred, challenge, | ||
499 | challenge_len)) != FIDO_OK) { | ||
500 | skdebug(__func__, "fido_cred_set_clientdata_hash: %s", | ||
501 | fido_strerr(r)); | ||
502 | goto out; | ||
503 | } | ||
504 | if ((r = fido_cred_set_rk(cred, (flags & SSH_SK_RESIDENT_KEY) != 0 ? | ||
505 | FIDO_OPT_TRUE : FIDO_OPT_OMIT)) != FIDO_OK) { | ||
506 | skdebug(__func__, "fido_cred_set_rk: %s", fido_strerr(r)); | ||
507 | goto out; | ||
508 | } | ||
509 | if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id), | ||
510 | "openssh", "openssh", NULL)) != FIDO_OK) { | ||
511 | skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r)); | ||
512 | goto out; | ||
513 | } | ||
514 | if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) { | ||
515 | skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); | ||
516 | goto out; | ||
517 | } | ||
518 | if ((dev = fido_dev_new()) == NULL) { | ||
519 | skdebug(__func__, "fido_dev_new failed"); | ||
520 | goto out; | ||
521 | } | ||
522 | if ((r = fido_dev_open(dev, device)) != FIDO_OK) { | ||
523 | skdebug(__func__, "fido_dev_open: %s", fido_strerr(r)); | ||
524 | goto out; | ||
525 | } | ||
526 | if ((r = fido_dev_make_cred(dev, cred, pin)) != FIDO_OK) { | ||
527 | skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); | ||
528 | ret = fidoerr_to_skerr(r); | ||
529 | goto out; | ||
530 | } | ||
531 | if (fido_cred_x5c_ptr(cred) != NULL) { | ||
532 | if ((r = fido_cred_verify(cred)) != FIDO_OK) { | ||
533 | skdebug(__func__, "fido_cred_verify: %s", | ||
534 | fido_strerr(r)); | ||
535 | goto out; | ||
536 | } | ||
537 | } else { | ||
538 | skdebug(__func__, "self-attested credential"); | ||
539 | if ((r = fido_cred_verify_self(cred)) != FIDO_OK) { | ||
540 | skdebug(__func__, "fido_cred_verify_self: %s", | ||
541 | fido_strerr(r)); | ||
542 | goto out; | ||
543 | } | ||
544 | } | ||
545 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
546 | skdebug(__func__, "calloc response failed"); | ||
547 | goto out; | ||
548 | } | ||
549 | if (pack_public_key(alg, cred, response) != 0) { | ||
550 | skdebug(__func__, "pack_public_key failed"); | ||
551 | goto out; | ||
552 | } | ||
553 | if ((ptr = fido_cred_id_ptr(cred)) != NULL) { | ||
554 | len = fido_cred_id_len(cred); | ||
555 | if ((response->key_handle = calloc(1, len)) == NULL) { | ||
556 | skdebug(__func__, "calloc key handle failed"); | ||
557 | goto out; | ||
558 | } | ||
559 | memcpy(response->key_handle, ptr, len); | ||
560 | response->key_handle_len = len; | ||
561 | } | ||
562 | if ((ptr = fido_cred_sig_ptr(cred)) != NULL) { | ||
563 | len = fido_cred_sig_len(cred); | ||
564 | if ((response->signature = calloc(1, len)) == NULL) { | ||
565 | skdebug(__func__, "calloc signature failed"); | ||
566 | goto out; | ||
567 | } | ||
568 | memcpy(response->signature, ptr, len); | ||
569 | response->signature_len = len; | ||
570 | } | ||
571 | if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) { | ||
572 | len = fido_cred_x5c_len(cred); | ||
573 | debug3("%s: attestation cert len=%zu", __func__, len); | ||
574 | if ((response->attestation_cert = calloc(1, len)) == NULL) { | ||
575 | skdebug(__func__, "calloc attestation cert failed"); | ||
576 | goto out; | ||
577 | } | ||
578 | memcpy(response->attestation_cert, ptr, len); | ||
579 | response->attestation_cert_len = len; | ||
580 | } | ||
581 | *enroll_response = response; | ||
582 | response = NULL; | ||
583 | ret = 0; | ||
584 | out: | ||
585 | free(device); | ||
586 | if (response != NULL) { | ||
587 | free(response->public_key); | ||
588 | free(response->key_handle); | ||
589 | free(response->signature); | ||
590 | free(response->attestation_cert); | ||
591 | free(response); | ||
592 | } | ||
593 | if (dev != NULL) { | ||
594 | fido_dev_close(dev); | ||
595 | fido_dev_free(&dev); | ||
596 | } | ||
597 | if (cred != NULL) { | ||
598 | fido_cred_free(&cred); | ||
599 | } | ||
600 | return ret; | ||
601 | } | ||
602 | |||
603 | #ifdef WITH_OPENSSL | ||
604 | static int | ||
605 | pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response) | ||
606 | { | ||
607 | ECDSA_SIG *sig = NULL; | ||
608 | const BIGNUM *sig_r, *sig_s; | ||
609 | const unsigned char *cp; | ||
610 | size_t sig_len; | ||
611 | int ret = -1; | ||
612 | |||
613 | cp = fido_assert_sig_ptr(assert, 0); | ||
614 | sig_len = fido_assert_sig_len(assert, 0); | ||
615 | if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) { | ||
616 | skdebug(__func__, "d2i_ECDSA_SIG failed"); | ||
617 | goto out; | ||
618 | } | ||
619 | ECDSA_SIG_get0(sig, &sig_r, &sig_s); | ||
620 | response->sig_r_len = BN_num_bytes(sig_r); | ||
621 | response->sig_s_len = BN_num_bytes(sig_s); | ||
622 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL || | ||
623 | (response->sig_s = calloc(1, response->sig_s_len)) == NULL) { | ||
624 | skdebug(__func__, "calloc signature failed"); | ||
625 | goto out; | ||
626 | } | ||
627 | BN_bn2bin(sig_r, response->sig_r); | ||
628 | BN_bn2bin(sig_s, response->sig_s); | ||
629 | ret = 0; | ||
630 | out: | ||
631 | ECDSA_SIG_free(sig); | ||
632 | if (ret != 0) { | ||
633 | free(response->sig_r); | ||
634 | free(response->sig_s); | ||
635 | response->sig_r = NULL; | ||
636 | response->sig_s = NULL; | ||
637 | } | ||
638 | return ret; | ||
639 | } | ||
640 | #endif /* WITH_OPENSSL */ | ||
641 | |||
642 | static int | ||
643 | pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response) | ||
644 | { | ||
645 | const unsigned char *ptr; | ||
646 | size_t len; | ||
647 | int ret = -1; | ||
648 | |||
649 | ptr = fido_assert_sig_ptr(assert, 0); | ||
650 | len = fido_assert_sig_len(assert, 0); | ||
651 | if (len != 64) { | ||
652 | skdebug(__func__, "bad length %zu", len); | ||
653 | goto out; | ||
654 | } | ||
655 | response->sig_r_len = len; | ||
656 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) { | ||
657 | skdebug(__func__, "calloc signature failed"); | ||
658 | goto out; | ||
659 | } | ||
660 | memcpy(response->sig_r, ptr, len); | ||
661 | ret = 0; | ||
662 | out: | ||
663 | if (ret != 0) { | ||
664 | free(response->sig_r); | ||
665 | response->sig_r = NULL; | ||
666 | } | ||
667 | return ret; | ||
668 | } | ||
669 | |||
670 | static int | ||
671 | pack_sig(uint32_t alg, fido_assert_t *assert, | ||
672 | struct sk_sign_response *response) | ||
673 | { | ||
674 | switch(alg) { | ||
675 | #ifdef WITH_OPENSSL | ||
676 | case SSH_SK_ECDSA: | ||
677 | return pack_sig_ecdsa(assert, response); | ||
678 | #endif /* WITH_OPENSSL */ | ||
679 | case SSH_SK_ED25519: | ||
680 | return pack_sig_ed25519(assert, response); | ||
681 | default: | ||
682 | return -1; | ||
683 | } | ||
684 | } | ||
685 | |||
686 | /* Checks sk_options for sk_sign() and sk_load_resident_keys() */ | ||
687 | static int | ||
688 | check_sign_load_resident_options(struct sk_option **options, char **devicep) | ||
689 | { | ||
690 | size_t i; | ||
691 | |||
692 | if (options == NULL) | ||
693 | return 0; | ||
694 | for (i = 0; options[i] != NULL; i++) { | ||
695 | if (strcmp(options[i]->name, "device") == 0) { | ||
696 | if ((*devicep = strdup(options[i]->value)) == NULL) { | ||
697 | skdebug(__func__, "strdup device failed"); | ||
698 | return -1; | ||
699 | } | ||
700 | skdebug(__func__, "requested device %s", *devicep); | ||
701 | } else { | ||
702 | skdebug(__func__, "requested unsupported option %s", | ||
703 | options[i]->name); | ||
704 | if (options[i]->required) { | ||
705 | skdebug(__func__, "unknown required option"); | ||
706 | return -1; | ||
707 | } | ||
708 | } | ||
709 | } | ||
710 | return 0; | ||
711 | } | ||
712 | |||
713 | int | ||
714 | sk_sign(uint32_t alg, const uint8_t *message, size_t message_len, | ||
715 | const char *application, | ||
716 | const uint8_t *key_handle, size_t key_handle_len, | ||
717 | uint8_t flags, const char *pin, struct sk_option **options, | ||
718 | struct sk_sign_response **sign_response) | ||
719 | { | ||
720 | fido_assert_t *assert = NULL; | ||
721 | char *device = NULL; | ||
722 | fido_dev_t *dev = NULL; | ||
723 | struct sk_sign_response *response = NULL; | ||
724 | int ret = SSH_SK_ERR_GENERAL; | ||
725 | int r; | ||
726 | |||
727 | #ifdef SK_DEBUG | ||
728 | fido_init(FIDO_DEBUG); | ||
729 | #endif | ||
730 | |||
731 | if (sign_response == NULL) { | ||
732 | skdebug(__func__, "sign_response == NULL"); | ||
733 | goto out; | ||
734 | } | ||
735 | *sign_response = NULL; | ||
736 | if (check_sign_load_resident_options(options, &device) != 0) | ||
737 | goto out; /* error already logged */ | ||
738 | if ((dev = find_device(device, message, message_len, | ||
739 | application, key_handle, key_handle_len)) == NULL) { | ||
740 | skdebug(__func__, "couldn't find device for key handle"); | ||
741 | goto out; | ||
742 | } | ||
743 | if ((assert = fido_assert_new()) == NULL) { | ||
744 | skdebug(__func__, "fido_assert_new failed"); | ||
745 | goto out; | ||
746 | } | ||
747 | if ((r = fido_assert_set_clientdata_hash(assert, message, | ||
748 | message_len)) != FIDO_OK) { | ||
749 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", | ||
750 | fido_strerr(r)); | ||
751 | goto out; | ||
752 | } | ||
753 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { | ||
754 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); | ||
755 | goto out; | ||
756 | } | ||
757 | if ((r = fido_assert_allow_cred(assert, key_handle, | ||
758 | key_handle_len)) != FIDO_OK) { | ||
759 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); | ||
760 | goto out; | ||
761 | } | ||
762 | if ((r = fido_assert_set_up(assert, | ||
763 | (flags & SSH_SK_USER_PRESENCE_REQD) ? | ||
764 | FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) { | ||
765 | skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r)); | ||
766 | goto out; | ||
767 | } | ||
768 | if ((r = fido_dev_get_assert(dev, assert, NULL)) != FIDO_OK) { | ||
769 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); | ||
770 | goto out; | ||
771 | } | ||
772 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
773 | skdebug(__func__, "calloc response failed"); | ||
774 | goto out; | ||
775 | } | ||
776 | response->flags = fido_assert_flags(assert, 0); | ||
777 | response->counter = fido_assert_sigcount(assert, 0); | ||
778 | if (pack_sig(alg, assert, response) != 0) { | ||
779 | skdebug(__func__, "pack_sig failed"); | ||
780 | goto out; | ||
781 | } | ||
782 | *sign_response = response; | ||
783 | response = NULL; | ||
784 | ret = 0; | ||
785 | out: | ||
786 | free(device); | ||
787 | if (response != NULL) { | ||
788 | free(response->sig_r); | ||
789 | free(response->sig_s); | ||
790 | free(response); | ||
791 | } | ||
792 | if (dev != NULL) { | ||
793 | fido_dev_close(dev); | ||
794 | fido_dev_free(&dev); | ||
795 | } | ||
796 | if (assert != NULL) { | ||
797 | fido_assert_free(&assert); | ||
798 | } | ||
799 | return ret; | ||
800 | } | ||
801 | |||
802 | static int | ||
803 | read_rks(const char *devpath, const char *pin, | ||
804 | struct sk_resident_key ***rksp, size_t *nrksp) | ||
805 | { | ||
806 | int ret = SSH_SK_ERR_GENERAL, r = -1; | ||
807 | fido_dev_t *dev = NULL; | ||
808 | fido_credman_metadata_t *metadata = NULL; | ||
809 | fido_credman_rp_t *rp = NULL; | ||
810 | fido_credman_rk_t *rk = NULL; | ||
811 | size_t i, j, nrp, nrk; | ||
812 | const fido_cred_t *cred; | ||
813 | struct sk_resident_key *srk = NULL, **tmp; | ||
814 | |||
815 | if ((dev = fido_dev_new()) == NULL) { | ||
816 | skdebug(__func__, "fido_dev_new failed"); | ||
817 | return ret; | ||
818 | } | ||
819 | if ((r = fido_dev_open(dev, devpath)) != FIDO_OK) { | ||
820 | skdebug(__func__, "fido_dev_open %s failed: %s", | ||
821 | devpath, fido_strerr(r)); | ||
822 | fido_dev_free(&dev); | ||
823 | return ret; | ||
824 | } | ||
825 | if ((metadata = fido_credman_metadata_new()) == NULL) { | ||
826 | skdebug(__func__, "alloc failed"); | ||
827 | goto out; | ||
828 | } | ||
829 | |||
830 | if ((r = fido_credman_get_dev_metadata(dev, metadata, pin)) != 0) { | ||
831 | if (r == FIDO_ERR_INVALID_COMMAND) { | ||
832 | skdebug(__func__, "device %s does not support " | ||
833 | "resident keys", devpath); | ||
834 | ret = 0; | ||
835 | goto out; | ||
836 | } | ||
837 | skdebug(__func__, "get metadata for %s failed: %s", | ||
838 | devpath, fido_strerr(r)); | ||
839 | ret = fidoerr_to_skerr(r); | ||
840 | goto out; | ||
841 | } | ||
842 | skdebug(__func__, "existing %llu, remaining %llu", | ||
843 | (unsigned long long)fido_credman_rk_existing(metadata), | ||
844 | (unsigned long long)fido_credman_rk_remaining(metadata)); | ||
845 | if ((rp = fido_credman_rp_new()) == NULL) { | ||
846 | skdebug(__func__, "alloc rp failed"); | ||
847 | goto out; | ||
848 | } | ||
849 | if ((r = fido_credman_get_dev_rp(dev, rp, pin)) != 0) { | ||
850 | skdebug(__func__, "get RPs for %s failed: %s", | ||
851 | devpath, fido_strerr(r)); | ||
852 | goto out; | ||
853 | } | ||
854 | nrp = fido_credman_rp_count(rp); | ||
855 | skdebug(__func__, "Device %s has resident keys for %zu RPs", | ||
856 | devpath, nrp); | ||
857 | |||
858 | /* Iterate over RP IDs that have resident keys */ | ||
859 | for (i = 0; i < nrp; i++) { | ||
860 | skdebug(__func__, "rp %zu: name=\"%s\" id=\"%s\" hashlen=%zu", | ||
861 | i, fido_credman_rp_name(rp, i), fido_credman_rp_id(rp, i), | ||
862 | fido_credman_rp_id_hash_len(rp, i)); | ||
863 | |||
864 | /* Skip non-SSH RP IDs */ | ||
865 | if (strncasecmp(fido_credman_rp_id(rp, i), "ssh:", 4) != 0) | ||
866 | continue; | ||
867 | |||
868 | fido_credman_rk_free(&rk); | ||
869 | if ((rk = fido_credman_rk_new()) == NULL) { | ||
870 | skdebug(__func__, "alloc rk failed"); | ||
871 | goto out; | ||
872 | } | ||
873 | if ((r = fido_credman_get_dev_rk(dev, fido_credman_rp_id(rp, i), | ||
874 | rk, pin)) != 0) { | ||
875 | skdebug(__func__, "get RKs for %s slot %zu failed: %s", | ||
876 | devpath, i, fido_strerr(r)); | ||
877 | goto out; | ||
878 | } | ||
879 | nrk = fido_credman_rk_count(rk); | ||
880 | skdebug(__func__, "RP \"%s\" has %zu resident keys", | ||
881 | fido_credman_rp_id(rp, i), nrk); | ||
882 | |||
883 | /* Iterate over resident keys for this RP ID */ | ||
884 | for (j = 0; j < nrk; j++) { | ||
885 | if ((cred = fido_credman_rk(rk, j)) == NULL) { | ||
886 | skdebug(__func__, "no RK in slot %zu", j); | ||
887 | continue; | ||
888 | } | ||
889 | skdebug(__func__, "Device %s RP \"%s\" slot %zu: " | ||
890 | "type %d", devpath, fido_credman_rp_id(rp, i), j, | ||
891 | fido_cred_type(cred)); | ||
892 | |||
893 | /* build response entry */ | ||
894 | if ((srk = calloc(1, sizeof(*srk))) == NULL || | ||
895 | (srk->key.key_handle = calloc(1, | ||
896 | fido_cred_id_len(cred))) == NULL || | ||
897 | (srk->application = strdup(fido_credman_rp_id(rp, | ||
898 | i))) == NULL) { | ||
899 | skdebug(__func__, "alloc sk_resident_key"); | ||
900 | goto out; | ||
901 | } | ||
902 | |||
903 | srk->key.key_handle_len = fido_cred_id_len(cred); | ||
904 | memcpy(srk->key.key_handle, | ||
905 | fido_cred_id_ptr(cred), | ||
906 | srk->key.key_handle_len); | ||
907 | |||
908 | switch (fido_cred_type(cred)) { | ||
909 | case COSE_ES256: | ||
910 | srk->alg = SSH_SK_ECDSA; | ||
911 | break; | ||
912 | case COSE_EDDSA: | ||
913 | srk->alg = SSH_SK_ED25519; | ||
914 | break; | ||
915 | default: | ||
916 | skdebug(__func__, "unsupported key type %d", | ||
917 | fido_cred_type(cred)); | ||
918 | goto out; /* XXX free rk and continue */ | ||
919 | } | ||
920 | |||
921 | if ((r = pack_public_key(srk->alg, cred, | ||
922 | &srk->key)) != 0) { | ||
923 | skdebug(__func__, "pack public key failed"); | ||
924 | goto out; | ||
925 | } | ||
926 | /* append */ | ||
927 | if ((tmp = recallocarray(*rksp, *nrksp, (*nrksp) + 1, | ||
928 | sizeof(**rksp))) == NULL) { | ||
929 | skdebug(__func__, "alloc rksp"); | ||
930 | goto out; | ||
931 | } | ||
932 | *rksp = tmp; | ||
933 | (*rksp)[(*nrksp)++] = srk; | ||
934 | srk = NULL; | ||
935 | } | ||
936 | } | ||
937 | /* Success */ | ||
938 | ret = 0; | ||
939 | out: | ||
940 | if (srk != NULL) { | ||
941 | free(srk->application); | ||
942 | freezero(srk->key.public_key, srk->key.public_key_len); | ||
943 | freezero(srk->key.key_handle, srk->key.key_handle_len); | ||
944 | freezero(srk, sizeof(*srk)); | ||
945 | } | ||
946 | fido_credman_rp_free(&rp); | ||
947 | fido_credman_rk_free(&rk); | ||
948 | fido_dev_close(dev); | ||
949 | fido_dev_free(&dev); | ||
950 | fido_credman_metadata_free(&metadata); | ||
951 | return ret; | ||
952 | } | ||
953 | |||
954 | int | ||
955 | sk_load_resident_keys(const char *pin, struct sk_option **options, | ||
956 | struct sk_resident_key ***rksp, size_t *nrksp) | ||
957 | { | ||
958 | int ret = SSH_SK_ERR_GENERAL, r = -1; | ||
959 | fido_dev_info_t *devlist = NULL; | ||
960 | size_t i, ndev = 0, nrks = 0; | ||
961 | const fido_dev_info_t *di; | ||
962 | struct sk_resident_key **rks = NULL; | ||
963 | char *device = NULL; | ||
964 | *rksp = NULL; | ||
965 | *nrksp = 0; | ||
966 | |||
967 | if (check_sign_load_resident_options(options, &device) != 0) | ||
968 | goto out; /* error already logged */ | ||
969 | if (device != NULL) { | ||
970 | skdebug(__func__, "trying %s", device); | ||
971 | if ((r = read_rks(device, pin, &rks, &nrks)) != 0) { | ||
972 | skdebug(__func__, "read_rks failed for %s", device); | ||
973 | ret = r; | ||
974 | goto out; | ||
975 | } | ||
976 | } else { | ||
977 | /* Try all devices */ | ||
978 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | ||
979 | skdebug(__func__, "fido_dev_info_new failed"); | ||
980 | goto out; | ||
981 | } | ||
982 | if ((r = fido_dev_info_manifest(devlist, | ||
983 | MAX_FIDO_DEVICES, &ndev)) != FIDO_OK) { | ||
984 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
985 | fido_strerr(r)); | ||
986 | goto out; | ||
987 | } | ||
988 | for (i = 0; i < ndev; i++) { | ||
989 | if ((di = fido_dev_info_ptr(devlist, i)) == NULL) { | ||
990 | skdebug(__func__, "no dev info at %zu", i); | ||
991 | continue; | ||
992 | } | ||
993 | skdebug(__func__, "trying %s", fido_dev_info_path(di)); | ||
994 | if ((r = read_rks(fido_dev_info_path(di), pin, | ||
995 | &rks, &nrks)) != 0) { | ||
996 | skdebug(__func__, "read_rks failed for %s", | ||
997 | fido_dev_info_path(di)); | ||
998 | /* remember last error */ | ||
999 | ret = r; | ||
1000 | continue; | ||
1001 | } | ||
1002 | } | ||
1003 | } | ||
1004 | /* success, unless we have no keys but a specific error */ | ||
1005 | if (nrks > 0 || ret == SSH_SK_ERR_GENERAL) | ||
1006 | ret = 0; | ||
1007 | *rksp = rks; | ||
1008 | *nrksp = nrks; | ||
1009 | rks = NULL; | ||
1010 | nrks = 0; | ||
1011 | out: | ||
1012 | free(device); | ||
1013 | for (i = 0; i < nrks; i++) { | ||
1014 | free(rks[i]->application); | ||
1015 | freezero(rks[i]->key.public_key, rks[i]->key.public_key_len); | ||
1016 | freezero(rks[i]->key.key_handle, rks[i]->key.key_handle_len); | ||
1017 | freezero(rks[i], sizeof(*rks[i])); | ||
1018 | } | ||
1019 | free(rks); | ||
1020 | fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); | ||
1021 | return ret; | ||
1022 | } | ||
1023 | |||
1024 | #endif /* ENABLE_SK_INTERNAL */ | ||