Coverage Report

Created: 2025-10-04 18:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/libfido2/src/nfc.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2020-2024 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 <stdio.h>
9
#include <string.h>
10
11
#include "fido.h"
12
#include "fido/param.h"
13
#include "iso7816.h"
14
15
24.8k
#define TX_CHUNK_SIZE   240
16
17
static const uint8_t aid[] = { 0xa0, 0x00, 0x00, 0x06, 0x47, 0x2f, 0x00, 0x01 };
18
static const uint8_t v_u2f[] = { 'U', '2', 'F', '_', 'V', '2' };
19
static const uint8_t v_fido[] = { 'F', 'I', 'D', 'O', '_', '2', '_', '0' };
20
21
static int
22
tx_short_apdu(fido_dev_t *d, const iso7816_header_t *h, const uint8_t *payload,
23
    uint8_t payload_len, uint8_t cla_flags)
24
24.2k
{
25
24.2k
        uint8_t apdu[5 + UINT8_MAX + 1];
26
24.2k
        uint8_t sw[2];
27
24.2k
        size_t apdu_len;
28
24.2k
        int ok = -1;
29
30
24.2k
        memset(&apdu, 0, sizeof(apdu));
31
24.2k
        apdu[0] = h->cla | cla_flags;
32
24.2k
        apdu[1] = h->ins;
33
24.2k
        apdu[2] = h->p1;
34
24.2k
        apdu[3] = h->p2;
35
24.2k
        apdu[4] = payload_len;
36
24.2k
        memcpy(&apdu[5], payload, payload_len);
37
24.2k
        apdu_len = (size_t)(5 + payload_len);
38
39
24.2k
        if (!(cla_flags & 0x10))
40
23.7k
                apdu_len += 1;
41
42
24.2k
        if (d->io.write(d->io_handle, apdu, apdu_len) < 0) {
43
725
                fido_log_debug("%s: write", __func__);
44
725
                goto fail;
45
725
        }
46
47
23.5k
        if (cla_flags & 0x10) {
48
515
                if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) {
49
427
                        fido_log_debug("%s: read", __func__);
50
427
                        goto fail;
51
427
                }
52
88
                if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) {
53
66
                        fido_log_debug("%s: unexpected sw", __func__);
54
66
                        goto fail;
55
66
                }
56
88
        }
57
58
23.0k
        ok = 0;
59
24.2k
fail:
60
24.2k
        explicit_bzero(apdu, sizeof(apdu));
61
62
24.2k
        return ok;
63
23.0k
}
64
65
static int
66
nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len)
67
24.4k
{
68
24.4k
        iso7816_header_t h;
69
70
24.4k
        if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) {
71
193
                fido_log_debug("%s: header", __func__);
72
193
                return -1;
73
193
        }
74
24.2k
        if (apdu_len < 2) {
75
15
                fido_log_debug("%s: apdu_len %zu", __func__, apdu_len);
76
15
                return -1;
77
15
        }
78
79
24.2k
        apdu_len -= 2; /* trim le1 le2 */
80
81
24.2k
        while (apdu_len > TX_CHUNK_SIZE) {
82
525
                if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) {
83
503
                        fido_log_debug("%s: chain", __func__);
84
503
                        return -1;
85
503
                }
86
22
                apdu_ptr += TX_CHUNK_SIZE;
87
22
                apdu_len -= TX_CHUNK_SIZE;
88
22
        }
89
90
23.7k
        if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) {
91
715
                fido_log_debug("%s: tx_short_apdu", __func__);
92
715
                return -1;
93
715
        }
94
95
23.0k
        return 0;
96
23.7k
}
97
98
int
99
fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count)
100
25.9k
{
101
25.9k
        iso7816_apdu_t *apdu = NULL;
102
25.9k
        const uint8_t *ptr;
103
25.9k
        size_t len;
104
25.9k
        int ok = -1;
105
106
25.9k
        switch (cmd) {
107
19.4k
        case CTAP_CMD_INIT: /* select */
108
19.4k
                if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL ||
109
19.4k
                    iso7816_add(apdu, aid, sizeof(aid)) < 0) {
110
456
                        fido_log_debug("%s: iso7816", __func__);
111
456
                        goto fail;
112
456
                }
113
18.9k
                break;
114
18.9k
        case CTAP_CMD_CBOR: /* wrap cbor */
115
3.63k
                if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00,
116
3.63k
                    (uint16_t)count)) == NULL ||
