• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

canokeys / canokey-core / 26453907226

26 May 2026 10:07AM UTC coverage: 89.919% (-0.4%) from 90.351%
26453907226

Pull #152

github

dangfan
Update canokey-crypto for CIU ML-KEM APIs
Pull Request #152: Move CIU-backed persistent configuration hooks into core

157 of 227 new or added lines in 18 files covered. (69.16%)

2 existing lines in 1 file now uncovered.

10472 of 11646 relevant lines covered (89.92%)

277415.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.9
/applets/admin/admin.c
1
// SPDX-License-Identifier: Apache-2.0
2
#include <admin.h>
3
#include <crypto-util.h>
4
#include <ctap.h>
5
#include <device-config.h>
6
#include <device.h>
7
#include <fs.h>
8
#if ENABLE_APPLET_NDEF
9
#include <ndef.h>
10
#endif
11
#include <oath.h>
12
#include <openpgp.h>
13
#include <pass.h>
14
#include <pin.h>
15
#include <piv.h>
16

17
#define PIN_RETRY_COUNTER 3
18

19
static pin_t pin = {.min_length = 6, .max_length = PIN_MAX_LENGTH, .is_validated = 0, .path = "admin-pin"};
20

21
static const admin_device_config_t default_cfg = {.led_normally_on = 1, .ndef_en = 1, .webusb_landing_en = 1};
22

23
static admin_device_config_t current_config;
24
static admin_device_config_t admin_get_current_config(void);
25

26
__attribute__((weak)) int admin_vendor_specific(const CAPDU *capdu, RAPDU *rapdu) {
27
  UNUSED(capdu);
28
  UNUSED(rapdu);
29
  return 0;
1✔
30
}
31

32
__attribute__((weak)) int admin_vendor_version(const CAPDU *capdu, RAPDU *rapdu) {
33
  UNUSED(capdu);
34
  UNUSED(rapdu);
35
  return 0;
×
36
}
37

38
__attribute__((weak)) int admin_vendor_hw_variant(const CAPDU *capdu, RAPDU *rapdu) {
39
  UNUSED(capdu);
40
  UNUSED(rapdu);
41
  return 0;
×
42
}
43

44
__attribute__((weak)) int admin_vendor_hw_sn(const CAPDU *capdu, RAPDU *rapdu) {
45
  UNUSED(capdu);
46
  UNUSED(rapdu);
47
  return 0;
×
48
}
49

50
__attribute__((weak)) int admin_vendor_nfc_enable(const CAPDU *capdu, RAPDU *rapdu, bool pin_validated) {
51
  UNUSED(capdu);
52
  UNUSED(rapdu);
53
  UNUSED(pin_validated);
54
  return 0;
×
55
}
56

57
__weak int admin_platform_device_config_read(admin_device_config_t *cfg) {
58
  UNUSED(cfg);
NEW
59
  return -1;
×
60
}
61

62
__weak int admin_platform_device_config_write(const admin_device_config_t *cfg) {
63
  UNUSED(cfg);
NEW
64
  return -1;
×
65
}
66

67
__weak int admin_platform_serial_read(uint8_t *buf) {
68
  UNUSED(buf);
NEW
69
  return -1;
×
70
}
71

72
__weak int admin_platform_serial_write_once(const uint8_t *buf) {
73
  UNUSED(buf);
NEW
74
  return -1;
×
75
}
76

77
__weak int admin_platform_kbd_keymap_write(uint8_t layout_id, const uint8_t *keymap, uint16_t len) {
78
  UNUSED(layout_id);
79
  UNUSED(keymap);
80
  UNUSED(len);
NEW
81
  return -1;
×
82
}
83

84
__weak int admin_platform_kbd_keymap_read(uint8_t *layout_id, uint8_t *keymap, uint16_t len) {
85
  UNUSED(layout_id);
86
  UNUSED(keymap);
87
  UNUSED(len);
NEW
88
  return -1;
×
89
}
90

NEW
91
__weak int admin_platform_kbd_keymap_clear(void) { return -1; }
×
92

93
// Query the platform hook on every read so a platform-backed config is not
94
// shadowed by core RAM after a vendor/admin APDU updates flash directly.
95
uint8_t device_config_is_led_normally_on(void) { return admin_get_current_config().led_normally_on; }
3,039✔
96

