diff options
Diffstat (limited to 'src/pin.c')
-rw-r--r-- | src/pin.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/src/pin.c b/src/pin.c new file mode 100644 index 0000000..1ed555c --- /dev/null +++ b/src/pin.c | |||
@@ -0,0 +1,428 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2018 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 | */ | ||
6 | |||
7 | #include <string.h> | ||
8 | #include "fido.h" | ||
9 | #include "fido/es256.h" | ||
10 | |||
11 | static int | ||
12 | parse_pintoken(const cbor_item_t *key, const cbor_item_t *val, void *arg) | ||
13 | { | ||
14 | fido_blob_t *token = arg; | ||
15 | |||
16 | if (cbor_isa_uint(key) == false || | ||
17 | cbor_int_get_width(key) != CBOR_INT_8 || | ||
18 | cbor_get_uint8(key) != 2) { | ||
19 | fido_log_debug("%s: cbor type", __func__); | ||
20 | return (0); /* ignore */ | ||
21 | } | ||
22 | |||
23 | return (fido_blob_decode(val, token)); | ||
24 | } | ||
25 | |||
26 | static int | ||
27 | fido_dev_get_pin_token_tx(fido_dev_t *dev, const char *pin, | ||
28 | const fido_blob_t *ecdh, const es256_pk_t *pk) | ||
29 | { | ||
30 | fido_blob_t f; | ||
31 | fido_blob_t *p = NULL; | ||
32 | cbor_item_t *argv[6]; | ||
33 | int r; | ||
34 | |||
35 | memset(&f, 0, sizeof(f)); | ||
36 | memset(argv, 0, sizeof(argv)); | ||
37 | |||
38 | if ((p = fido_blob_new()) == NULL || fido_blob_set(p, | ||
39 | (const unsigned char *)pin, strlen(pin)) < 0) { | ||
40 | fido_log_debug("%s: fido_blob_set", __func__); | ||
41 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
42 | goto fail; | ||
43 | } | ||
44 | |||
45 | if ((argv[0] = cbor_build_uint8(1)) == NULL || | ||
46 | (argv[1] = cbor_build_uint8(5)) == NULL || | ||
47 | (argv[2] = es256_pk_encode(pk, 0)) == NULL || | ||
48 | (argv[5] = cbor_encode_pin_hash_enc(ecdh, p)) == NULL) { | ||
49 | fido_log_debug("%s: cbor encode", __func__); | ||
50 | r = FIDO_ERR_INTERNAL; | ||
51 | goto fail; | ||
52 | } | ||
53 | |||
54 | if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 6, &f) < 0 || | ||
55 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
56 | fido_log_debug("%s: fido_tx", __func__); | ||
57 | r = FIDO_ERR_TX; | ||
58 | goto fail; | ||
59 | } | ||
60 | |||
61 | r = FIDO_OK; | ||
62 | fail: | ||
63 | cbor_vector_free(argv, nitems(argv)); | ||
64 | fido_blob_free(&p); | ||
65 | free(f.ptr); | ||
66 | |||
67 | return (r); | ||
68 | } | ||
69 | |||
70 | static int | ||
71 | fido_dev_get_pin_token_rx(fido_dev_t *dev, const fido_blob_t *ecdh, | ||
72 | fido_blob_t *token, int ms) | ||
73 | { | ||
74 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
75 | fido_blob_t *aes_token = NULL; | ||
76 | unsigned char reply[2048]; | ||
77 | int reply_len; | ||
78 | int r; | ||
79 | |||
80 | if ((aes_token = fido_blob_new()) == NULL) { | ||
81 | r = FIDO_ERR_INTERNAL; | ||
82 | goto fail; | ||
83 | } | ||
84 | |||
85 | if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) { | ||
86 | fido_log_debug("%s: fido_rx", __func__); | ||
87 | r = FIDO_ERR_RX; | ||
88 | goto fail; | ||
89 | } | ||
90 | |||
91 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, aes_token, | ||
92 | parse_pintoken)) != FIDO_OK) { | ||
93 | fido_log_debug("%s: parse_pintoken", __func__); | ||
94 | goto fail; | ||
95 | } | ||
96 | |||
97 | if (aes256_cbc_dec(ecdh, aes_token, token) < 0) { | ||
98 | fido_log_debug("%s: aes256_cbc_dec", __func__); | ||
99 | r = FIDO_ERR_RX; | ||
100 | goto fail; | ||
101 | } | ||
102 | |||
103 | r = FIDO_OK; | ||
104 | fail: | ||
105 | fido_blob_free(&aes_token); | ||
106 | |||
107 | return (r); | ||
108 | } | ||
109 | |||
110 | static int | ||
111 | fido_dev_get_pin_token_wait(fido_dev_t *dev, const char *pin, | ||
112 | const fido_blob_t *ecdh, const es256_pk_t *pk, fido_blob_t *token, int ms) | ||
113 | { | ||
114 | int r; | ||
115 | |||
116 | if ((r = fido_dev_get_pin_token_tx(dev, pin, ecdh, pk)) != FIDO_OK || | ||
117 | (r = fido_dev_get_pin_token_rx(dev, ecdh, token, ms)) != FIDO_OK) | ||
118 | return (r); | ||
119 | |||
120 | return (FIDO_OK); | ||
121 | } | ||
122 | |||
123 | int | ||
124 | fido_dev_get_pin_token(fido_dev_t *dev, const char *pin, | ||
125 | const fido_blob_t *ecdh, const es256_pk_t *pk, fido_blob_t *token) | ||
126 | { | ||
127 | return (fido_dev_get_pin_token_wait(dev, pin, ecdh, pk, token, -1)); | ||
128 | } | ||
129 | |||
130 | static int | ||
131 | pad64(const char *pin, fido_blob_t **ppin) | ||
132 | { | ||
133 | size_t pin_len; | ||
134 | size_t ppin_len; | ||
135 | |||
136 | pin_len = strlen(pin); | ||
137 | if (pin_len < 4 || pin_len > 255) { | ||
138 | fido_log_debug("%s: invalid pin length", __func__); | ||
139 | return (FIDO_ERR_PIN_POLICY_VIOLATION); | ||
140 | } | ||
141 | |||
142 | if ((*ppin = fido_blob_new()) == NULL) | ||
143 | return (FIDO_ERR_INTERNAL); | ||
144 | |||
145 | ppin_len = (pin_len + 63) & ~63; | ||
146 | if (ppin_len < pin_len || ((*ppin)->ptr = calloc(1, ppin_len)) == NULL) { | ||
147 | fido_blob_free(ppin); | ||
148 | return (FIDO_ERR_INTERNAL); | ||
149 | } | ||
150 | |||
151 | memcpy((*ppin)->ptr, pin, pin_len); | ||
152 | (*ppin)->len = ppin_len; | ||
153 | |||
154 | return (FIDO_OK); | ||
155 | } | ||
156 | |||
157 | static int | ||
158 | fido_dev_change_pin_tx(fido_dev_t *dev, const char *pin, const char *oldpin) | ||
159 | { | ||
160 | fido_blob_t f; | ||
161 | fido_blob_t *ppin = NULL; | ||
162 | fido_blob_t *ecdh = NULL; | ||
163 | fido_blob_t *opin = NULL; | ||
164 | cbor_item_t *argv[6]; | ||
165 | es256_pk_t *pk = NULL; | ||
166 | int r; | ||
167 | |||
168 | memset(&f, 0, sizeof(f)); | ||
169 | memset(argv, 0, sizeof(argv)); | ||
170 | |||
171 | if ((opin = fido_blob_new()) == NULL || fido_blob_set(opin, | ||
172 | (const unsigned char *)oldpin, strlen(oldpin)) < 0) { | ||
173 | fido_log_debug("%s: fido_blob_set", __func__); | ||
174 | r = FIDO_ERR_INVALID_ARGUMENT; | ||
175 | goto fail; | ||
176 | } | ||
177 | |||
178 | if ((r = pad64(pin, &ppin)) != FIDO_OK) { | ||
179 | fido_log_debug("%s: pad64", __func__); | ||
180 | goto fail; | ||
181 | } | ||
182 | |||
183 | if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) { | ||
184 | fido_log_debug("%s: fido_do_ecdh", __func__); | ||
185 | goto fail; | ||
186 | } | ||
187 | |||
188 | if ((argv[0] = cbor_build_uint8(1)) == NULL || | ||
189 | (argv[1] = cbor_build_uint8(4)) == NULL || | ||
190 | (argv[2] = es256_pk_encode(pk, 0)) == NULL || | ||
191 | (argv[3] = cbor_encode_change_pin_auth(ecdh, ppin, opin)) == NULL || | ||
192 | (argv[4] = cbor_encode_pin_enc(ecdh, ppin)) == NULL || | ||
193 | (argv[5] = cbor_encode_pin_hash_enc(ecdh, opin)) == NULL) { | ||
194 | fido_log_debug("%s: cbor encode", __func__); | ||
195 | r = FIDO_ERR_INTERNAL; | ||
196 | goto fail; | ||
197 | } | ||
198 | |||
199 | if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 6, &f) < 0 || | ||
200 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
201 | fido_log_debug("%s: fido_tx", __func__); | ||
202 | r = FIDO_ERR_TX; | ||
203 | goto fail; | ||
204 | } | ||
205 | |||
206 | r = FIDO_OK; | ||
207 | fail: | ||
208 | cbor_vector_free(argv, nitems(argv)); | ||
209 | es256_pk_free(&pk); | ||
210 | fido_blob_free(&ppin); | ||
211 | fido_blob_free(&ecdh); | ||
212 | fido_blob_free(&opin); | ||
213 | free(f.ptr); | ||
214 | |||
215 | return (r); | ||
216 | |||
217 | } | ||
218 | |||
219 | static int | ||
220 | fido_dev_set_pin_tx(fido_dev_t *dev, const char *pin) | ||
221 | { | ||
222 | fido_blob_t f; | ||
223 | fido_blob_t *ppin = NULL; | ||
224 | fido_blob_t *ecdh = NULL; | ||
225 | cbor_item_t *argv[5]; | ||
226 | es256_pk_t *pk = NULL; | ||
227 | int r; | ||
228 | |||
229 | memset(&f, 0, sizeof(f)); | ||
230 | memset(argv, 0, sizeof(argv)); | ||
231 | |||
232 | if ((r = pad64(pin, &ppin)) != FIDO_OK) { | ||
233 | fido_log_debug("%s: pad64", __func__); | ||
234 | goto fail; | ||
235 | } | ||
236 | |||
237 | if ((r = fido_do_ecdh(dev, &pk, &ecdh)) != FIDO_OK) { | ||
238 | fido_log_debug("%s: fido_do_ecdh", __func__); | ||
239 | goto fail; | ||
240 | } | ||
241 | |||
242 | if ((argv[0] = cbor_build_uint8(1)) == NULL || | ||
243 | (argv[1] = cbor_build_uint8(3)) == NULL || | ||
244 | (argv[2] = es256_pk_encode(pk, 0)) == NULL || | ||
245 | (argv[3] = cbor_encode_set_pin_auth(ecdh, ppin)) == NULL || | ||
246 | (argv[4] = cbor_encode_pin_enc(ecdh, ppin)) == NULL) { | ||
247 | fido_log_debug("%s: cbor encode", __func__); | ||
248 | r = FIDO_ERR_INTERNAL; | ||
249 | goto fail; | ||
250 | } | ||
251 | |||
252 | if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 5, &f) < 0 || | ||
253 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
254 | fido_log_debug("%s: fido_tx", __func__); | ||
255 | r = FIDO_ERR_TX; | ||
256 | goto fail; | ||
257 | } | ||
258 | |||
259 | r = FIDO_OK; | ||
260 | fail: | ||
261 | cbor_vector_free(argv, nitems(argv)); | ||
262 | es256_pk_free(&pk); | ||
263 | fido_blob_free(&ppin); | ||
264 | fido_blob_free(&ecdh); | ||
265 | free(f.ptr); | ||
266 | |||
267 | return (r); | ||
268 | } | ||
269 | |||
270 | static int | ||
271 | fido_dev_set_pin_wait(fido_dev_t *dev, const char *pin, const char *oldpin, | ||
272 | int ms) | ||
273 | { | ||
274 | int r; | ||
275 | |||
276 | if (oldpin != NULL) { | ||
277 | if ((r = fido_dev_change_pin_tx(dev, pin, oldpin)) != FIDO_OK) { | ||
278 | fido_log_debug("%s: fido_dev_change_pin_tx", __func__); | ||
279 | return (r); | ||
280 | } | ||
281 | } else { | ||
282 | if ((r = fido_dev_set_pin_tx(dev, pin)) != FIDO_OK) { | ||
283 | fido_log_debug("%s: fido_dev_set_pin_tx", __func__); | ||
284 | return (r); | ||
285 | } | ||
286 | } | ||
287 | |||
288 | if ((r = fido_rx_cbor_status(dev, ms)) != FIDO_OK) { | ||
289 | fido_log_debug("%s: fido_rx_cbor_status", __func__); | ||
290 | return (r); | ||
291 | } | ||
292 | |||
293 | return (FIDO_OK); | ||
294 | } | ||
295 | |||
296 | int | ||
297 | fido_dev_set_pin(fido_dev_t *dev, const char *pin, const char *oldpin) | ||
298 | { | ||
299 | return (fido_dev_set_pin_wait(dev, pin, oldpin, -1)); | ||
300 | } | ||
301 | |||
302 | static int | ||
303 | parse_retry_count(const cbor_item_t *key, const cbor_item_t *val, void *arg) | ||
304 | { | ||
305 | int *retries = arg; | ||
306 | uint64_t n; | ||
307 | |||
308 | if (cbor_isa_uint(key) == false || | ||
309 | cbor_int_get_width(key) != CBOR_INT_8 || | ||
310 | cbor_get_uint8(key) != 3) { | ||
311 | fido_log_debug("%s: cbor type", __func__); | ||
312 | return (0); /* ignore */ | ||
313 | } | ||
314 | |||
315 | if (cbor_decode_uint64(val, &n) < 0 || n > INT_MAX) { | ||
316 | fido_log_debug("%s: cbor_decode_uint64", __func__); | ||
317 | return (-1); | ||
318 | } | ||
319 | |||
320 | *retries = (int)n; | ||
321 | |||
322 | return (0); | ||
323 | } | ||
324 | |||
325 | static int | ||
326 | fido_dev_get_retry_count_tx(fido_dev_t *dev) | ||
327 | { | ||
328 | fido_blob_t f; | ||
329 | cbor_item_t *argv[2]; | ||
330 | int r; | ||
331 | |||
332 | memset(&f, 0, sizeof(f)); | ||
333 | memset(argv, 0, sizeof(argv)); | ||
334 | |||
335 | if ((argv[0] = cbor_build_uint8(1)) == NULL || | ||
336 | (argv[1] = cbor_build_uint8(1)) == NULL) { | ||
337 | r = FIDO_ERR_INTERNAL; | ||
338 | goto fail; | ||
339 | } | ||
340 | |||
341 | if (cbor_build_frame(CTAP_CBOR_CLIENT_PIN, argv, 2, &f) < 0 || | ||
342 | fido_tx(dev, CTAP_FRAME_INIT | CTAP_CMD_CBOR, f.ptr, f.len) < 0) { | ||
343 | fido_log_debug("%s: fido_tx", __func__); | ||
344 | r = FIDO_ERR_TX; | ||
345 | goto fail; | ||
346 | } | ||
347 | |||
348 | r = FIDO_OK; | ||
349 | fail: | ||
350 | cbor_vector_free(argv, nitems(argv)); | ||
351 | free(f.ptr); | ||
352 | |||
353 | return (r); | ||
354 | } | ||
355 | |||
356 | static int | ||
357 | fido_dev_get_retry_count_rx(fido_dev_t *dev, int *retries, int ms) | ||
358 | { | ||
359 | const uint8_t cmd = CTAP_FRAME_INIT | CTAP_CMD_CBOR; | ||
360 | unsigned char reply[512]; | ||
361 | int reply_len; | ||
362 | int r; | ||
363 | |||
364 | *retries = 0; | ||
365 | |||
366 | if ((reply_len = fido_rx(dev, cmd, &reply, sizeof(reply), ms)) < 0) { | ||
367 | fido_log_debug("%s: fido_rx", __func__); | ||
368 | return (FIDO_ERR_RX); | ||
369 | } | ||
370 | |||
371 | if ((r = cbor_parse_reply(reply, (size_t)reply_len, retries, | ||
372 | parse_retry_count)) != FIDO_OK) { | ||
373 | fido_log_debug("%s: parse_retry_count", __func__); | ||
374 | return (r); | ||
375 | } | ||
376 | |||
377 | return (FIDO_OK); | ||
378 | } | ||
379 | |||
380 | static int | ||
381 | fido_dev_get_retry_count_wait(fido_dev_t *dev, int *retries, int ms) | ||
382 | { | ||
383 | int r; | ||
384 | |||
385 | if ((r = fido_dev_get_retry_count_tx(dev)) != FIDO_OK || | ||
386 | (r = fido_dev_get_retry_count_rx(dev, retries, ms)) != FIDO_OK) | ||
387 | return (r); | ||
388 | |||
389 | return (FIDO_OK); | ||
390 | } | ||
391 | |||
392 | int | ||
393 | fido_dev_get_retry_count(fido_dev_t *dev, int *retries) | ||
394 | { | ||
395 | return (fido_dev_get_retry_count_wait(dev, retries, -1)); | ||
396 | } | ||
397 | |||
398 | int | ||
399 | cbor_add_pin_params(fido_dev_t *dev, const fido_blob_t *hmac_data, | ||
400 | const es256_pk_t *pk, const fido_blob_t *ecdh, const char *pin, | ||
401 | cbor_item_t **auth, cbor_item_t **opt) | ||
402 | { | ||
403 | fido_blob_t *token = NULL; | ||
404 | int r; | ||
405 | |||
406 | if ((token = fido_blob_new()) == NULL) { | ||
407 | r = FIDO_ERR_INTERNAL; | ||
408 | goto fail; | ||
409 | } | ||
410 | |||
411 | if ((r = fido_dev_get_pin_token(dev, pin, ecdh, pk, token)) != FIDO_OK) { | ||
412 | fido_log_debug("%s: fido_dev_get_pin_token", __func__); | ||
413 | goto fail; | ||
414 | } | ||
415 | |||
416 | if ((*auth = cbor_encode_pin_auth(token, hmac_data)) == NULL || | ||
417 | (*opt = cbor_encode_pin_opt()) == NULL) { | ||
418 | fido_log_debug("%s: cbor encode", __func__); | ||
419 | r = FIDO_ERR_INTERNAL; | ||
420 | goto fail; | ||
421 | } | ||
422 | |||
423 | r = FIDO_OK; | ||
424 | fail: | ||
425 | fido_blob_free(&token); | ||
426 | |||
427 | return (r); | ||
428 | } | ||