diff options
author | djm@openbsd.org <djm@openbsd.org> | 2019-11-14 21:27:29 +0000 |
---|---|---|
committer | Damien Miller <djm@mindrot.org> | 2019-11-15 09:57:30 +1100 |
commit | 6bff9521ab9a9f7396d635755c342b72373bb4f9 (patch) | |
tree | c434974d93c1b918b1a0ae045f421d247f75f391 /sk-usbhid.c | |
parent | 4f5e331cb8e11face3025aa6578662dde489c3ad (diff) |
upstream: directly support U2F/FIDO2 security keys in OpenSSH by
linking against the (previously external) USB HID middleware. The dlopen()
capability still exists for alternate middlewares, e.g. for Bluetooth, NFC
and test/debugging.
OpenBSD-Commit-ID: 14446cf170ac0351f0d4792ba0bca53024930069
Diffstat (limited to 'sk-usbhid.c')
-rw-r--r-- | sk-usbhid.c | 697 |
1 files changed, 697 insertions, 0 deletions
diff --git a/sk-usbhid.c b/sk-usbhid.c new file mode 100644 index 000000000..c0a6bd0da --- /dev/null +++ b/sk-usbhid.c | |||
@@ -0,0 +1,697 @@ | |||
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 | #include <openssl/opensslv.h> | ||
29 | #include <openssl/crypto.h> | ||
30 | #include <openssl/bn.h> | ||
31 | #include <openssl/ec.h> | ||
32 | #include <openssl/ecdsa.h> | ||
33 | |||
34 | #include <fido.h> | ||
35 | |||
36 | #ifndef SK_STANDALONE | ||
37 | #include "log.h" | ||
38 | #include "xmalloc.h" | ||
39 | #endif | ||
40 | |||
41 | /* #define SK_DEBUG 1 */ | ||
42 | |||
43 | #define MAX_FIDO_DEVICES 256 | ||
44 | |||
45 | /* Compatibility with OpenSSH 1.0.x */ | ||
46 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) | ||
47 | #define ECDSA_SIG_get0(sig, pr, ps) \ | ||
48 | do { \ | ||
49 | (*pr) = sig->r; \ | ||
50 | (*ps) = sig->s; \ | ||
51 | } while (0) | ||
52 | #endif | ||
53 | |||
54 | #define SK_VERSION_MAJOR 0x00020000 /* current API version */ | ||
55 | |||
56 | /* Flags */ | ||
57 | #define SK_USER_PRESENCE_REQD 0x01 | ||
58 | |||
59 | /* Algs */ | ||
60 | #define SK_ECDSA 0x00 | ||
61 | #define SK_ED25519 0x01 | ||
62 | |||
63 | struct sk_enroll_response { | ||
64 | uint8_t *public_key; | ||
65 | size_t public_key_len; | ||
66 | uint8_t *key_handle; | ||
67 | size_t key_handle_len; | ||
68 | uint8_t *signature; | ||
69 | size_t signature_len; | ||
70 | uint8_t *attestation_cert; | ||
71 | size_t attestation_cert_len; | ||
72 | }; | ||
73 | |||
74 | struct sk_sign_response { | ||
75 | uint8_t flags; | ||
76 | uint32_t counter; | ||
77 | uint8_t *sig_r; | ||
78 | size_t sig_r_len; | ||
79 | uint8_t *sig_s; | ||
80 | size_t sig_s_len; | ||
81 | }; | ||
82 | |||
83 | /* If building as part of OpenSSH, then rename exported functions */ | ||
84 | #if !defined(SK_STANDALONE) | ||
85 | #define sk_api_version ssh_sk_api_version | ||
86 | #define sk_enroll ssh_sk_enroll | ||
87 | #define sk_sign ssh_sk_sign | ||
88 | #endif | ||
89 | |||
90 | /* Return the version of the middleware API */ | ||
91 | uint32_t sk_api_version(void); | ||
92 | |||
93 | /* Enroll a U2F key (private key generation) */ | ||
94 | int sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | ||
95 | const char *application, uint8_t flags, | ||
96 | struct sk_enroll_response **enroll_response); | ||
97 | |||
98 | /* Sign a challenge */ | ||
99 | int sk_sign(int alg, const uint8_t *message, size_t message_len, | ||
100 | const char *application, const uint8_t *key_handle, size_t key_handle_len, | ||
101 | uint8_t flags, struct sk_sign_response **sign_response); | ||
102 | |||
103 | static void skdebug(const char *func, const char *fmt, ...) | ||
104 | __attribute__((__format__ (printf, 2, 3))); | ||
105 | |||
106 | static void | ||
107 | skdebug(const char *func, const char *fmt, ...) | ||
108 | { | ||
109 | #if !defined(SK_STANDALONE) | ||
110 | char *msg; | ||
111 | va_list ap; | ||
112 | |||
113 | va_start(ap, fmt); | ||
114 | xvasprintf(&msg, fmt, ap); | ||
115 | va_end(ap); | ||
116 | debug("%s: %s", __func__, msg); | ||
117 | free(msg); | ||
118 | #elif defined(SK_DEBUG) | ||
119 | va_list ap; | ||
120 | |||
121 | va_start(ap, fmt); | ||
122 | fprintf(stderr, "%s: ", func); | ||
123 | vfprintf(stderr, fmt, ap); | ||
124 | fputc('\n', stderr); | ||
125 | va_end(ap); | ||
126 | #else | ||
127 | (void)func; /* XXX */ | ||
128 | (void)fmt; /* XXX */ | ||
129 | #endif | ||
130 | } | ||
131 | |||
132 | uint32_t | ||
133 | sk_api_version(void) | ||
134 | { | ||
135 | return SK_VERSION_MAJOR; | ||
136 | } | ||
137 | |||
138 | /* Select the first identified FIDO device attached to the system */ | ||
139 | static char * | ||
140 | pick_first_device(void) | ||
141 | { | ||
142 | char *ret = NULL; | ||
143 | fido_dev_info_t *devlist = NULL; | ||
144 | size_t olen = 0; | ||
145 | int r; | ||
146 | const fido_dev_info_t *di; | ||
147 | |||
148 | if ((devlist = fido_dev_info_new(1)) == NULL) { | ||
149 | skdebug(__func__, "fido_dev_info_new failed"); | ||
150 | goto out; | ||
151 | } | ||
152 | if ((r = fido_dev_info_manifest(devlist, 1, &olen)) != FIDO_OK) { | ||
153 | skdebug(__func__, "fido_dev_info_manifest failed: %s", | ||
154 | fido_strerr(r)); | ||
155 | goto out; | ||
156 | } | ||
157 | if (olen != 1) { | ||
158 | skdebug(__func__, "fido_dev_info_manifest bad len %zu", olen); | ||
159 | goto out; | ||
160 | } | ||
161 | di = fido_dev_info_ptr(devlist, 0); | ||
162 | if ((ret = strdup(fido_dev_info_path(di))) == NULL) { | ||
163 | skdebug(__func__, "fido_dev_info_path failed"); | ||
164 | goto out; | ||
165 | } | ||
166 | out: | ||
167 | fido_dev_info_free(&devlist, 1); | ||
168 | return ret; | ||
169 | } | ||
170 | |||
171 | /* Check if the specified key handle exists on a given device. */ | ||
172 | static int | ||
173 | try_device(fido_dev_t *dev, const uint8_t *message, size_t message_len, | ||
174 | const char *application, const uint8_t *key_handle, size_t key_handle_len) | ||
175 | { | ||
176 | fido_assert_t *assert = NULL; | ||
177 | int r = FIDO_ERR_INTERNAL; | ||
178 | |||
179 | if ((assert = fido_assert_new()) == NULL) { | ||
180 | skdebug(__func__, "fido_assert_new failed"); | ||
181 | goto out; | ||
182 | } | ||
183 | if ((r = fido_assert_set_clientdata_hash(assert, message, | ||
184 | message_len)) != FIDO_OK) { | ||
185 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", | ||
186 | fido_strerr(r)); | ||
187 | goto out; | ||
188 | } | ||
189 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { | ||
190 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); | ||
191 | goto out; | ||
192 | } | ||
193 | if ((r = fido_assert_allow_cred(assert, key_handle, | ||
194 | key_handle_len)) != FIDO_OK) { | ||
195 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); | ||
196 | goto out; | ||
197 | } | ||
198 | if ((r = fido_assert_set_up(assert, FIDO_OPT_FALSE)) != FIDO_OK) { | ||
199 | skdebug(__func__, "fido_assert_up: %s", fido_strerr(r)); | ||
200 | goto out; | ||
201 | } | ||
202 | r = fido_dev_get_assert(dev, assert, NULL); | ||
203 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); | ||
204 | out: | ||
205 | fido_assert_free(&assert); | ||
206 | |||
207 | return r != FIDO_OK ? -1 : 0; | ||
208 | } | ||
209 | |||
210 | /* Iterate over configured devices looking for a specific key handle */ | ||
211 | static fido_dev_t * | ||
212 | find_device(const uint8_t *message, size_t message_len, const char *application, | ||
213 | const uint8_t *key_handle, size_t key_handle_len) | ||
214 | { | ||
215 | fido_dev_info_t *devlist = NULL; | ||
216 | fido_dev_t *dev = NULL; | ||
217 | size_t devlist_len = 0; | ||
218 | const char *path; | ||
219 | int r; | ||
220 | |||
221 | if ((devlist = fido_dev_info_new(MAX_FIDO_DEVICES)) == NULL) { | ||
222 | skdebug(__func__, "fido_dev_info_new failed"); | ||
223 | goto out; | ||
224 | } | ||
225 | if ((r = fido_dev_info_manifest(devlist, MAX_FIDO_DEVICES, | ||
226 | &devlist_len)) != FIDO_OK) { | ||
227 | skdebug(__func__, "fido_dev_info_manifest: %s", fido_strerr(r)); | ||
228 | goto out; | ||
229 | } | ||
230 | |||
231 | skdebug(__func__, "found %zu device(s)", devlist_len); | ||
232 | |||
233 | for (size_t i = 0; i < devlist_len; i++) { | ||
234 | const fido_dev_info_t *di = fido_dev_info_ptr(devlist, i); | ||
235 | |||
236 | if (di == NULL) { | ||
237 | skdebug(__func__, "fido_dev_info_ptr %zu failed", i); | ||
238 | continue; | ||
239 | } | ||
240 | if ((path = fido_dev_info_path(di)) == NULL) { | ||
241 | skdebug(__func__, "fido_dev_info_path %zu failed", i); | ||
242 | continue; | ||
243 | } | ||
244 | skdebug(__func__, "trying device %zu: %s", i, path); | ||
245 | if ((dev = fido_dev_new()) == NULL) { | ||
246 | skdebug(__func__, "fido_dev_new failed"); | ||
247 | continue; | ||
248 | } | ||
249 | if ((r = fido_dev_open(dev, path)) != FIDO_OK) { | ||
250 | skdebug(__func__, "fido_dev_open failed"); | ||
251 | fido_dev_free(&dev); | ||
252 | continue; | ||
253 | } | ||
254 | if (try_device(dev, message, message_len, application, | ||
255 | key_handle, key_handle_len) == 0) { | ||
256 | skdebug(__func__, "found key"); | ||
257 | break; | ||
258 | } | ||
259 | fido_dev_close(dev); | ||
260 | fido_dev_free(&dev); | ||
261 | } | ||
262 | |||
263 | out: | ||
264 | if (devlist != NULL) | ||
265 | fido_dev_info_free(&devlist, MAX_FIDO_DEVICES); | ||
266 | |||
267 | return dev; | ||
268 | } | ||
269 | |||
270 | /* | ||
271 | * The key returned via fido_cred_pubkey_ptr() is in affine coordinates, | ||
272 | * but the API expects a SEC1 octet string. | ||
273 | */ | ||
274 | static int | ||
275 | pack_public_key_ecdsa(fido_cred_t *cred, 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 | BN_CTX *bn_ctx = NULL; | ||
282 | int ret = -1; | ||
283 | |||
284 | response->public_key = NULL; | ||
285 | response->public_key_len = 0; | ||
286 | |||
287 | if ((bn_ctx = BN_CTX_new()) == NULL || | ||
288 | (x = BN_CTX_get(bn_ctx)) == NULL || | ||
289 | (y = BN_CTX_get(bn_ctx)) == NULL || | ||
290 | (g = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)) == NULL || | ||
291 | (q = EC_POINT_new(g)) == NULL) { | ||
292 | skdebug(__func__, "libcrypto setup failed"); | ||
293 | goto out; | ||
294 | } | ||
295 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { | ||
296 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); | ||
297 | goto out; | ||
298 | } | ||
299 | if (fido_cred_pubkey_len(cred) != 64) { | ||
300 | skdebug(__func__, "bad fido_cred_pubkey_len %zu", | ||
301 | fido_cred_pubkey_len(cred)); | ||
302 | goto out; | ||
303 | } | ||
304 | |||
305 | if (BN_bin2bn(ptr, 32, x) == NULL || | ||
306 | BN_bin2bn(ptr + 32, 32, y) == NULL) { | ||
307 | skdebug(__func__, "BN_bin2bn failed"); | ||
308 | goto out; | ||
309 | } | ||
310 | if (EC_POINT_set_affine_coordinates_GFp(g, q, x, y, bn_ctx) != 1) { | ||
311 | skdebug(__func__, "EC_POINT_set_affine_coordinates_GFp failed"); | ||
312 | goto out; | ||
313 | } | ||
314 | response->public_key_len = EC_POINT_point2oct(g, q, | ||
315 | POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bn_ctx); | ||
316 | if (response->public_key_len == 0 || response->public_key_len > 2048) { | ||
317 | skdebug(__func__, "bad pubkey length %zu", | ||
318 | response->public_key_len); | ||
319 | goto out; | ||
320 | } | ||
321 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
322 | skdebug(__func__, "malloc pubkey failed"); | ||
323 | goto out; | ||
324 | } | ||
325 | if (EC_POINT_point2oct(g, q, POINT_CONVERSION_UNCOMPRESSED, | ||
326 | response->public_key, response->public_key_len, bn_ctx) == 0) { | ||
327 | skdebug(__func__, "EC_POINT_point2oct failed"); | ||
328 | goto out; | ||
329 | } | ||
330 | /* success */ | ||
331 | ret = 0; | ||
332 | out: | ||
333 | if (ret != 0 && response->public_key != NULL) { | ||
334 | memset(response->public_key, 0, response->public_key_len); | ||
335 | free(response->public_key); | ||
336 | response->public_key = NULL; | ||
337 | } | ||
338 | EC_POINT_free(q); | ||
339 | EC_GROUP_free(g); | ||
340 | BN_CTX_free(bn_ctx); | ||
341 | return ret; | ||
342 | } | ||
343 | |||
344 | static int | ||
345 | pack_public_key_ed25519(fido_cred_t *cred, struct sk_enroll_response *response) | ||
346 | { | ||
347 | const uint8_t *ptr; | ||
348 | size_t len; | ||
349 | int ret = -1; | ||
350 | |||
351 | response->public_key = NULL; | ||
352 | response->public_key_len = 0; | ||
353 | |||
354 | if ((len = fido_cred_pubkey_len(cred)) != 32) { | ||
355 | skdebug(__func__, "bad fido_cred_pubkey_len len %zu", len); | ||
356 | goto out; | ||
357 | } | ||
358 | if ((ptr = fido_cred_pubkey_ptr(cred)) == NULL) { | ||
359 | skdebug(__func__, "fido_cred_pubkey_ptr failed"); | ||
360 | goto out; | ||
361 | } | ||
362 | response->public_key_len = len; | ||
363 | if ((response->public_key = malloc(response->public_key_len)) == NULL) { | ||
364 | skdebug(__func__, "malloc pubkey failed"); | ||
365 | goto out; | ||
366 | } | ||
367 | memcpy(response->public_key, ptr, len); | ||
368 | ret = 0; | ||
369 | out: | ||
370 | if (ret != 0) | ||
371 | free(response->public_key); | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | static int | ||
376 | pack_public_key(int alg, fido_cred_t *cred, struct sk_enroll_response *response) | ||
377 | { | ||
378 | switch(alg) { | ||
379 | case SK_ECDSA: | ||
380 | return pack_public_key_ecdsa(cred, response); | ||
381 | case SK_ED25519: | ||
382 | return pack_public_key_ed25519(cred, response); | ||
383 | default: | ||
384 | return -1; | ||
385 | } | ||
386 | } | ||
387 | |||
388 | int | ||
389 | sk_enroll(int alg, const uint8_t *challenge, size_t challenge_len, | ||
390 | const char *application, uint8_t flags, | ||
391 | struct sk_enroll_response **enroll_reponse) | ||
392 | { | ||
393 | fido_cred_t *cred = NULL; | ||
394 | fido_dev_t *dev = NULL; | ||
395 | const uint8_t *ptr; | ||
396 | uint8_t user_id[32]; | ||
397 | struct sk_enroll_response *response = NULL; | ||
398 | size_t len; | ||
399 | int cose_alg; | ||
400 | int ret = -1; | ||
401 | int r; | ||
402 | char *device = NULL; | ||
403 | |||
404 | (void)flags; /* XXX; unused */ | ||
405 | #ifdef SK_DEBUG | ||
406 | fido_init(FIDO_DEBUG); | ||
407 | #endif | ||
408 | if (enroll_reponse == NULL) { | ||
409 | skdebug(__func__, "enroll_reponse == NULL"); | ||
410 | goto out; | ||
411 | } | ||
412 | *enroll_reponse = NULL; | ||
413 | switch(alg) { | ||
414 | case SK_ECDSA: | ||
415 | cose_alg = COSE_ES256; | ||
416 | break; | ||
417 | case SK_ED25519: | ||
418 | cose_alg = COSE_EDDSA; | ||
419 | break; | ||
420 | default: | ||
421 | skdebug(__func__, "unsupported key type %d", alg); | ||
422 | goto out; | ||
423 | } | ||
424 | if ((device = pick_first_device()) == NULL) { | ||
425 | skdebug(__func__, "pick_first_device failed"); | ||
426 | goto out; | ||
427 | } | ||
428 | skdebug(__func__, "using device %s", device); | ||
429 | if ((cred = fido_cred_new()) == NULL) { | ||
430 | skdebug(__func__, "fido_cred_new failed"); | ||
431 | goto out; | ||
432 | } | ||
433 | memset(user_id, 0, sizeof(user_id)); | ||
434 | if ((r = fido_cred_set_type(cred, cose_alg)) != FIDO_OK) { | ||
435 | skdebug(__func__, "fido_cred_set_type: %s", fido_strerr(r)); | ||
436 | goto out; | ||
437 | } | ||
438 | if ((r = fido_cred_set_clientdata_hash(cred, challenge, | ||
439 | challenge_len)) != FIDO_OK) { | ||
440 | skdebug(__func__, "fido_cred_set_clientdata_hash: %s", | ||
441 | fido_strerr(r)); | ||
442 | goto out; | ||
443 | } | ||
444 | if ((r = fido_cred_set_user(cred, user_id, sizeof(user_id), | ||
445 | "openssh", "openssh", NULL)) != FIDO_OK) { | ||
446 | skdebug(__func__, "fido_cred_set_user: %s", fido_strerr(r)); | ||
447 | goto out; | ||
448 | } | ||
449 | if ((r = fido_cred_set_rp(cred, application, NULL)) != FIDO_OK) { | ||
450 | skdebug(__func__, "fido_cred_set_rp: %s", fido_strerr(r)); | ||
451 | goto out; | ||
452 | } | ||
453 | if ((dev = fido_dev_new()) == NULL) { | ||
454 | skdebug(__func__, "fido_dev_new failed"); | ||
455 | goto out; | ||
456 | } | ||
457 | if ((r = fido_dev_open(dev, device)) != FIDO_OK) { | ||
458 | skdebug(__func__, "fido_dev_open: %s", fido_strerr(r)); | ||
459 | goto out; | ||
460 | } | ||
461 | if ((r = fido_dev_make_cred(dev, cred, NULL)) != FIDO_OK) { | ||
462 | skdebug(__func__, "fido_dev_make_cred: %s", fido_strerr(r)); | ||
463 | goto out; | ||
464 | } | ||
465 | if (fido_cred_x5c_ptr(cred) != NULL) { | ||
466 | if ((r = fido_cred_verify(cred)) != FIDO_OK) { | ||
467 | skdebug(__func__, "fido_cred_verify: %s", | ||
468 | fido_strerr(r)); | ||
469 | goto out; | ||
470 | } | ||
471 | } else { | ||
472 | skdebug(__func__, "self-attested credential"); | ||
473 | if ((r = fido_cred_verify_self(cred)) != FIDO_OK) { | ||
474 | skdebug(__func__, "fido_cred_verify_self: %s", | ||
475 | fido_strerr(r)); | ||
476 | goto out; | ||
477 | } | ||
478 | } | ||
479 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
480 | skdebug(__func__, "calloc response failed"); | ||
481 | goto out; | ||
482 | } | ||
483 | if (pack_public_key(alg, cred, response) != 0) { | ||
484 | skdebug(__func__, "pack_public_key failed"); | ||
485 | goto out; | ||
486 | } | ||
487 | if ((ptr = fido_cred_id_ptr(cred)) != NULL) { | ||
488 | len = fido_cred_id_len(cred); | ||
489 | if ((response->key_handle = calloc(1, len)) == NULL) { | ||
490 | skdebug(__func__, "calloc key handle failed"); | ||
491 | goto out; | ||
492 | } | ||
493 | memcpy(response->key_handle, ptr, len); | ||
494 | response->key_handle_len = len; | ||
495 | } | ||
496 | if ((ptr = fido_cred_sig_ptr(cred)) != NULL) { | ||
497 | len = fido_cred_sig_len(cred); | ||
498 | if ((response->signature = calloc(1, len)) == NULL) { | ||
499 | skdebug(__func__, "calloc signature failed"); | ||
500 | goto out; | ||
501 | } | ||
502 | memcpy(response->signature, ptr, len); | ||
503 | response->signature_len = len; | ||
504 | } | ||
505 | if ((ptr = fido_cred_x5c_ptr(cred)) != NULL) { | ||
506 | len = fido_cred_x5c_len(cred); | ||
507 | if ((response->attestation_cert = calloc(1, len)) == NULL) { | ||
508 | skdebug(__func__, "calloc attestation cert failed"); | ||
509 | goto out; | ||
510 | } | ||
511 | memcpy(response->attestation_cert, ptr, len); | ||
512 | response->attestation_cert_len = len; | ||
513 | } | ||
514 | *enroll_reponse = response; | ||
515 | response = NULL; | ||
516 | ret = 0; | ||
517 | out: | ||
518 | free(device); | ||
519 | if (response != NULL) { | ||
520 | free(response->public_key); | ||
521 | free(response->key_handle); | ||
522 | free(response->signature); | ||
523 | free(response->attestation_cert); | ||
524 | free(response); | ||
525 | } | ||
526 | if (dev != NULL) { | ||
527 | fido_dev_close(dev); | ||
528 | fido_dev_free(&dev); | ||
529 | } | ||
530 | if (cred != NULL) { | ||
531 | fido_cred_free(&cred); | ||
532 | } | ||
533 | return ret; | ||
534 | } | ||
535 | |||
536 | static int | ||
537 | pack_sig_ecdsa(fido_assert_t *assert, struct sk_sign_response *response) | ||
538 | { | ||
539 | ECDSA_SIG *sig = NULL; | ||
540 | const BIGNUM *sig_r, *sig_s; | ||
541 | const unsigned char *cp; | ||
542 | size_t sig_len; | ||
543 | int ret = -1; | ||
544 | |||
545 | cp = fido_assert_sig_ptr(assert, 0); | ||
546 | sig_len = fido_assert_sig_len(assert, 0); | ||
547 | if ((sig = d2i_ECDSA_SIG(NULL, &cp, sig_len)) == NULL) { | ||
548 | skdebug(__func__, "d2i_ECDSA_SIG failed"); | ||
549 | goto out; | ||
550 | } | ||
551 | ECDSA_SIG_get0(sig, &sig_r, &sig_s); | ||
552 | response->sig_r_len = BN_num_bytes(sig_r); | ||
553 | response->sig_s_len = BN_num_bytes(sig_s); | ||
554 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL || | ||
555 | (response->sig_s = calloc(1, response->sig_s_len)) == NULL) { | ||
556 | skdebug(__func__, "calloc signature failed"); | ||
557 | goto out; | ||
558 | } | ||
559 | BN_bn2bin(sig_r, response->sig_r); | ||
560 | BN_bn2bin(sig_s, response->sig_s); | ||
561 | ret = 0; | ||
562 | out: | ||
563 | ECDSA_SIG_free(sig); | ||
564 | if (ret != 0) { | ||
565 | free(response->sig_r); | ||
566 | free(response->sig_s); | ||
567 | response->sig_r = NULL; | ||
568 | response->sig_s = NULL; | ||
569 | } | ||
570 | return ret; | ||
571 | } | ||
572 | |||
573 | static int | ||
574 | pack_sig_ed25519(fido_assert_t *assert, struct sk_sign_response *response) | ||
575 | { | ||
576 | const unsigned char *ptr; | ||
577 | size_t len; | ||
578 | int ret = -1; | ||
579 | |||
580 | ptr = fido_assert_sig_ptr(assert, 0); | ||
581 | len = fido_assert_sig_len(assert, 0); | ||
582 | if (len != 64) { | ||
583 | skdebug(__func__, "bad length %zu", len); | ||
584 | goto out; | ||
585 | } | ||
586 | response->sig_r_len = len; | ||
587 | if ((response->sig_r = calloc(1, response->sig_r_len)) == NULL) { | ||
588 | skdebug(__func__, "calloc signature failed"); | ||
589 | goto out; | ||
590 | } | ||
591 | memcpy(response->sig_r, ptr, len); | ||
592 | ret = 0; | ||
593 | out: | ||
594 | if (ret != 0) { | ||
595 | free(response->sig_r); | ||
596 | response->sig_r = NULL; | ||
597 | } | ||
598 | return ret; | ||
599 | } | ||
600 | |||
601 | static int | ||
602 | pack_sig(int alg, fido_assert_t *assert, struct sk_sign_response *response) | ||
603 | { | ||
604 | switch(alg) { | ||
605 | case SK_ECDSA: | ||
606 | return pack_sig_ecdsa(assert, response); | ||
607 | case SK_ED25519: | ||
608 | return pack_sig_ed25519(assert, response); | ||
609 | default: | ||
610 | return -1; | ||
611 | } | ||
612 | } | ||
613 | |||
614 | int | ||
615 | sk_sign(int alg, const uint8_t *message, size_t message_len, | ||
616 | const char *application, | ||
617 | const uint8_t *key_handle, size_t key_handle_len, | ||
618 | uint8_t flags, struct sk_sign_response **sign_response) | ||
619 | { | ||
620 | fido_assert_t *assert = NULL; | ||
621 | fido_dev_t *dev = NULL; | ||
622 | struct sk_sign_response *response = NULL; | ||
623 | int ret = -1; | ||
624 | int r; | ||
625 | |||
626 | #ifdef SK_DEBUG | ||
627 | fido_init(FIDO_DEBUG); | ||
628 | #endif | ||
629 | |||
630 | if (sign_response == NULL) { | ||
631 | skdebug(__func__, "sign_response == NULL"); | ||
632 | goto out; | ||
633 | } | ||
634 | *sign_response = NULL; | ||
635 | if ((dev = find_device(message, message_len, application, key_handle, | ||
636 | key_handle_len)) == NULL) { | ||
637 | skdebug(__func__, "couldn't find device for key handle"); | ||
638 | goto out; | ||
639 | } | ||
640 | if ((assert = fido_assert_new()) == NULL) { | ||
641 | skdebug(__func__, "fido_assert_new failed"); | ||
642 | goto out; | ||
643 | } | ||
644 | if ((r = fido_assert_set_clientdata_hash(assert, message, | ||
645 | message_len)) != FIDO_OK) { | ||
646 | skdebug(__func__, "fido_assert_set_clientdata_hash: %s", | ||
647 | fido_strerr(r)); | ||
648 | goto out; | ||
649 | } | ||
650 | if ((r = fido_assert_set_rp(assert, application)) != FIDO_OK) { | ||
651 | skdebug(__func__, "fido_assert_set_rp: %s", fido_strerr(r)); | ||
652 | goto out; | ||
653 | } | ||
654 | if ((r = fido_assert_allow_cred(assert, key_handle, | ||
655 | key_handle_len)) != FIDO_OK) { | ||
656 | skdebug(__func__, "fido_assert_allow_cred: %s", fido_strerr(r)); | ||
657 | goto out; | ||
658 | } | ||
659 | if ((r = fido_assert_set_up(assert, | ||
660 | (flags & SK_USER_PRESENCE_REQD) ? | ||
661 | FIDO_OPT_TRUE : FIDO_OPT_FALSE)) != FIDO_OK) { | ||
662 | skdebug(__func__, "fido_assert_set_up: %s", fido_strerr(r)); | ||
663 | goto out; | ||
664 | } | ||
665 | if ((r = fido_dev_get_assert(dev, assert, NULL)) != FIDO_OK) { | ||
666 | skdebug(__func__, "fido_dev_get_assert: %s", fido_strerr(r)); | ||
667 | goto out; | ||
668 | } | ||
669 | if ((response = calloc(1, sizeof(*response))) == NULL) { | ||
670 | skdebug(__func__, "calloc response failed"); | ||
671 | goto out; | ||
672 | } | ||
673 | response->flags = fido_assert_flags(assert, 0); | ||
674 | response->counter = fido_assert_sigcount(assert, 0); | ||
675 | if (pack_sig(alg, assert, response) != 0) { | ||
676 | skdebug(__func__, "pack_sig failed"); | ||
677 | goto out; | ||
678 | } | ||
679 | *sign_response = response; | ||
680 | response = NULL; | ||
681 | ret = 0; | ||
682 | out: | ||
683 | if (response != NULL) { | ||
684 | free(response->sig_r); | ||
685 | free(response->sig_s); | ||
686 | free(response); | ||
687 | } | ||
688 | if (dev != NULL) { | ||
689 | fido_dev_close(dev); | ||
690 | fido_dev_free(&dev); | ||
691 | } | ||
692 | if (assert != NULL) { | ||
693 | fido_assert_free(&assert); | ||
694 | } | ||
695 | return ret; | ||
696 | } | ||
697 | #endif /* ENABLE_SK_INTERNAL */ | ||