117
3.63k
                    iso7816_add(apdu, buf, count) < 0) {
118
235
                        fido_log_debug("%s: iso7816", __func__);
119
235
                        goto fail;
120
235
                }
121
3.39k
                break;
122
3.39k
        case CTAP_CMD_MSG: /* already an apdu */
123
2.08k
                break;
124
873
        default:
125
873
                fido_log_debug("%s: cmd=%02x", __func__, cmd);
126
873
                goto fail;
127
25.9k
        }
128
129
24.4k
        if (apdu != NULL) {
130
22.3k
                ptr = iso7816_ptr(apdu);
131
22.3k
                len = iso7816_len(apdu);
132
22.3k
        } else {
133
2.08k
                ptr = buf;
134
2.08k
                len = count;
135
2.08k
        }
136
137
24.4k
        if (nfc_do_tx(d, ptr, len) < 0) {
138
1.42k
                fido_log_debug("%s: nfc_do_tx", __func__);
139
1.42k
                goto fail;
140
1.42k
        }
141
142
23.0k
        ok = 0;
143
25.9k
fail:
144
25.9k
        iso7816_free(&apdu);
145
146
25.9k
        return ok;
147
23.0k
}
148
149
static int
150
tx_get_response(fido_dev_t *d, uint8_t count, bool cbor)
151
605
{
152
605
        uint8_t apdu[5];
153
154
605
        memset(apdu, 0, sizeof(apdu));
155
605
        apdu[0] = cbor ? 0x80 : 0x00;
156
605
        apdu[1] = 0xc0; /* GET_RESPONSE */
157
605
        apdu[4] = count;
158
159
605
        if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) {
160
43
                fido_log_debug("%s: write", __func__);
161
43
                return -1;
162
43
        }
163
164
562
        return 0;
165
605
}
166
167
static int
168
rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms)
169
23.4k
{
170
23.4k
        uint8_t f[256 + 2];
171
23.4k
        struct timespec ts;
172
23.4k
        int n, ok = -1;
173
174
23.4k
        if (fido_time_now(&ts) != 0)
175
512
                goto fail;
176
177
22.9k
        if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) {
178
14.0k
                fido_log_debug("%s: read", __func__);
179
14.0k
                goto fail;
180
14.0k
        }
181
182
8.87k
        if (fido_time_delta(&ts, ms) != 0)
183
215
                goto fail;
184
185
8.65k
        if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) {
186
2.05k
                fido_log_debug("%s: fido_buf_write", __func__);
187
2.05k
                goto fail;
188
2.05k
        }
189
190
6.60k
        memcpy(sw, f + n - 2, 2);
191
192
6.60k
        ok = 0;
193
23.4k
fail:
194
23.4k
        explicit_bzero(f, sizeof(f));
195
196
23.4k
        return ok;
197
6.60k
}
198
199
static int
200
rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms, bool cbor)
201
22.8k
{
202
22.8k
        uint8_t sw[2];
203
22.8k
        const size_t bufsiz = count;
204
205
22.8k
        if (rx_apdu(d, sw, &buf, &count, &ms) < 0) {
206
16.2k
                fido_log_debug("%s: preamble", __func__);
207
16.2k
                return -1;
208
16.2k
        }
209
210
6.60k
        while (sw[0] == SW1_MORE_DATA)
211
605
                if (tx_get_response(d, sw[1], cbor) < 0 ||
212
605
                    rx_apdu(d, sw, &buf, &count, &ms) < 0) {
213
605
                        fido_log_debug("%s: chain", __func__);
214
605
                        return -1;
215
605
                }
216
217
6.00k
        if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) {
218
49
                fido_log_debug("%s: sw", __func__);
219
49
                return -1;
220
49
        }
221
222
5.95k
        if (bufsiz - count > INT_MAX) {
223
0
                fido_log_debug("%s: bufsiz", __func__);
224
0
                return -1;
225
0
        }
226
227
5.95k
        return (int)(bufsiz - count);
