Coverage Report

Created: 2026-04-08 06:32

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
105k
#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
100k
{
25
100k
        uint8_t apdu[5 + UINT8_MAX + 1];
26
100k
        uint8_t sw[2];
27
100k
        size_t apdu_len;
28
100k
        int ok = -1;
29
30
100k
        memset(&apdu, 0, sizeof(apdu));
31
100k
        apdu[0] = h->cla | cla_flags;
32
100k
        apdu[1] = h->ins;
33
100k
        apdu[2] = h->p1;
34
100k
        apdu[3] = h->p2;
35
100k
        apdu[4] = payload_len;
36
100k
        memcpy(&apdu[5], payload, payload_len);
37
100k
        apdu_len = (size_t)(5 + payload_len);
38
39
100k
        if (!(cla_flags & 0x10))
40
97.0k
                apdu_len += 1;
41
42
100k
        if (d->io.write(d->io_handle, apdu, apdu_len) < 0) {
43
1.58k
                fido_log_debug("%s: write", __func__);
44
1.58k
                goto fail;
45
1.58k
        }
46
47
98.9k
        if (cla_flags & 0x10) {
48
3.38k
                if (d->io.read(d->io_handle, sw, sizeof(sw), -1) != 2) {
49
1.89k
                        fido_log_debug("%s: read", __func__);
50
1.89k
                        goto fail;
51
1.89k
                }
52
1.49k
                if ((sw[0] << 8 | sw[1]) != SW_NO_ERROR) {
53
500
                        fido_log_debug("%s: unexpected sw", __func__);
54
500
                        goto fail;
55
500
                }
56
1.49k
        }
57
58
96.5k
        ok = 0;
59
100k
fail:
60
100k
        explicit_bzero(apdu, sizeof(apdu));
61
62
100k
        return ok;
63
96.5k
}
64
65
static int
66
nfc_do_tx(fido_dev_t *d, const uint8_t *apdu_ptr, size_t apdu_len)
67
100k
{
68
100k
        iso7816_header_t h;
69
70
100k
        if (fido_buf_read(&apdu_ptr, &apdu_len, &h, sizeof(h)) < 0) {
71
470
                fido_log_debug("%s: header", __func__);
72
470
                return -1;
73
470
        }
74
99.6k
        if (apdu_len < 2) {
75
105
                fido_log_debug("%s: apdu_len %zu", __func__, apdu_len);
76
105
                return -1;
77
105
        }
78
79
99.5k
        apdu_len -= 2; /* trim le1 le2 */
80
81
100k
        while (apdu_len > TX_CHUNK_SIZE) {
82
3.40k
                if (tx_short_apdu(d, &h, apdu_ptr, TX_CHUNK_SIZE, 0x10) < 0) {
83
2.40k
                        fido_log_debug("%s: chain", __func__);
84
2.40k
                        return -1;
85
2.40k
                }
86
996
                apdu_ptr += TX_CHUNK_SIZE;
87
996
                apdu_len -= TX_CHUNK_SIZE;
88
996
        }
89
90
97.0k
        if (tx_short_apdu(d, &h, apdu_ptr, (uint8_t)apdu_len, 0) < 0) {
91
1.57k
                fido_log_debug("%s: tx_short_apdu", __func__);
92
1.57k
                return -1;
93
1.57k
        }
94
95
95.5k
        return 0;
96
97.0k
}
97
98
int
99
fido_nfc_tx(fido_dev_t *d, uint8_t cmd, const unsigned char *buf, size_t count)
100
105k
{
101
105k
        iso7816_apdu_t *apdu = NULL;
102
105k
        const uint8_t *ptr;
103
105k
        size_t len;
104
105k
        int ok = -1;
105
106
105k
        switch (cmd) {
107
82.8k
        case CTAP_CMD_INIT: /* select */
108
82.8k
                if ((apdu = iso7816_new(0, 0xa4, 0x04, sizeof(aid))) == NULL ||
109
82.8k
                    iso7816_add(apdu, aid, sizeof(aid)) < 0) {
110
639
                        fido_log_debug("%s: iso7816", __func__);
111
639
                        goto fail;
112
639
                }
113
82.2k
                break;
114
82.2k
        case CTAP_CMD_CBOR: /* wrap cbor */
115
12.2k
                if (count > UINT16_MAX || (apdu = iso7816_new(0x80, 0x10, 0x00,
116
12.2k
                    (uint16_t)count)) == NULL ||
117
12.2k
                    iso7816_add(apdu, buf, count) < 0) {
118
139
                        fido_log_debug("%s: iso7816", __func__);
119
139
                        goto fail;
120
139
                }
121
12.1k
                break;
122
12.1k
        case CTAP_CMD_MSG: /* already an apdu */
123
5.71k
                break;
124
4.35k
        default:
125
4.35k
                fido_log_debug("%s: cmd=%02x", __func__, cmd);
126
4.35k
                goto fail;
127
105k
        }
128
129
100k
        if (apdu != NULL) {
130
94.3k
                ptr = iso7816_ptr(apdu);
131
94.3k
                len = iso7816_len(apdu);
132
94.3k
        } else {
133
5.71k
                ptr = buf;
134
5.71k
                len = count;
135
5.71k
        }
136
137
100k
        if (nfc_do_tx(d, ptr, len) < 0) {
138
4.54k
                fido_log_debug("%s: nfc_do_tx", __func__);
139
4.54k
                goto fail;
140
4.54k
        }
141
142
95.5k
        ok = 0;
143
105k
fail:
144
105k
        iso7816_free(&apdu);
145
146
105k
        return ok;
147
95.5k
}
148
149
static int
150
tx_get_response(fido_dev_t *d, uint8_t count, bool cbor)
151
2.96k
{
152
2.96k
        uint8_t apdu[5];
153
154
2.96k
        memset(apdu, 0, sizeof(apdu));
155
2.96k
        apdu[0] = cbor ? 0x80 : 0x00;
156
2.96k
        apdu[1] = 0xc0; /* GET_RESPONSE */
157
2.96k
        apdu[4] = count;
158
159
2.96k
        if (d->io.write(d->io_handle, apdu, sizeof(apdu)) < 0) {
160
113
                fido_log_debug("%s: write", __func__);
161
113
                return -1;
162
113
        }
163
164
2.84k
        return 0;
165
2.96k
}
166
167
static int
168
rx_apdu(fido_dev_t *d, uint8_t sw[2], unsigned char **buf, size_t *count, int *ms)
169
96.8k
{
170
96.8k
        uint8_t f[256 + 2];
171
96.8k
        struct timespec ts;
172
96.8k
        int n, ok = -1;
173
174
96.8k
        if (fido_time_now(&ts) != 0)
175
467
                goto fail;
176
177
96.3k
        if ((n = d->io.read(d->io_handle, f, sizeof(f), *ms)) < 2) {
178
67.1k
                fido_log_debug("%s: read", __func__);
179
67.1k
                goto fail;
180
67.1k
        }
181
182
29.2k
        if (fido_time_delta(&ts, ms) != 0)
183
49
                goto fail;
184
185
29.2k
        if (fido_buf_write(buf, count, f, (size_t)(n - 2)) < 0) {
186
4.68k
                fido_log_debug("%s: fido_buf_write", __func__);
187
4.68k
                goto fail;
188
4.68k
        }
189
190
24.5k
        memcpy(sw, f + n - 2, 2);
191
192
24.5k
        ok = 0;
193
96.8k
fail:
194
96.8k
        explicit_bzero(f, sizeof(f));
195
196
96.8k
        return ok;
197
24.5k
}
198
199
static int
200
rx_msg(fido_dev_t *d, unsigned char *buf, size_t count, int ms, bool cbor)
201
94.0k
{
202
94.0k
        uint8_t sw[2];
203
94.0k
        const size_t bufsiz = count;
204
205
94.0k
        if (rx_apdu(d, sw, &buf, &count, &ms) < 0) {
206
69.4k
                fido_log_debug("%s: preamble", __func__);
207
69.4k
                return -1;
208
69.4k
        }
209
210
24.5k
        while (sw[0] == SW1_MORE_DATA)
211
2.96k
                if (tx_get_response(d, sw[1], cbor) < 0 ||
212
2.96k
                    rx_apdu(d, sw, &buf, &count, &ms) < 0) {
213
2.96k
                        fido_log_debug("%s: chain", __func__);
214
2.96k
                        return -1;
215
2.96k
                }
216
217
21.5k
        if (fido_buf_write(&buf, &count, sw, sizeof(sw)) < 0) {
218
133
                fido_log_debug("%s: sw", __func__);
219
133
                return -1;
220
133
        }
221
222
21.4k
        if (bufsiz - count > INT_MAX) {
223
0
                fido_log_debug("%s: bufsiz", __func__);
224
0
                return -1;
225
0
        }
226
227
21.4k
        return (int)(bufsiz - count);
228
21.4k
}
229
230
static int
231
rx_cbor(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
232
11.9k
{
233
11.9k
        int r;
234
235
11.9k
        if ((r = rx_msg(d, buf, count, ms, true)) < 2)
236
10.1k
                return -1;
237
238
1.85k
        return r - 2;
239
11.9k
}
240
241
static int
242
rx_init(fido_dev_t *d, unsigned char *buf, size_t count, int ms)
243
80.2k
{
244
80.2k
        fido_ctap_info_t *attr = (fido_ctap_info_t *)buf;
245
80.2k
        uint8_t f[64];
246
80.2k
        int n;
247
248
80.2k
        if (count != sizeof(*attr)) {
249
3.87k
                fido_log_debug("%s: count=%zu", __func__, count);
250
3.87k
                return -1;
251
3.87k
        }
252
253
76.3k
        memset(attr, 0, sizeof(*attr));
254
255
76.3k
        if ((n = rx_msg(d, f, sizeof(f), ms, false)) < 2 ||
256
76.3k
            (f[n - 2] << 8 | f[n - 1]) != SW_NO_ERROR) {
257
68.3k
                fido_log_debug("%s: read", __func__);
258
68.3k
                return -1;
259
68.3k
        }
260
261
8.02k
        n -= 2;
262
263
8.02k
        if (n == sizeof(v_u2f) && memcmp(f, v_u2f, sizeof(v_u2f)) == 0)
264
326
                attr->flags = FIDO_CAP_CBOR;
265
7.69k
        else if (n == sizeof(v_fido) && memcmp(f, v_fido, sizeof(v_fido)) == 0)
266
37
                attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
267
7.65k
        else {
268
7.65k
                fido_log_debug("%s: unknown version string", __func__);
269
7.65k
#ifdef FIDO_FUZZ
270
7.65k
                attr->flags = FIDO_CAP_CBOR | FIDO_CAP_NMSG;
271
#else
272
                return -1;
273
#endif
274
7.65k
        }
275
276
8.02k
        memcpy(&attr->nonce, &d->nonce, sizeof(attr->nonce)); /* XXX */
277
278
8.02k
        return (int)count;
279
76.3k
}
280
281
int
282
fido_nfc_rx(fido_dev_t *d, uint8_t cmd, unsigned char *buf, size_t count, int ms)
283
102k
{
284
102k
        switch (cmd) {
285
80.2k
        case CTAP_CMD_INIT:
286
80.2k
                return rx_init(d, buf, count, ms);
287
11.9k
        case CTAP_CMD_CBOR:
288
11.9k
                return rx_cbor(d, buf, count, ms);
289
5.65k
        case CTAP_CMD_MSG:
290
5.65k
                return rx_msg(d, buf, count, ms, false);
291
4.35k
        default:
292
4.35k
                fido_log_debug("%s: cmd=%02x", __func__, cmd);
293
4.35k
                return -1;
294
102k
        }
295
102k
}
296
297
bool
298
nfc_is_fido(const char *path)
299
2.37M
{
300
2.37M
        bool fido = false;
301
2.37M
        fido_dev_t *d;
302
2.37M
        int r;
303
304
2.37M
        if ((d = fido_dev_new()) == NULL) {
305
5.63k
                fido_log_debug("%s: fido_dev_new", __func__);
306
5.63k
                goto fail;
307
5.63k
        }
308
        /* fido_dev_open selects the fido applet */
309
2.37M
        if ((r = fido_dev_open(d, path)) != FIDO_OK) {
310
2.36M
                fido_log_debug("%s: fido_dev_open: 0x%x", __func__, r);
311
2.36M
                goto fail;
312
2.36M
        }
313
5.61k
        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
5.61k
        fido = true;
320
2.37M
fail:
321
2.37M
        fido_dev_free(&d);
322
323
2.37M
        return fido;
324
5.61k
}
325
326
#ifdef USE_NFC
327
bool
328
fido_is_nfc(const char *path)
329
2.97M
{
330
2.97M
        return strncmp(path, FIDO_NFC_PREFIX, strlen(FIDO_NFC_PREFIX)) == 0;
331
2.97M
}
332
333
int
334
fido_dev_set_nfc(fido_dev_t *d)
335
2.29M
{
336
2.29M
        if (d->io_handle != NULL) {
337
0
                fido_log_debug("%s: device open", __func__);
338
0
                return -1;
339
0
        }
340
2.29M
        d->io_own = true;
341
2.29M
        d->io = (fido_dev_io_t) {
342
2.29M
                fido_nfc_open,
343
2.29M
                fido_nfc_close,
344
2.29M
                fido_nfc_read,
345
2.29M
                fido_nfc_write,
346
2.29M
        };
347
2.29M
        d->transport = (fido_dev_transport_t) {
348
2.29M
                fido_nfc_rx,
349
2.29M
                fido_nfc_tx,
350
2.29M
        };
351
352
2.29M
        return 0;
353
2.29M
}
354
#endif /* USE_NFC */