/libfido2/src/largeblob.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2020-2026 Yubico AB. All rights reserved. |
3 | | * Use of this source code is governed by a BSD-style |
4 | | * license that can be found in the LICENSE file. |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include <openssl/sha.h> |
9 | | |
10 | | #include "fido.h" |
11 | | #include "fido/es256.h" |
12 | | |
13 | 6.04k | #define LARGEBLOB_DIGEST_LENGTH 16 |
14 | 2.87k | #define LARGEBLOB_NONCE_LENGTH 12 |
15 | 2.90k | #define LARGEBLOB_TAG_LENGTH 16 |
16 | | |
17 | | typedef struct largeblob { |
18 | | size_t origsiz; |
19 | | fido_blob_t ciphertext; |
20 | | fido_blob_t nonce; |
21 | | } largeblob_t; |
22 | | |
23 | | static largeblob_t * |
24 | | largeblob_new(void) |
25 | 5.60k | { |
26 | 5.60k | return calloc(1, sizeof(largeblob_t)); |
27 | 5.60k | } |
28 | | |
29 | | static void |
30 | | largeblob_reset(largeblob_t *blob) |
31 | 8.51k | { |
32 | 8.51k | fido_blob_reset(&blob->ciphertext); |
33 | 8.51k | fido_blob_reset(&blob->nonce); |
34 | 8.51k | blob->origsiz = 0; |
35 | 8.51k | } |
36 | | |
37 | | static void |
38 | | largeblob_free(largeblob_t **blob_ptr) |
39 | 5.60k | { |
40 | 5.60k | largeblob_t *blob; |
41 | | |
42 | 5.60k | if (blob_ptr == NULL || (blob = *blob_ptr) == NULL) |
43 | 33 | return; |
44 | 5.57k | largeblob_reset(blob); |
45 | 5.57k | free(blob); |
46 | 5.57k | *blob_ptr = NULL; |
47 | 5.57k | } |
48 | | |
49 | | static int |
50 | | largeblob_aad(fido_blob_t *aad, uint64_t size) |
51 | 8.37k | { |
52 | 8.37k | uint8_t buf[4 + sizeof(uint64_t)]; |
53 | | |
54 | 8.37k | buf[0] = 0x62; /* b */ |
55 | 8.37k | buf[1] = 0x6c; /* l */ |
56 | 8.37k | buf[2] = 0x6f; /* o */ |
57 | 8.37k | buf[3] = 0x62; /* b */ |
58 | 8.37k | size = htole64(size); |
59 | 8.37k | memcpy(&buf[4], &size, sizeof(uint64_t)); |
60 | | |
61 | 8.37k | return fido_blob_set(aad, buf, sizeof(buf)); |
62 | 8.37k | } |
63 | | |
64 | | static fido_blob_t * |
65 | | largeblob_decrypt(const largeblob_t *blob, const fido_blob_t *key) |
66 | 2.87k | { |
67 | 2.87k | fido_blob_t *plaintext = NULL, *aad = NULL; |
68 | 2.87k | int ok = -1; |
69 | | |
70 | 2.87k | if ((plaintext = fido_blob_new()) == NULL || |
71 | 2.87k | (aad = fido_blob_new()) == NULL) { |
72 | 46 | fido_log_debug("%s: fido_blob_new", __func__); |
73 | 46 | goto fail; |
74 | 46 | } |
75 | 2.82k | if (largeblob_aad(aad, blob->origsiz) < 0) { |
76 | 52 | fido_log_debug("%s: largeblob_aad", __func__); |
77 | 52 | goto fail; |
78 | 52 | } |
79 | 2.77k | if (aes256_gcm_dec(key, &blob->nonce, aad, &blob->ciphertext, |
80 | 2.77k | plaintext) < 0) { |
81 | 2.21k | fido_log_debug("%s: aes256_gcm_dec", __func__); |
82 | 2.21k | goto fail; |
83 | 2.21k | } |
84 | | |
85 | 560 | ok = 0; |
86 | 2.87k | fail: |
87 | 2.87k | fido_blob_free(&aad); |
88 | | |
89 | 2.87k | if (ok < 0) |
90 | 2.31k | fido_blob_free(&plaintext); |
91 | | |
92 | 2.87k | return plaintext; |
93 | 560 | } |
94 | | |
95 | | static int |
96 | | largeblob_get_nonce(largeblob_t *blob) |
97 | 5.54k | { |
98 | 5.54k | uint8_t buf[LARGEBLOB_NONCE_LENGTH]; |
99 | 5.54k | int ok = -1; |
100 | | |
101 | 5.54k | if (fido_get_random(buf, sizeof(buf)) < 0) { |
102 | 9 | fido_log_debug("%s: fido_get_random", __func__); |
103 | 9 | goto fail; |
104 | 9 | } |
105 | 5.53k | if (fido_blob_set(&blob->nonce, buf, sizeof(buf)) < 0) { |
106 | 0 | fido_log_debug("%s: fido_blob_set", __func__); |
107 | 0 | goto fail; |
108 | 0 | } |
109 | | |
110 | 5.53k | ok = 0; |
111 | 5.54k | fail: |
112 | 5.54k | explicit_bzero(buf, sizeof(buf)); |
113 | | |
114 | 5.54k | return ok; |
115 | 5.53k | } |
116 | | |
117 | | static int |
118 | | largeblob_seal(largeblob_t *blob, const fido_blob_t *body, |
119 | | const fido_blob_t *key) |
120 | 5.57k | { |
121 | 5.57k | fido_blob_t *plaintext = NULL, *aad = NULL; |
122 | 5.57k | int ok = -1; |
123 | | |
124 | 5.57k | if ((plaintext = fido_blob_new()) == NULL || |
125 | 5.57k | (aad = fido_blob_new()) == NULL) { |
126 | 14 | fido_log_debug("%s: fido_blob_new", __func__); |
127 | 14 | goto fail; |
128 | 14 | } |
129 | 5.55k | if (fido_compress(plaintext, body) != FIDO_OK) { |
130 | 14 | fido_log_debug("%s: fido_compress", __func__); |
131 | 14 | goto fail; |
132 | 14 | } |
133 | 5.54k | if (largeblob_aad(aad, body->len) < 0) { |
134 | 4 | fido_log_debug("%s: largeblob_aad", __func__); |
135 | 4 | goto fail; |
136 | 4 | } |
137 | 5.54k | if (largeblob_get_nonce(blob) < 0) { |
138 | 9 | fido_log_debug("%s: largeblob_get_nonce", __func__); |
139 | 9 | goto fail; |
140 | 9 | } |
141 | 5.53k | if (aes256_gcm_enc(key, &blob->nonce, aad, plaintext, |
142 | 5.53k | &blob->ciphertext) < 0) { |
143 | 88 | fido_log_debug("%s: aes256_gcm_enc", __func__); |
144 | 88 | goto fail; |
145 | 88 | } |
146 | 5.44k | blob->origsiz = body->len; |
147 | | |
148 | 5.44k | ok = 0; |
149 | 5.57k | fail: |
150 | 5.57k | fido_blob_free(&plaintext); |
151 | 5.57k | fido_blob_free(&aad); |
152 | | |
153 | 5.57k | return ok; |
154 | 5.44k | } |
155 | | |
156 | | static int |
157 | | largeblob_get_tx(fido_dev_t *dev, size_t offset, size_t count, int *ms) |
158 | 5.97k | { |
159 | 5.97k | fido_blob_t f; |
160 | 5.97k | cbor_item_t *argv[3]; |
161 | 5.97k | int r; |
162 | | |
163 | 5.97k | memset(argv, 0, sizeof(argv)); |
164 | 5.97k | memset(&f, 0, sizeof(f)); |
165 | | |
166 | 5.97k | if ((argv[0] = cbor_build_uint(count)) == NULL || |
167 | 5.97k | (argv[2] = cbor_build_uint(offset)) == NULL) { |
168 | 18 | fido_log_debug("%s: cbor encode", __func__); |
169 | 18 | r = FIDO_ERR_INTERNAL; |
170 | 18 | goto fail; |
171 | 18 | } |
172 | 5.95k | if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || |
173 | 5.95k | fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { |
174 | 46 | fido_log_debug("%s: fido_tx", __func__); |
175 | 46 | r = FIDO_ERR_TX; |
176 | 46 | goto fail; |
177 | 46 | } |
178 | | |
179 | 5.91k | r = FIDO_OK; |
180 | 5.97k | fail: |
181 | 5.97k | cbor_vector_free(argv, nitems(argv)); |
182 | 5.97k | free(f.ptr); |
183 | | |
184 | 5.97k | return r; |
185 | 5.91k | } |
186 | | |
187 | | static int |
188 | | parse_largeblob_reply(const cbor_item_t *key, const cbor_item_t *val, |
189 | | void *arg) |
190 | 4.50k | { |
191 | 4.50k | if (cbor_isa_uint(key) == false || |
192 | 4.50k | cbor_int_get_width(key) != CBOR_INT_8 || |
193 | 4.50k | cbor_get_uint8(key) != 1) { |
194 | 506 | fido_log_debug("%s: cbor type", __func__); |
195 | 506 | return 0; /* ignore */ |
196 | 506 | } |
197 | | |
198 | 3.99k | return fido_blob_decode(val, arg); |
199 | 4.50k | } |
200 | | |
201 | | static int |
202 | | largeblob_get_rx(fido_dev_t *dev, fido_blob_t **chunk, int *ms) |
203 | 5.91k | { |
204 | 5.91k | unsigned char *msg; |
205 | 5.91k | int msglen, r; |
206 | | |
207 | 5.91k | *chunk = NULL; |
208 | 5.91k | if ((msg = malloc(FIDO_MAXMSG)) == NULL) { |
209 | 11 | r = FIDO_ERR_INTERNAL; |
210 | 11 | goto out; |
211 | 11 | } |
212 | 5.90k | if ((msglen = fido_rx(dev, CTAP_CMD_CBOR, msg, FIDO_MAXMSG, ms)) < 0) { |
213 | 1.57k | fido_log_debug("%s: fido_rx", __func__); |
214 | 1.57k | r = FIDO_ERR_RX; |
215 | 1.57k | goto out; |
216 | 1.57k | } |
217 | 4.32k | if ((*chunk = fido_blob_new()) == NULL) { |
218 | 7 | fido_log_debug("%s: fido_blob_new", __func__); |
219 | 7 | r = FIDO_ERR_INTERNAL; |
220 | 7 | goto out; |
221 | 7 | } |
222 | 4.31k | if ((r = cbor_parse_reply(msg, (size_t)msglen, *chunk, |
223 | 4.31k | parse_largeblob_reply)) != FIDO_OK) { |
224 | 389 | fido_log_debug("%s: parse_largeblob_reply", __func__); |
225 | 389 | goto out; |
226 | 389 | } |
227 | | |
228 | 3.92k | r = FIDO_OK; |
229 | 5.91k | out: |
230 | 5.91k | if (r != FIDO_OK) |
231 | 1.98k | fido_blob_free(chunk); |
232 | | |
233 | 5.91k | freezero(msg, FIDO_MAXMSG); |
234 | | |
235 | 5.91k | return r; |
236 | 3.92k | } |
237 | | |
238 | | static cbor_item_t * |
239 | | largeblob_array_load(const uint8_t *ptr, size_t len) |
240 | 1.29k | { |
241 | 1.29k | struct cbor_load_result cbor; |
242 | 1.29k | cbor_item_t *item; |
243 | | |
244 | 1.29k | if (len < LARGEBLOB_DIGEST_LENGTH) { |
245 | 0 | fido_log_debug("%s: len", __func__); |
246 | 0 | return NULL; |
247 | 0 | } |
248 | 1.29k | len -= LARGEBLOB_DIGEST_LENGTH; |
249 | 1.29k | if ((item = cbor_load(ptr, len, &cbor)) == NULL) { |
250 | 5 | fido_log_debug("%s: cbor_load", __func__); |
251 | 5 | return NULL; |
252 | 5 | } |
253 | 1.29k | if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { |
254 | 0 | fido_log_debug("%s: cbor type", __func__); |
255 | 0 | cbor_decref(&item); |
256 | 0 | return NULL; |
257 | 0 | } |
258 | | |
259 | 1.29k | return item; |
260 | 1.29k | } |
261 | | |
262 | | static size_t |
263 | | get_chunklen(fido_dev_t *dev) |
264 | 26.9k | { |
265 | 26.9k | uint64_t maxchunklen; |
266 | | |
267 | 26.9k | if ((maxchunklen = fido_dev_maxmsgsize(dev)) > SIZE_MAX) |
268 | 0 | maxchunklen = SIZE_MAX; |
269 | 26.9k | if (maxchunklen > FIDO_MAXMSG) |
270 | 1.24k | maxchunklen = FIDO_MAXMSG; |
271 | 26.9k | maxchunklen = maxchunklen > 64 ? maxchunklen - 64 : 0; |
272 | | |
273 | 26.9k | return (size_t)maxchunklen; |
274 | 26.9k | } |
275 | | |
276 | | static int |
277 | | largeblob_do_decode(const cbor_item_t *key, const cbor_item_t *val, void *arg) |
278 | 8.70k | { |
279 | 8.70k | largeblob_t *blob = arg; |
280 | 8.70k | uint64_t origsiz; |
281 | | |
282 | 8.70k | if (cbor_isa_uint(key) == false || |
283 | 8.70k | cbor_int_get_width(key) != CBOR_INT_8) { |
284 | 0 | fido_log_debug("%s: cbor type", __func__); |
285 | 0 | return 0; /* ignore */ |
286 | 0 | } |
287 | | |
288 | 8.70k | switch (cbor_get_uint8(key)) { |
289 | 2.93k | case 1: /* ciphertext */ |
290 | 2.93k | if (fido_blob_decode(val, &blob->ciphertext) < 0 || |
291 | 2.93k | blob->ciphertext.len < LARGEBLOB_TAG_LENGTH) |
292 | 30 | return -1; |
293 | 2.90k | return 0; |
294 | 2.90k | case 2: /* nonce */ |
295 | 2.90k | if (fido_blob_decode(val, &blob->nonce) < 0 || |
296 | 2.90k | blob->nonce.len != LARGEBLOB_NONCE_LENGTH) |
297 | 28 | return -1; |
298 | 2.87k | return 0; |
299 | 2.87k | case 3: /* origSize */ |
300 | 2.87k | if (!cbor_isa_uint(val) || |
301 | 2.87k | (origsiz = cbor_get_int(val)) > SIZE_MAX) |
302 | 0 | return -1; |
303 | 2.87k | blob->origsiz = (size_t)origsiz; |
304 | 2.87k | return 0; |
305 | 0 | default: /* ignore */ |
306 | 0 | fido_log_debug("%s: cbor type", __func__); |
307 | 0 | return 0; |
308 | 8.70k | } |
309 | 8.70k | } |
310 | | |
311 | | static int |
312 | | largeblob_decode(largeblob_t *blob, const cbor_item_t *item) |
313 | 2.94k | { |
314 | 2.94k | if (!cbor_isa_map(item) || !cbor_map_is_definite(item)) { |
315 | 0 | fido_log_debug("%s: cbor type", __func__); |
316 | 0 | return -1; |
317 | 0 | } |
318 | 2.94k | if (cbor_map_iter(item, blob, largeblob_do_decode) < 0) { |
319 | 67 | fido_log_debug("%s: cbor_map_iter", __func__); |
320 | 67 | return -1; |
321 | 67 | } |
322 | 2.87k | if (fido_blob_is_empty(&blob->ciphertext) || |
323 | 2.87k | fido_blob_is_empty(&blob->nonce) || blob->origsiz == 0) { |
324 | 0 | fido_log_debug("%s: incomplete blob", __func__); |
325 | 0 | return -1; |
326 | 0 | } |
327 | | |
328 | 2.87k | return 0; |
329 | 2.87k | } |
330 | | |
331 | | static cbor_item_t * |
332 | | largeblob_encode(const fido_blob_t *body, const fido_blob_t *key) |
333 | 5.60k | { |
334 | 5.60k | largeblob_t *blob; |
335 | 5.60k | cbor_item_t *argv[3], *item = NULL; |
336 | | |
337 | 5.60k | memset(argv, 0, sizeof(argv)); |
338 | 5.60k | if ((blob = largeblob_new()) == NULL || |
339 | 5.60k | largeblob_seal(blob, body, key) < 0) { |
340 | 162 | fido_log_debug("%s: largeblob_seal", __func__); |
341 | 162 | goto fail; |
342 | 162 | } |
343 | 5.44k | if ((argv[0] = fido_blob_encode(&blob->ciphertext)) == NULL || |
344 | 5.44k | (argv[1] = fido_blob_encode(&blob->nonce)) == NULL || |
345 | 5.44k | (argv[2] = cbor_build_uint(blob->origsiz)) == NULL) { |
346 | 31 | fido_log_debug("%s: cbor encode", __func__); |
347 | 31 | goto fail; |
348 | 31 | } |
349 | 5.41k | item = cbor_flatten_vector(argv, nitems(argv)); |
350 | 5.60k | fail: |
351 | 5.60k | cbor_vector_free(argv, nitems(argv)); |
352 | 5.60k | largeblob_free(&blob); |
353 | | |
354 | 5.60k | return item; |
355 | 5.41k | } |
356 | | |
357 | | static int |
358 | | largeblob_array_lookup(fido_blob_t *out, size_t *idx, const cbor_item_t *item, |
359 | | const fido_blob_t *key) |
360 | 3.03k | { |
361 | 3.03k | cbor_item_t **v; |
362 | 3.03k | fido_blob_t *plaintext = NULL; |
363 | 3.03k | largeblob_t blob; |
364 | 3.03k | int r; |
365 | | |
366 | 3.03k | memset(&blob, 0, sizeof(blob)); |
367 | 3.03k | if (idx != NULL) |
368 | 2.87k | *idx = 0; |
369 | 3.03k | if ((v = cbor_array_handle(item)) == NULL) |
370 | 23 | return FIDO_ERR_INVALID_ARGUMENT; |
371 | 5.39k | for (size_t i = 0; i < cbor_array_size(item); i++) { |
372 | 2.94k | if (largeblob_decode(&blob, v[i]) < 0 || |
373 | 2.94k | (plaintext = largeblob_decrypt(&blob, key)) == NULL) { |
374 | 2.38k | fido_log_debug("%s: largeblob_decode", __func__); |
375 | 2.38k | largeblob_reset(&blob); |
376 | 2.38k | continue; |
377 | 2.38k | } |
378 | 560 | if (idx != NULL) |
379 | 528 | *idx = i; |
380 | 560 | break; |
381 | 2.94k | } |
382 | 3.01k | if (plaintext == NULL) { |
383 | 2.45k | fido_log_debug("%s: not found", __func__); |
384 | 2.45k | return FIDO_ERR_NOTFOUND; |
385 | 2.45k | } |
386 | 560 | if (out != NULL) |
387 | 32 | r = fido_uncompress(out, plaintext, blob.origsiz); |
388 | 528 | else |
389 | 528 | r = FIDO_OK; |
390 | | |
391 | 560 | fido_blob_free(&plaintext); |
392 | 560 | largeblob_reset(&blob); |
393 | | |
394 | 560 | return r; |
395 | 3.01k | } |
396 | | |
397 | | static int |
398 | | largeblob_array_digest(u_char out[LARGEBLOB_DIGEST_LENGTH], const u_char *data, |
399 | | size_t len) |
400 | 3.46k | { |
401 | 3.46k | u_char dgst[SHA256_DIGEST_LENGTH]; |
402 | | |
403 | 3.46k | if (data == NULL || len == 0) |
404 | 3 | return -1; |
405 | 3.46k | if (SHA256(data, len, dgst) != dgst) |
406 | 17 | return -1; |
407 | 3.44k | memcpy(out, dgst, LARGEBLOB_DIGEST_LENGTH); |
408 | | |
409 | 3.44k | return 0; |
410 | 3.46k | } |
411 | | |
412 | | static int |
413 | | largeblob_array_check(const fido_blob_t *array) |
414 | 3.51k | { |
415 | 3.51k | u_char expected_hash[LARGEBLOB_DIGEST_LENGTH]; |
416 | 3.51k | size_t body_len; |
417 | | |
418 | 3.51k | fido_log_xxd(array->ptr, array->len, __func__); |
419 | 3.51k | if (array->len < sizeof(expected_hash)) { |
420 | 47 | fido_log_debug("%s: len %zu", __func__, array->len); |
421 | 47 | return -1; |
422 | 47 | } |
423 | 3.46k | body_len = array->len - sizeof(expected_hash); |
424 | 3.46k | if (largeblob_array_digest(expected_hash, array->ptr, body_len) < 0) { |
425 | 20 | fido_log_debug("%s: largeblob_array_digest", __func__); |
426 | 20 | return -1; |
427 | 20 | } |
428 | | |
429 | 3.44k | return timingsafe_bcmp(expected_hash, array->ptr + body_len, |
430 | 3.44k | sizeof(expected_hash)); |
431 | 3.46k | } |
432 | | |
433 | | static int |
434 | | largeblob_get_array(fido_dev_t *dev, cbor_item_t **item, int *ms) |
435 | 18.8k | { |
436 | 18.8k | fido_blob_t *array, *chunk = NULL; |
437 | 18.8k | size_t n; |
438 | 18.8k | int r; |
439 | | |
440 | 18.8k | *item = NULL; |
441 | 18.8k | if ((n = get_chunklen(dev)) == 0) |
442 | 13.2k | return FIDO_ERR_INVALID_ARGUMENT; |
443 | 5.61k | if ((array = fido_blob_new()) == NULL) |
444 | 6 | return FIDO_ERR_INTERNAL; |
445 | 5.97k | do { |
446 | 5.97k | fido_blob_free(&chunk); |
447 | 5.97k | if ((r = largeblob_get_tx(dev, array->len, n, ms)) != FIDO_OK || |
448 | 5.97k | (r = largeblob_get_rx(dev, &chunk, ms)) != FIDO_OK) { |
449 | 2.04k | fido_log_debug("%s: largeblob_get_wait %zu/%zu", |
450 | 2.04k | __func__, array->len, n); |
451 | 2.04k | goto fail; |
452 | 2.04k | } |
453 | 3.92k | if (fido_blob_append(array, chunk->ptr, chunk->len) < 0) { |
454 | 44 | fido_log_debug("%s: fido_blob_append", __func__); |
455 | 44 | r = FIDO_ERR_INTERNAL; |
456 | 44 | goto fail; |
457 | 44 | } |
458 | 3.92k | } while (chunk->len == n); |
459 | | |
460 | 3.51k | if (largeblob_array_check(array) != 0) |
461 | 2.21k | *item = cbor_new_definite_array(0); /* per spec */ |
462 | 1.29k | else |
463 | 1.29k | *item = largeblob_array_load(array->ptr, array->len); |
464 | 3.51k | if (*item == NULL) |
465 | 31 | r = FIDO_ERR_INTERNAL; |
466 | 3.48k | else |
467 | 3.48k | r = FIDO_OK; |
468 | 5.60k | fail: |
469 | 5.60k | fido_blob_free(&array); |
470 | 5.60k | fido_blob_free(&chunk); |
471 | | |
472 | 5.60k | return r; |
473 | 3.51k | } |
474 | | |
475 | | static int |
476 | | prepare_hmac(size_t offset, const u_char *data, size_t len, fido_blob_t *hmac) |
477 | 317 | { |
478 | 317 | uint8_t buf[32 + 2 + sizeof(uint32_t) + SHA256_DIGEST_LENGTH]; |
479 | 317 | uint32_t u32_offset; |
480 | | |
481 | 317 | if (data == NULL || len == 0) { |
482 | 0 | fido_log_debug("%s: invalid data=%p, len=%zu", __func__, |
483 | 0 | (const void *)data, len); |
484 | 0 | return -1; |
485 | 0 | } |
486 | 317 | if (offset > UINT32_MAX) { |
487 | 0 | fido_log_debug("%s: invalid offset=%zu", __func__, offset); |
488 | 0 | return -1; |
489 | 0 | } |
490 | | |
491 | 317 | memset(buf, 0xff, 32); |
492 | 317 | buf[32] = CTAP_CBOR_LARGEBLOB; |
493 | 317 | buf[33] = 0x00; |
494 | 317 | u32_offset = htole32((uint32_t)offset); |
495 | 317 | memcpy(&buf[34], &u32_offset, sizeof(uint32_t)); |
496 | 317 | if (SHA256(data, len, &buf[38]) != &buf[38]) { |
497 | 1 | fido_log_debug("%s: SHA256", __func__); |
498 | 1 | return -1; |
499 | 1 | } |
500 | | |
501 | 316 | return fido_blob_set(hmac, buf, sizeof(buf)); |
502 | 317 | } |
503 | | |
504 | | static int |
505 | | largeblob_set_tx(fido_dev_t *dev, const fido_blob_t *token, const u_char *chunk, |
506 | | size_t chunk_len, size_t offset, size_t totalsiz, int *ms) |
507 | 2.37k | { |
508 | 2.37k | fido_blob_t *hmac = NULL, f; |
509 | 2.37k | cbor_item_t *argv[6]; |
510 | 2.37k | int r; |
511 | | |
512 | 2.37k | memset(argv, 0, sizeof(argv)); |
513 | 2.37k | memset(&f, 0, sizeof(f)); |
514 | | |
515 | 2.37k | if ((argv[1] = cbor_build_bytestring(chunk, chunk_len)) == NULL || |
516 | 2.37k | (argv[2] = cbor_build_uint(offset)) == NULL || |
517 | 2.37k | (offset == 0 && (argv[3] = cbor_build_uint(totalsiz)) == NULL)) { |
518 | 32 | fido_log_debug("%s: cbor encode", __func__); |
519 | 32 | r = FIDO_ERR_INTERNAL; |
520 | 32 | goto fail; |
521 | 32 | } |
522 | 2.33k | if (token != NULL) { |
523 | 327 | if ((hmac = fido_blob_new()) == NULL || |
524 | 327 | prepare_hmac(offset, chunk, chunk_len, hmac) < 0 || |
525 | 327 | (argv[4] = cbor_encode_pin_auth(dev, token, hmac)) == NULL || |
526 | 327 | (argv[5] = cbor_encode_pin_opt(dev)) == NULL) { |
527 | 37 | fido_log_debug("%s: cbor_encode_pin_auth", __func__); |
528 | 37 | r = FIDO_ERR_INTERNAL; |
529 | 37 | goto fail; |
530 | 37 | } |
531 | 327 | } |
532 | 2.30k | if (cbor_build_frame(CTAP_CBOR_LARGEBLOB, argv, nitems(argv), &f) < 0 || |
533 | 2.30k | fido_tx(dev, CTAP_CMD_CBOR, f.ptr, f.len, ms) < 0) { |
534 | 149 | fido_log_debug("%s: fido_tx", __func__); |
535 | 149 | r = FIDO_ERR_TX; |
536 | 149 | goto fail; |
537 | 149 | } |
538 | | |
539 | 2.15k | r = FIDO_OK; |
540 | 2.37k | fail: |
541 | 2.37k | cbor_vector_free(argv, nitems(argv)); |
542 | 2.37k | fido_blob_free(&hmac); |
543 | 2.37k | free(f.ptr); |
544 | | |
545 | 2.37k | return r; |
546 | 2.15k | } |
547 | | |
548 | | static int |
549 | | largeblob_get_uv_token(fido_dev_t *dev, const char *pin, fido_blob_t *token, |
550 | | int *ms) |
551 | 2.30k | { |
552 | 2.30k | es256_pk_t *pk = NULL; |
553 | 2.30k | fido_blob_t *ecdh = NULL; |
554 | 2.30k | int r; |
555 | | |
556 | 2.30k | if ((r = fido_do_ecdh(dev, &pk, &ecdh, ms)) != FIDO_OK) { |
557 | 1.00k | fido_log_debug("%s: fido_do_ecdh", __func__); |
558 | 1.00k | goto fail; |
559 | 1.00k | } |
560 | 1.30k | if ((r = fido_dev_get_uv_token(dev, CTAP_CBOR_LARGEBLOB, pin, ecdh, pk, |
561 | 1.30k | NULL, token, ms)) != FIDO_OK) { |
562 | 1.08k | fido_log_debug("%s: fido_dev_get_uv_token", __func__); |
563 | 1.08k | goto fail; |
564 | 1.08k | } |
565 | | |
566 | 219 | r = FIDO_OK; |
567 | 2.30k | fail: |
568 | 2.30k | if (r != FIDO_OK) |
569 | 2.08k | fido_blob_reset(token); |
570 | | |
571 | 2.30k | fido_blob_free(&ecdh); |
572 | 2.30k | es256_pk_free(&pk); |
573 | | |
574 | 2.30k | return r; |
575 | 219 | } |
576 | | |
577 | | static int |
578 | | largeblob_set_array(fido_dev_t *dev, const cbor_item_t *item, const char *pin, |
579 | | int *ms) |
580 | 8.12k | { |
581 | 8.12k | unsigned char dgst[SHA256_DIGEST_LENGTH]; |
582 | 8.12k | fido_blob_t cbor, token_store; |
583 | 8.12k | const fido_blob_t *token = NULL; |
584 | 8.12k | size_t chunklen, maxchunklen, totalsize; |
585 | 8.12k | int r; |
586 | | |
587 | 8.12k | memset(&cbor, 0, sizeof(cbor)); |
588 | 8.12k | memset(&token_store, 0, sizeof(token_store)); |
589 | | |
590 | 8.12k | if ((maxchunklen = get_chunklen(dev)) == 0) { |
591 | 4.14k | fido_log_debug("%s: maxchunklen=%zu", __func__, maxchunklen); |
592 | 4.14k | r = FIDO_ERR_INVALID_ARGUMENT; |
593 | 4.14k | goto fail; |
594 | 4.14k | } |
595 | 3.97k | if (!cbor_isa_array(item) || !cbor_array_is_definite(item)) { |
596 | 388 | fido_log_debug("%s: cbor type", __func__); |
597 | 388 | r = FIDO_ERR_INVALID_ARGUMENT; |
598 | 388 | goto fail; |
599 | 388 | } |
600 | 3.58k | if ((fido_blob_serialise(&cbor, item)) < 0) { |
601 | 3 | fido_log_debug("%s: fido_blob_serialise", __func__); |
602 | 3 | r = FIDO_ERR_INTERNAL; |
603 | 3 | goto fail; |
604 | 3 | } |
605 | 3.58k | if (cbor.len > SIZE_MAX - sizeof(dgst)) { |
606 | 0 | fido_log_debug("%s: cbor.len=%zu", __func__, cbor.len); |
607 | 0 | r = FIDO_ERR_INVALID_ARGUMENT; |
608 | 0 | goto fail; |
609 | 0 | } |
610 | 3.58k | if (SHA256(cbor.ptr, cbor.len, dgst) != dgst) { |
611 | 9 | fido_log_debug("%s: SHA256", __func__); |
612 | 9 | r = FIDO_ERR_INTERNAL; |
613 | 9 | goto fail; |
614 | 9 | } |
615 | 3.57k | totalsize = cbor.len + sizeof(dgst) - 16; /* the first 16 bytes only */ |
616 | | |
617 | 3.57k | if ((token = fido_dev_puat_blob(dev)) == NULL && |
618 | 3.57k | (pin != NULL || fido_dev_supports_permissions(dev))) { |
619 | 2.30k | if ((r = largeblob_get_uv_token(dev, pin, &token_store, |
620 | 2.30k | ms)) != FIDO_OK) { |
621 | 2.08k | fido_log_debug("%s: largeblob_get_uv_token", __func__); |
622 | 2.08k | goto fail; |
623 | 2.08k | } |
624 | 219 | token = &token_store; |
625 | 219 | } |
626 | | |
627 | 2.37k | for (size_t offset = 0; offset < cbor.len; offset += chunklen) { |
628 | 1.97k | if ((chunklen = cbor.len - offset) > maxchunklen) |
629 | 766 | chunklen = maxchunklen; |
630 | 1.97k | if ((r = largeblob_set_tx(dev, token, cbor.ptr + offset, |
631 | 1.97k | chunklen, offset, totalsize, ms)) != FIDO_OK || |
632 | 1.97k | (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { |
633 | 1.09k | fido_log_debug("%s: body", __func__); |
634 | 1.09k | goto fail; |
635 | 1.09k | } |
636 | 1.97k | } |
637 | 391 | if ((r = largeblob_set_tx(dev, token, dgst, sizeof(dgst) - 16, cbor.len, |
638 | 391 | totalsize, ms)) != FIDO_OK || |
639 | 391 | (r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { |
640 | 332 | fido_log_debug("%s: dgst", __func__); |
641 | 332 | goto fail; |
642 | 332 | } |
643 | | |
644 | 59 | r = FIDO_OK; |
645 | 8.12k | fail: |
646 | 8.12k | fido_blob_reset(&token_store); |
647 | 8.12k | fido_blob_reset(&cbor); |
648 | | |
649 | 8.12k | return r; |
650 | 59 | } |
651 | | |
652 | | static int |
653 | | largeblob_add(fido_dev_t *dev, const fido_blob_t *key, cbor_item_t *item, |
654 | | const char *pin, int *ms) |
655 | 5.33k | { |
656 | 5.33k | cbor_item_t *array = NULL; |
657 | 5.33k | size_t idx; |
658 | 5.33k | int r; |
659 | | |
660 | 5.33k | if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { |
661 | 3.35k | fido_log_debug("%s: largeblob_get_array", __func__); |
662 | 3.35k | goto fail; |
663 | 3.35k | } |
664 | | |
665 | 1.98k | switch (r = largeblob_array_lookup(NULL, &idx, array, key)) { |
666 | 207 | case FIDO_OK: |
667 | 207 | if (!cbor_array_replace(array, idx, item)) { |
668 | 0 | r = FIDO_ERR_INTERNAL; |
669 | 0 | goto fail; |
670 | 0 | } |
671 | 207 | break; |
672 | 1.76k | case FIDO_ERR_NOTFOUND: |
673 | 1.76k | if (cbor_array_append(&array, item) < 0) { |
674 | 47 | r = FIDO_ERR_INTERNAL; |
675 | 47 | goto fail; |
676 | 47 | } |
677 | 1.71k | break; |
678 | 1.71k | default: |
679 | 15 | fido_log_debug("%s: largeblob_array_lookup", __func__); |
680 | 15 | goto fail; |
681 | 1.98k | } |
682 | | |
683 | 1.92k | if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { |
684 | 1.88k | fido_log_debug("%s: largeblob_set_array", __func__); |
685 | 1.88k | goto fail; |
686 | 1.88k | } |
687 | | |
688 | 39 | r = FIDO_OK; |
689 | 5.33k | fail: |
690 | 5.33k | if (array != NULL) |
691 | 1.98k | cbor_decref(&array); |
692 | | |
693 | 5.33k | return r; |
694 | 39 | } |
695 | | |
696 | | static int |
697 | | largeblob_drop(fido_dev_t *dev, const fido_blob_t *key, const char *pin, |
698 | | int *ms) |
699 | 5.39k | { |
700 | 5.39k | cbor_item_t *array = NULL; |
701 | 5.39k | size_t idx; |
702 | 5.39k | int r; |
703 | | |
704 | 5.39k | if ((r = largeblob_get_array(dev, &array, ms)) != FIDO_OK) { |
705 | 4.50k | fido_log_debug("%s: largeblob_get_array", __func__); |
706 | 4.50k | goto fail; |
707 | 4.50k | } |
708 | 889 | if ((r = largeblob_array_lookup(NULL, &idx, array, key)) != FIDO_OK) { |
709 | 568 | fido_log_debug("%s: largeblob_array_lookup", __func__); |
710 | 568 | goto fail; |
711 | 568 | } |
712 | 321 | if (cbor_array_drop(&array, idx) < 0) { |
713 | 11 | fido_log_debug("%s: cbor_array_drop", __func__); |
714 | 11 | r = FIDO_ERR_INTERNAL; |
715 | 11 | goto fail; |
716 | 11 | } |
717 | 310 | if ((r = largeblob_set_array(dev, array, pin, ms)) != FIDO_OK) { |
718 | 305 | fido_log_debug("%s: largeblob_set_array", __func__); |
719 | 305 | goto fail; |
720 | 305 | } |
721 | | |
722 | 5 | r = FIDO_OK; |
723 | 5.39k | fail: |
724 | 5.39k | if (array != NULL) |
725 | 889 | cbor_decref(&array); |
726 | | |
727 | 5.39k | return r; |
728 | 5 | } |
729 | | |
730 | | int |
731 | | fido_dev_largeblob_get(fido_dev_t *dev, const unsigned char *key_ptr, |
732 | | size_t key_len, unsigned char **blob_ptr, size_t *blob_len) |
733 | 5.77k | { |
734 | 5.77k | cbor_item_t *item = NULL; |
735 | 5.77k | fido_blob_t key, body; |
736 | 5.77k | int ms = dev->timeout_ms; |
737 | 5.77k | int r; |
738 | | |
739 | 5.77k | memset(&key, 0, sizeof(key)); |
740 | 5.77k | memset(&body, 0, sizeof(body)); |
741 | | |
742 | 5.77k | if (key_len != 32) { |
743 | 3.47k | fido_log_debug("%s: invalid key len %zu", __func__, key_len); |
744 | 3.47k | return FIDO_ERR_INVALID_ARGUMENT; |
745 | 3.47k | } |
746 | 2.29k | if (blob_ptr == NULL || blob_len == NULL) { |
747 | 0 | fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%p", __func__, |
748 | 0 | (const void *)blob_ptr, (const void *)blob_len); |
749 | 0 | return FIDO_ERR_INVALID_ARGUMENT; |
750 | 0 | } |
751 | 2.29k | *blob_ptr = NULL; |
752 | 2.29k | *blob_len = 0; |
753 | 2.29k | if (fido_blob_set(&key, key_ptr, key_len) < 0) { |
754 | 4 | fido_log_debug("%s: fido_blob_set", __func__); |
755 | 4 | return FIDO_ERR_INTERNAL; |
756 | 4 | } |
757 | 2.29k | if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { |
758 | 2.13k | fido_log_debug("%s: largeblob_get_array", __func__); |
759 | 2.13k | goto fail; |
760 | 2.13k | } |
761 | 164 | if ((r = largeblob_array_lookup(&body, NULL, item, &key)) != FIDO_OK) |
762 | 133 | fido_log_debug("%s: largeblob_array_lookup", __func__); |
763 | 31 | else { |
764 | 31 | *blob_ptr = body.ptr; |
765 | 31 | *blob_len = body.len; |
766 | 31 | } |
767 | 2.29k | fail: |
768 | 2.29k | if (item != NULL) |
769 | 164 | cbor_decref(&item); |
770 | | |
771 | 2.29k | fido_blob_reset(&key); |
772 | | |
773 | 2.29k | return r; |
774 | 164 | } |
775 | | |
776 | | int |
777 | | fido_dev_largeblob_set(fido_dev_t *dev, const unsigned char *key_ptr, |
778 | | size_t key_len, const unsigned char *blob_ptr, size_t blob_len, |
779 | | const char *pin) |
780 | 16.0k | { |
781 | 16.0k | cbor_item_t *item = NULL; |
782 | 16.0k | fido_blob_t key, body; |
783 | 16.0k | int ms = dev->timeout_ms; |
784 | 16.0k | int r; |
785 | | |
786 | 16.0k | memset(&key, 0, sizeof(key)); |
787 | 16.0k | memset(&body, 0, sizeof(body)); |
788 | | |
789 | 16.0k | if (key_len != 32) { |
790 | 10.4k | fido_log_debug("%s: invalid key len %zu", __func__, key_len); |
791 | 10.4k | return FIDO_ERR_INVALID_ARGUMENT; |
792 | 10.4k | } |
793 | 5.62k | if (blob_ptr == NULL || blob_len == 0) { |
794 | 0 | fido_log_debug("%s: invalid blob_ptr=%p, blob_len=%zu", __func__, |
795 | 0 | (const void *)blob_ptr, blob_len); |
796 | 0 | return FIDO_ERR_INVALID_ARGUMENT; |
797 | 0 | } |
798 | 5.62k | if (fido_blob_set(&key, key_ptr, key_len) < 0 || |
799 | 5.62k | fido_blob_set(&body, blob_ptr, blob_len) < 0) { |
800 | 20 | fido_log_debug("%s: fido_blob_set", __func__); |
801 | 20 | r = FIDO_ERR_INTERNAL; |
802 | 20 | goto fail; |
803 | 20 | } |
804 | 5.60k | if ((item = largeblob_encode(&body, &key)) == NULL) { |
805 | 266 | fido_log_debug("%s: largeblob_encode", __func__); |
806 | 266 | r = FIDO_ERR_INTERNAL; |
807 | 266 | goto fail; |
808 | 266 | } |
809 | 5.33k | if ((r = largeblob_add(dev, &key, item, pin, &ms)) != FIDO_OK) |
810 | 5.30k | fido_log_debug("%s: largeblob_add", __func__); |
811 | 5.62k | fail: |
812 | 5.62k | if (item != NULL) |
813 | 5.33k | cbor_decref(&item); |
814 | | |
815 | 5.62k | fido_blob_reset(&key); |
816 | 5.62k | fido_blob_reset(&body); |
817 | | |
818 | 5.62k | return r; |
819 | 5.33k | } |
820 | | |
821 | | int |
822 | | fido_dev_largeblob_remove(fido_dev_t *dev, const unsigned char *key_ptr, |
823 | | size_t key_len, const char *pin) |
824 | 15.6k | { |
825 | 15.6k | fido_blob_t key; |
826 | 15.6k | int ms = dev->timeout_ms; |
827 | 15.6k | int r; |
828 | | |
829 | 15.6k | memset(&key, 0, sizeof(key)); |
830 | | |
831 | 15.6k | if (key_len != 32) { |
832 | 10.2k | fido_log_debug("%s: invalid key len %zu", __func__, key_len); |
833 | 10.2k | return FIDO_ERR_INVALID_ARGUMENT; |
834 | 10.2k | } |
835 | 5.39k | if (fido_blob_set(&key, key_ptr, key_len) < 0) { |
836 | 2 | fido_log_debug("%s: fido_blob_set", __func__); |
837 | 2 | return FIDO_ERR_INTERNAL; |
838 | 2 | } |
839 | 5.39k | if ((r = largeblob_drop(dev, &key, pin, &ms)) != FIDO_OK) |
840 | 5.38k | fido_log_debug("%s: largeblob_drop", __func__); |
841 | | |
842 | 5.39k | fido_blob_reset(&key); |
843 | | |
844 | 5.39k | return r; |
845 | 5.39k | } |
846 | | |
847 | | int |
848 | | fido_dev_largeblob_get_array(fido_dev_t *dev, unsigned char **cbor_ptr, |
849 | | size_t *cbor_len) |
850 | 5.82k | { |
851 | 5.82k | cbor_item_t *item = NULL; |
852 | 5.82k | fido_blob_t cbor; |
853 | 5.82k | int ms = dev->timeout_ms; |
854 | 5.82k | int r; |
855 | | |
856 | 5.82k | memset(&cbor, 0, sizeof(cbor)); |
857 | | |
858 | 5.82k | if (cbor_ptr == NULL || cbor_len == NULL) { |
859 | 0 | fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%p", __func__, |
860 | 0 | (const void *)cbor_ptr, (const void *)cbor_len); |
861 | 0 | return FIDO_ERR_INVALID_ARGUMENT; |
862 | 0 | } |
863 | 5.82k | *cbor_ptr = NULL; |
864 | 5.82k | *cbor_len = 0; |
865 | 5.82k | if ((r = largeblob_get_array(dev, &item, &ms)) != FIDO_OK) { |
866 | 5.37k | fido_log_debug("%s: largeblob_get_array", __func__); |
867 | 5.37k | return r; |
868 | 5.37k | } |
869 | 447 | if (fido_blob_serialise(&cbor, item) < 0) { |
870 | 93 | fido_log_debug("%s: fido_blob_serialise", __func__); |
871 | 93 | r = FIDO_ERR_INTERNAL; |
872 | 354 | } else { |
873 | 354 | *cbor_ptr = cbor.ptr; |
874 | 354 | *cbor_len = cbor.len; |
875 | 354 | } |
876 | | |
877 | 447 | cbor_decref(&item); |
878 | | |
879 | 447 | return r; |
880 | 5.82k | } |
881 | | |
882 | | int |
883 | | fido_dev_largeblob_set_array(fido_dev_t *dev, const unsigned char *cbor_ptr, |
884 | | size_t cbor_len, const char *pin) |
885 | 15.7k | { |
886 | 15.7k | cbor_item_t *item = NULL; |
887 | 15.7k | struct cbor_load_result cbor_result; |
888 | 15.7k | int ms = dev->timeout_ms; |
889 | 15.7k | int r; |
890 | | |
891 | 15.7k | if (cbor_ptr == NULL || cbor_len == 0) { |
892 | 0 | fido_log_debug("%s: invalid cbor_ptr=%p, cbor_len=%zu", __func__, |
893 | 0 | (const void *)cbor_ptr, cbor_len); |
894 | 0 | return FIDO_ERR_INVALID_ARGUMENT; |
895 | 0 | } |
896 | 15.7k | if ((item = cbor_load(cbor_ptr, cbor_len, &cbor_result)) == NULL) { |
897 | 9.91k | fido_log_debug("%s: cbor_load", __func__); |
898 | 9.91k | return FIDO_ERR_INVALID_ARGUMENT; |
899 | 9.91k | } |
900 | 5.88k | if ((r = largeblob_set_array(dev, item, pin, &ms)) != FIDO_OK) |
901 | 5.87k | fido_log_debug("%s: largeblob_set_array", __func__); |
902 | | |
903 | 5.88k | cbor_decref(&item); |
904 | | |
905 | 5.88k | return r; |
906 | 15.7k | } |