97
uint8_t device_config_is_ndef_enabled(void) { return admin_get_current_config().ndef_en; }
20✔
98

NEW
99
uint8_t device_config_is_webusb_landing_enabled(void) { return admin_get_current_config().webusb_landing_en; }
×
100

101
static admin_device_config_t admin_get_current_config(void) {
3,093✔
102
  admin_device_config_t cfg = default_cfg;
3,093✔
103
  if (admin_platform_device_config_read(&cfg) == 0) return cfg;
3,093✔
NEW
104
  return default_cfg;
×
105
}
106

107
void admin_poweroff(void) { pin.is_validated = 0; }
697✔
108

109
int admin_install(const uint8_t reset) {
110
  admin_poweroff();
336✔
111
  // Device config is platform-backed. Core keeps no LittleFS fallback, which
112
  // avoids two independent sources of truth for LED/NDEF/WebUSB flags.
113
  if (reset || admin_platform_device_config_read(&current_config) < 0) {
336✔
114
    current_config = default_cfg;
3✔
115
    if (admin_platform_device_config_write(&current_config) < 0) return -1;
3✔
116
  }
117
  if (reset || get_file_size(pin.path) < 0) {
336✔
118
    if (pin_create(&pin, "123456", 6, PIN_RETRY_COUNTER) < 0) return -1;
6✔
119
  }
120
  return 0;
336✔
121
}
122

123
static int admin_verify(const CAPDU *capdu, RAPDU *rapdu) {
112✔
124
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
112✔
125
  if (LC == 0) {
112✔
126
    if (pin.is_validated) return 0;
28✔
127
    const int retries = pin_get_retries(&pin);
28✔
128
    if (retries < 0) return -1;
28✔
129
    EXCEPT(pin_get_retry_sw((uint8_t)retries));
28✔
130
  }
131
  uint8_t ctr;
84✔
132
  const int err = pin_verify(&pin, DATA, LC, &ctr);
84✔
133
  if (err == PIN_IO_FAIL) return -1;
84✔
134
  if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH);
84✔
135
  if (ctr == 0) EXCEPT(SW_AUTHENTICATION_BLOCKED);
57✔
136
  if (err == PIN_AUTH_FAIL) EXCEPT(pin_get_retry_sw(ctr));
53✔
137
  return 0;
44✔
138
}
139

140
static int admin_change_pin(const CAPDU *capdu, RAPDU *rapdu) {
7✔
141
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
7✔
142
  const int err = pin_update(&pin, DATA, LC);
7✔
143
  if (err == PIN_IO_FAIL) return -1;
7✔
144
  if (err == PIN_LENGTH_INVALID) EXCEPT(SW_WRONG_LENGTH);
7✔
145
  return 0;
3✔
146
}
147

148
static int admin_write_sn(const CAPDU *capdu, RAPDU *rapdu) {
1✔
149
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
1✔
150
  if (LC != 0x04) EXCEPT(SW_WRONG_LENGTH);
1✔
151
  if (admin_platform_serial_write_once(DATA) < 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED);
1✔
152
  return 0;
1✔
153
}
154

155
static int admin_read_sn(const CAPDU *capdu, RAPDU *rapdu) {
2✔
156
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
2✔
157
  if (LE < 4) EXCEPT(SW_WRONG_LENGTH);
2✔
158

159
  device_config_fill_serial(RDATA);
2✔
160
  LL = 4;
2✔
161

162
  return 0;
2✔
163
}
164

165
static int admin_config(const CAPDU *capdu, RAPDU *rapdu) {
18✔
166
  current_config = admin_get_current_config();
18✔
167
  switch (P1) {
18✔
168
  case ADMIN_P1_CFG_LED_ON:
4✔
169
    current_config.led_normally_on = P2 & 1;
4✔
170
    break;
4✔
171
  case ADMIN_P1_CFG_NDEF:
6✔
172
    current_config.ndef_en = P2 & 1;
6✔
173
    break;
6✔
174
  case ADMIN_P1_CFG_WEBUSB_LANDING:
4✔
175
    current_config.webusb_landing_en = P2 & 1;
4✔
176
    break;
4✔
177
  default:
4✔
178
    EXCEPT(SW_WRONG_P1P2);
4✔
179
  }
180
  const int ret = admin_platform_device_config_write(&current_config);
14✔
181
  stop_blinking();
14✔
182
  return ret;
14✔
183
}
184