228
5.95k
}
229
230
static int
231
rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
232
3.27k
{
233
3.27k
        int r;
234
235
3.27k
        if ((r = rx_msg(d, buf, count, ms, true)) < 2)
236
3.00k
                return -1;
237
238
270
        return r - 2;
239
3.27k
}
240
241
static int
242
rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
243
18.1k
{
244
18.1k
        fido_ctap_info_t *attr = (fido_ctap_info_t *)buf;
245
18.1k
        uint8_t f[64];
246
18.1k
        int n;
247
248
18.1k
        if (count != sizeof(*attr)) {
249
620
                fido_log_debug("%s: count=%zu", __func__, count);
250
620
                return -1;
251
620
        }
252
253
17.5k
        memset(attr, 0, sizeof(*attr));
254
255
17.5k
        if ((n = rx_msg(d, f, sizeof(f), ms, false)) < 2 ||
256
17.5k
            (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) {
257
14.6k
                fido_log_debug("%s: read", __func__);
258
14.6k
                return -1;
259
14.6k
        }
260
261
2.94k
        n -= 2;
262
263
2.94k
        if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0)
264
92
                attr->flags = FIDO_CAP_CBOR;
265
2.85k
        else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0)
266
83
                attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
267
2.76k
        else {
268
2.76k
                fido_log_debug("%s: unknown version string", __func__);
269
2.76k
#ifdef FIDO_FUZZ
270
2.76k
                attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
271
#else
272
                return -1;
273
#endif
274
2.76k
        }
275
276
2.94k
        memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */
277
278
2.94k
        return (int)count;
279
17.5k
}
280
281
int
282
fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms)
283
24.3k
{
284
24.3k
        switch (cmd) {
285
18.1k
        case CTAP_CMD_INIT:
286
18.1k
                return rx_init(d, buf, count, ms);
287
3.27k
        case CTAP_CMD_CBOR:
288
3.27k
                return rx_cbor(d, buf, count, ms);
289
2.02k
        case CTAP_CMD_MSG:
290
2.02k
                return rx_msg(d, buf, count, ms, false);
291
873
        default:
292
873
                fido_log_debug("%s: cmd=%02x", __func__, cmd);
293
873
                return -1;
294
24.3k
        }
295
24.3k
}
296
297
bool
298
nfc_is_fido(const char *path)
299
2.80M
{
300
2.80M
        bool fido = false;
301
2.80M
        fido_dev_t *d;
302
2.80M
        int r;
303
304
2.80M
        if ((d = fido_dev_new()) == NULL) {
305
7.00k
                fido_log_debug("%s: fido_dev_new", __func__);
306
7.00k
                goto fail;
307
7.00k
        }
308
        /* fido_dev_open selects the fido applet */
309
2.79M
        if ((r = fido_dev_open(d, path)) != FIDO_OK) {
310
2.79M
                fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r);
311
2.79M
                goto fail;
312
2.79M
        }
313
759
        if ((r = fido_dev_close(d)) != FIDO_OK) {
314
0
                fido_log_debug("%s: fido_dev_close: 0x%x", __func__, r);
315
0
                goto fail;
316
317
0
        }
318
319
759
        fido = true;
320
2.80M
fail:
321
2.80M
        fido_dev_free(&d);
322
323
2.80M
        return fido;
324
759
}
325
326
#ifdef USE_NFC
327
bool
328
fido_is_nfc(const char *path)
329
3.15M
{
330
3.15M
        return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0;
331
3.15M
}
332
333
int
334
fido_dev_set_nfc(fido_dev_t *d)
335
2.78M
{
336
2.78M
        if (d->io_handle != NULL) {
337
0
                fido_log_debug("%s: device open", __func__);
338
0
                return -1;
339
0
        }
340
2.78M
        d->io_own = true;
341
2.78M
        d->io = (fido_dev_io_t) {
342
2.78M
                fido_nfc_open,
343
2.78M
                fido_nfc_close,
344
2.78M
                fido_nfc_read,
345
2.78M
                fido_nfc_write,
346
2.78M
        };
347
2.78M
        d->transport = (fido_dev_transport_t) {
348
2.78M
                fido_nfc_rx,
349
2.78M
                fido_nfc_tx,
350
2.78M
        };
351
352
2.78M
        return 0;
353
2.78M
}
354
#endif /* USE_NFC */