185
static int admin_read_config(const CAPDU *capdu, RAPDU *rapdu) {
16✔
186
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
16✔
187
  if (LE < 6) EXCEPT(SW_WRONG_LENGTH);
16✔
188

189
  const admin_device_config_t cfg = admin_get_current_config();
16✔
190

191
  RDATA[0] = cfg.led_normally_on;
16✔
192
  RDATA[1] = 0; // reserved
16✔
193
#if ENABLE_APPLET_NDEF
194
  RDATA[2] = ndef_is_read_only();
16✔
195
#else
196
  RDATA[2] = 0;
197
#endif
198
  RDATA[3] = cfg.ndef_en;
16✔
199
  RDATA[4] = cfg.webusb_landing_en;
16✔
200
  RDATA[5] = 0; // reserved
16✔
201
  LL = 6;
16✔
202

203
  return 0;
16✔
204
}
205

206
static int admin_flash_usage(const CAPDU *capdu, RAPDU *rapdu) {
2✔
207
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
2✔
208
  if (LE < 2) EXCEPT(SW_WRONG_LENGTH);
2✔
209

210
  RDATA[0] = get_fs_usage();
2✔
211
  RDATA[1] = get_fs_size();
2✔
212
  LL = 2;
2✔
213

214
  return 0;
2✔
215
}
216

NEW
217
static int admin_write_kbd_keymap(const CAPDU *capdu, RAPDU *rapdu) {
×
218
  UNUSED(rapdu);
219
  // Payload is 128 ASCII entries, each {modifier, HID usage}. P2 carries a
220
  // host-defined layout id so tooling can identify what was installed.
NEW
221
  if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2);
×
NEW
222
  if (LC != ADMIN_KBD_KEYMAP_LENGTH) EXCEPT(SW_WRONG_LENGTH);
×
NEW
223
  return admin_platform_kbd_keymap_write(P2, DATA, LC);
×
224
}
225

NEW
226
static int admin_read_kbd_keymap(const CAPDU *capdu, RAPDU *rapdu) {
×
NEW
227
  if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2);
×
NEW
228
  if (LC != 0) EXCEPT(SW_WRONG_LENGTH);
×
NEW
229
  switch (P2) {
×
NEW
230
  case ADMIN_P2_KBD_READ_LAYOUT_ID:
×
NEW
231
    if (LE < 1) EXCEPT(SW_WRONG_LENGTH);
×
NEW
232
    if (admin_platform_kbd_keymap_read(RDATA, NULL, 0) < 0) EXCEPT(SW_REFERENCE_DATA_NOT_FOUND);
×
NEW
233
    LL = 1;
×
NEW
234
    return 0;
×
NEW
235
  case ADMIN_P2_KBD_READ_KEYMAP: {
×
NEW
236
    uint8_t layout_id;
×
NEW
237
    if (LE < ADMIN_KBD_KEYMAP_LENGTH) EXCEPT(SW_WRONG_LENGTH);
×
NEW
238
    if (admin_platform_kbd_keymap_read(&layout_id, RDATA, ADMIN_KBD_KEYMAP_LENGTH) < 0)
×
NEW
239
      EXCEPT(SW_REFERENCE_DATA_NOT_FOUND);
×
NEW
240
    LL = ADMIN_KBD_KEYMAP_LENGTH;
×
NEW
241
    return 0;
×
242
  }
NEW
243
  default:
×
NEW
244
    EXCEPT(SW_WRONG_P1P2);
×
245
  }
246
}
247

NEW
248
static int admin_clear_kbd_keymap(const CAPDU *capdu, RAPDU *rapdu) {
×
249
  UNUSED(rapdu);
NEW
250
  if (P1 != 0x00 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
×
NEW
251
  if (LC != 0) EXCEPT(SW_WRONG_LENGTH);
×
NEW
252
  return admin_platform_kbd_keymap_clear();
×
253
}
254

255
static int admin_factory_reset(const CAPDU *capdu, RAPDU *rapdu) {
4✔
256
  int ret;
257
  if (P1 != 0x00) EXCEPT(SW_WRONG_P1P2);
4✔
258
  if (LC != 5) EXCEPT(SW_WRONG_LENGTH);
4✔
259
  if (memcmp_s(DATA, "RESET", 5) != 0) EXCEPT(SW_WRONG_DATA);
4✔
260
#ifndef FUZZ
261
  ret = pin_get_retries(&pin);
4✔
262
  if (ret > 0) EXCEPT(SW_CONDITIONS_NOT_SATISFIED);
4✔
263

264
  if (is_nfc()) EXCEPT(SW_CONDITIONS_NOT_SATISFIED);
3✔
265
  if (strong_user_presence_test() < 0) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED);
3✔
266
#endif
267

268
  DBG_MSG("factory reset begins\n");
3✔
269
  ret = openpgp_install(1);
3✔
270
  if (ret < 0) return ret;
3✔
271
  ret = piv_install(1);
3✔
272
  if (ret < 0) return ret;
3✔
273
  ret = oath_install(1);
3✔
274
  if (ret < 0) return ret;
3✔
275
  ret = ctap_install(1);
3✔
276
  if (ret < 0) return ret;
3✔
277
#if ENABLE_APPLET_NDEF
278
  ret = ndef_install(1);
3✔
279
  if (ret < 0) return ret;
3✔
280
#endif
281
  ret = pass_install(1);
3✔
282
  if (ret < 0) return ret;
3✔
283
  ret = admin_install(1);
3✔
284
  if (ret < 0) return ret;
3✔
285

286
  return 0;
3✔
287
}
288

289
void device_config_fill_serial(uint8_t *buf) {
290
  if (admin_platform_serial_read(buf) < 0) memset(buf, 0, 4);
454✔
291
}
454✔
292

293
int admin_process_apdu(const CAPDU *capdu, RAPDU *rapdu) {
294
  LL = 0;
315✔
295
  SW = SW_NO_ERROR;
315✔
296

297
  int ret = 0;
315✔
298
  switch (INS) {
315✔
299
  case ADMIN_INS_SELECT:
49✔
300
    if (P1 != 0x04 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
49✔
301
    return 0;
49✔
302

303
  case ADMIN_INS_READ_VERSION:
4✔
304
    if (P1 > 1 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
4✔
305
    if (P1 == 0)
4✔
306
      ret = admin_vendor_version(capdu, rapdu);
2✔
307
    else if (P1 == 1)
2✔
308
      ret = admin_vendor_hw_variant(capdu, rapdu);
2✔
309
    goto done;
4✔
310

311
  case ADMIN_INS_READ_SN:
4✔
312
    if (P1 > 1 || P2 != 0x00) EXCEPT(SW_WRONG_P1P2);
4✔
313
    if (P1 == 0)
4✔
314
      ret = admin_read_sn(capdu, rapdu);
2✔
315
    else if (P1 == 1)
2✔
316
      ret = admin_vendor_hw_sn(capdu, rapdu);
2✔
317
    goto done;
4✔
318

319
  case ADMIN_INS_NFC_ENABLE:
×
320
    ret = admin_vendor_nfc_enable(capdu, rapdu, pin.is_validated);
×
321
    goto done;
×
322

323
  case ADMIN_INS_FACTORY_RESET:
4✔
324
    ret = admin_factory_reset(capdu, rapdu);
4✔
325
    goto done;
4✔
326

327
  case ADMIN_INS_VERIFY:
112✔
328
    ret = admin_verify(capdu, rapdu);
112✔
329
    goto done;
112✔
330

331
  default:
142✔
332
    break;
142✔
333
  }
334

335
#ifndef FUZZ
336
  if (!pin.is_validated) EXCEPT(SW_SECURITY_STATUS_NOT_SATISFIED);
142✔
337
#endif
338

339
  switch (INS) {
96✔
340
  case ADMIN_INS_WRITE_FIDO_PRIVATE_KEY:
2✔
341
    ret = ctap_install_private_key(capdu, rapdu);
2✔
342
    break;
2✔
343
  case ADMIN_INS_WRITE_FIDO_CERT:
2✔
344
    ret = ctap_install_cert(capdu, rapdu);
2✔
345
    break;
2✔
346
  case ADMIN_INS_RESET_OPENPGP:
1✔
347
    ret = openpgp_install(1);
1✔
348
    break;
1✔
349
  case ADMIN_INS_RESET_PIV:
1✔
350
    ret = piv_install(1);
1✔
351
    break;
1✔
352
  case ADMIN_INS_RESET_OATH:
1✔
353
    ret = oath_install(1);
1✔
354
    break;
1✔
355
  case ADMIN_INS_RESET_NDEF:
3✔
356
#if ENABLE_APPLET_NDEF
357
    ret = ndef_install(1);
3✔
358
#else
359
    EXCEPT(SW_INS_NOT_SUPPORTED);
360
#endif
361
    break;
3✔
362
  case ADMIN_INS_TOGGLE_NDEF_READ_ONLY:
22✔
363
#if ENABLE_APPLET_NDEF
364
    ret = ndef_toggle_read_only(capdu, rapdu);
22✔
365
#else
366
    EXCEPT(SW_INS_NOT_SUPPORTED);
367
#endif
368
    break;
22✔
369
  case ADMIN_INS_RESET_PASS:
×
370
#if ENABLE_PASS
371
    ret = pass_install(1);
×
372
#else
373
    EXCEPT(SW_INS_NOT_SUPPORTED);
374
#endif
375
    break;
×
376
  case ADMIN_INS_RESET_CTAP:
×
377
    ret = ctap_install(1);
×
378
    break;
×
379
  case ADMIN_INS_READ_CTAP_SM2_CONFIG:
×
380
    ret = ctap_read_sm2_config(capdu, rapdu);
×
381
    break;
×
382
  case ADMIN_INS_WRITE_CTAP_SM2_CONFIG:
×
383
    ret = ctap_write_sm2_config(capdu, rapdu);
×
384
    break;
×
385
  case ADMIN_INS_CHANGE_PIN:
7✔
386
    ret = admin_change_pin(capdu, rapdu);
7✔
387
    break;
7✔
388
  case ADMIN_INS_WRITE_SN:
1✔
389
    ret = admin_write_sn(capdu, rapdu);
1✔
390
    break;
1✔
391
  case ADMIN_INS_CONFIG:
18✔
392
    ret = admin_config(capdu, rapdu);
18✔
393
    break;
18✔
394
  case ADMIN_INS_FLASH_USAGE:
2✔
395
    ret = admin_flash_usage(capdu, rapdu);
2✔
396
    break;
2✔
397
  case ADMIN_INS_READ_CONFIG:
16✔
398
    ret = admin_read_config(capdu, rapdu);
16✔
399
    break;
16✔
400
  case ADMIN_INS_READ_PASS_CONFIG:
6✔
401
#if ENABLE_PASS
402
    ret = pass_read_config(capdu, rapdu);
6✔
403
#else
404
    EXCEPT(SW_INS_NOT_SUPPORTED);
405
#endif
406
    break;
6✔
407
  case ADMIN_INS_WRITE_PASS_CONFIG:
12✔
408
#if ENABLE_PASS
409
    ret = pass_write_config(capdu, rapdu);
12✔
410
#else
411
    EXCEPT(SW_INS_NOT_SUPPORTED);
412
#endif
413
    break;
12✔
NEW
414
  case ADMIN_INS_WRITE_KBD_KEYMAP:
×
NEW
415
    ret = admin_write_kbd_keymap(capdu, rapdu);
×
NEW
416
    break;
×
NEW
417
  case ADMIN_INS_READ_KBD_KEYMAP:
×
NEW
418
    ret = admin_read_kbd_keymap(capdu, rapdu);
×
NEW
419
    break;
×
NEW
420
  case ADMIN_INS_CLEAR_KBD_KEYMAP:
×
NEW
421
    ret = admin_clear_kbd_keymap(capdu, rapdu);
×
NEW
422
    break;
×
423
  case ADMIN_INS_VENDOR_SPECIFIC:
1✔
424
    ret = admin_vendor_specific(capdu, rapdu);
1✔
425
    break;
1✔
426
  default:
1✔
427
    EXCEPT(SW_INS_NOT_SUPPORTED);
1✔
428
  }
429

430
done:
219✔
431
  if (ret < 0) EXCEPT(SW_UNABLE_TO_PROCESS);
219✔
432
  return 0;
219✔
433
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc