419 Commits

Author SHA1 Message Date
Pol Henarejos
31991a31c3 Fix MSOS/BOS descriptor.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-03 16:34:49 +01:00
Pol Henarejos
fcc9b49799 Do not debug in ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-02 14:39:43 +01:00
Pol Henarejos
cf40b8dff8 Fix OTP button press in ESP32.
Fixes #208

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-02 14:39:01 +01:00
Pol Henarejos
1f5e106f22 Set anti-rollback version only when the binary is signed.
Fixes #207

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-02 09:57:37 +01:00
Pol Henarejos
39208c2167 Increase anti-rollback version to 2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 23:49:34 +01:00
Pol Henarejos
f5c0793a8d Add anti-rollback argument.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 23:48:55 +01:00
Pol Henarejos
abcfe6e87b Upgrade to v7.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 17:17:17 +01:00
Pol Henarejos
d0526d7de6 Update mbedtls only when necessary.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 17:17:04 +01:00
Pol Henarejos
4ce816e9f6 Update mbedTLS only when necessary.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 12:15:54 +01:00
Pol Henarejos
85bd329e3b Fix on AID selection. It should support shorter AID if matches.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-01 01:45:08 +01:00
Pol Henarejos
5f45a6b75b Fix oath aid test.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-30 19:06:15 +01:00
Pol Henarejos
f97b942d11 Upgrade Pico Keys SDK to v8.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-30 18:31:19 +01:00
Pol Henarejos
93bba4fb76 Moved to pypicofido.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-18 01:04:36 +01:00
Pol Henarejos
0dc2b73de4 Add support for RP2354.
Add PHY READ.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-15 20:18:23 +01:00
Pol Henarejos
dc572bcc81 Add versions.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-15 20:18:08 +01:00
Pol Henarejos
f6a1d146e7 Merge pull request #193 from MageDelfador/esp32pr
ESP32 Optimization
2025-11-10 00:57:11 +01:00
Pol Henarejos
0d89a21be7 Fix if/else logic. Fixes #199.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-09 20:13:45 +01:00
Pol Henarejos
65194e3775 Remove debug.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-09 20:13:04 +01:00
Pol Henarejos
5b778f2e27 Fix CI/CD
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-28 10:19:48 +01:00
Pol Henarejos
b0180711e7 Fix build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-28 09:36:55 +01:00
Pol Henarejos
4bcbf7f9a9 Merge branch 'development' 2025-10-27 09:30:13 +01:00
Pol Henarejos
cf0686f857 Add template for pull requests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-27 08:57:59 +01:00
Pol Henarejos
c54a6fa6fe Add CONTRIBUTING
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-27 08:53:08 +01:00
Pol Henarejos
8b08618875 Update license models and add ENTERPRISE.md
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-26 20:45:37 +01:00
Pol Henarejos
a59cdef8e6 Merge branch 'main' into development
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>

# Conflicts:
#	pico-keys-sdk
2025-10-26 20:12:26 +01:00
Pol Henarejos
d4f2d04487 Relicense project under the GNU Affero General Public License v3 (AGPLv3)
and add the Enterprise / Commercial licensing option.

Main changes:
- Replace GPLv3 headers with AGPLv3 headers in source files.
- Update LICENSE file to the full AGPLv3 text.
- Add ENTERPRISE.md describing the dual-licensing model:
  * Community Edition: AGPLv3 (strong copyleft, including network use).
  * Enterprise / Commercial Edition: proprietary license for production /
    multi-user / OEM use without the obligation to disclose derivative code.
- Update README with a new "License and Commercial Use" section pointing to
  ENTERPRISE.md and clarifying how companies can obtain a commercial license.

Why this change:
- AGPLv3 ensures that modified versions offered as a service or deployed
  in production environments must provide corresponding source code.
- The Enterprise / Commercial edition provides organizations with an
  alternative proprietary license that allows internal, large-scale, or OEM
  use (bulk provisioning, policy enforcement, inventory / revocation,
  custom attestation, signed builds) without AGPL disclosure obligations.

This commit formally marks the first release that is dual-licensed:
AGPLv3 for the Community Edition and a proprietary commercial license
for Enterprise customers.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-26 20:10:06 +01:00
MageDelfador
522b7d5841 Update sdkconfig.defaults 2025-10-15 23:43:38 +08:00
Pol Henarejos
6b93938040 Fix warnings.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-12 18:56:14 +02:00
Pol Henarejos
898c88dc6d Migration to the new system of secure functions to derive keys based on OTP, if available, and pico_serial as a fallback. PIN is also an input vector, which defines a separated domain.
PIN is used to derive encryption key, derive session key and derive verifier. From session key is derived encryption key. As a consequence, MKEK functionalities are not necessary anymore, since key device is handled by this new set directly. Some MKEK functions are left for compatibility purposes and for the silent migration to new format.  It also applies for double_hash_pin and hash_multi, which are deprecated.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-08 00:33:23 +02:00
Pol Henarejos
51c13b0f0b Add memory leak checker.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-07 23:41:58 +02:00
Pol Henarejos
d424f0dea7 Add sanity check.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-07 21:11:50 +02:00
Pol Henarejos
de1bf3d2d4 Add OTP security enhancements.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-10-06 14:22:23 +02:00
Pol Henarejos
85423fed85 Using new PIN format.
Now, PIN uses OTP as a seed to avoid memory dumps, when available (RP2350 / ESP32).

Related with #187.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-28 20:29:06 +02:00
Pol Henarejos
6c85421eca Using new PIN format.
Now, PIN uses OTP as a seed to avoid memory dumps, when available (RP2350 / ESP32).

Related with #187.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-28 20:28:04 +02:00
Pol Henarejos
3e9d1a4eb4 Fix silent authentication with resident keys.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-28 00:05:25 +02:00
Pol Henarejos
c6dba5df43 Fix silent authentication with new resident key system.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-27 23:52:08 +02:00
Pol Henarejos
eae22a97fb Fix conditional build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-23 17:17:01 +02:00
Pol Henarejos
1b8ee2fc87 Fix missing files.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-23 17:03:53 +02:00
Pol Henarejos
7d97b21ca4 Update Pico Keys SDK.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-23 17:00:10 +02:00
Pol Henarejos
665f029593 Fix build for non-pico boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-22 23:41:55 +02:00
Pol Henarejos
78de56f0a9 Fix build for non-pico boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-22 23:36:05 +02:00
Pol Henarejos
b25e4bed6c Fix build for non-pico boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-22 23:35:55 +02:00
Pol Henarejos
56b6b4a8b9 Vendor Config cmds have to be < 0x8000000000000000
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-21 01:23:02 +02:00
Pol Henarejos
9b254a0738 Add support to PIN POLICY URL via VendorConfig.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 19:20:20 +02:00
Pol Henarejos
e4f8caa1ba Add VendorConfig upload EA command to get_info().
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 18:20:36 +02:00
Pol Henarejos
7e720e8c23 Enable enterprise attestation through VendorConfig.
Add a subcommand to enable through pico-tool.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 12:56:02 +02:00
Pol Henarejos
b3b3a5eecc Add other PHY commands to get_info().
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 12:23:45 +02:00
Pol Henarejos
bf484d8663 Use internal macro.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 12:16:14 +02:00
Pol Henarejos
6b636d0bf4 Fix CMD_CONFIG with VendorCmd.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 12:13:44 +02:00
Pol Henarejos
54fb02995f Add 4 pseudorandom bytes to allow indexing used by some RP entities.
Fixes #185

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-11 11:31:45 +02:00
Pol Henarejos
56d5c61044 Add compatibility of old resident key system with the new one.
Related to #184.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-06 19:14:27 +02:00
Pol Henarejos
1ac628d241 Major refactor on resident keys.
Now, credential ids have shorter and fixed length (40) to avoid issues with some servers, which have maximum credential id length constraints.

Fixes #184

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-04 21:57:53 +02:00
Pol Henarejos
48cc417546 Added support for Brainpool curves and Ed448.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-02 15:49:39 +02:00
Pol Henarejos
2919b37e9c Fix descriptor description when there are disabled interfaces.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-02 01:20:15 +02:00
Pol Henarejos
6836ffaf02 Add dummy led driver to avoid crashes in case a non-supported board is built.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 22:02:13 +02:00
Pol Henarejos
d1c61536e0 Add support for dynamic led driver.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 21:28:09 +02:00
Pol Henarejos
351242d377 Fix build for ESP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 21:27:53 +02:00
Pol Henarejos
3fe3a9d2ec Fix build for emulation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 20:50:44 +02:00
Pol Henarejos
35a043f261 Fix automatic build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 20:41:23 +02:00
Pol Henarejos
44c5ad4adb Some VIDs do not support VENDOR_CONFIG values.
Fixes #172.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-09-01 20:38:07 +02:00
Pol Henarejos
a5fd31a5d6 Upgrade to bookworm CI for fido2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-29 01:32:22 +02:00
Pol Henarejos
fdf97f5469 Upgrade tests to python-fido2 v2.0.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-29 01:20:31 +02:00
Pol Henarejos
d30ebde4f0 Upgrade tinycbor to 0.6.1
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-29 01:20:12 +02:00
Pol Henarejos
f7ba3eec38 Fix crash APDU with CBOR.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-29 01:19:54 +02:00
Pol Henarejos
66ecd6a7fc Fix uint16 endianness that affected chained RAPDU.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-29 01:17:40 +02:00
Pol Henarejos
d1dccf3762 Merge branch 'main' into development 2025-08-28 15:09:40 +02:00
Pol Henarejos
292a9e8d8a Add support for hmac-secret-mc extension.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-28 01:04:09 +02:00
Pol Henarejos
73a7856866 Add support for persistentPinUvAuthToken.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-28 00:17:57 +02:00
Pol Henarejos
2b640a5c36 Add support for FIDO 2.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-27 12:51:34 +02:00
Pol Henarejos
bf1072781b Fix build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-25 01:42:24 +02:00
Pol Henarejos
81e03cefda Fix for rp2350 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-25 01:39:41 +02:00
Pol Henarejos
5facbf61cd NK compatibility improvements.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-25 01:34:34 +02:00
Pol Henarejos
669f6041bd Do not call pico_sdk_init.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-25 01:34:05 +02:00
Pol Henarejos
db679e4143 Merge pull request #181 from sylvainpelissier/conftest_updte
Remove WindowsClient from imports
2025-08-20 17:48:38 +02:00
Sylvain
8b317042a8 Remove WindowsClient from imports 2025-08-20 13:54:06 +02:00
Pol Henarejos
71512ae61a Stick with Espressif v5.5
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-08-12 00:51:15 +02:00
Pol Henarejos
fcd29a0717 Add autobuild for RP2350.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-07-05 00:51:29 +02:00
Pol Henarejos
bb79e6d726 Fix cross build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-06-22 20:28:32 +02:00
Pol Henarejos
a9c35afda3 Fix deps build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-06-22 20:22:53 +02:00
Pol Henarejos
be2ab59cd1 Fix ESP32 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-06-22 20:12:08 +02:00
Pol Henarejos
9c28f72d17 Merge branch 'development' 2025-06-22 18:00:08 +02:00
Pol Henarejos
0518ac3655 Flash size is obtained dynamically rather than in build time. It will allow to reduce dramatically the number of builds.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-30 12:06:34 +02:00
Pol Henarejos
b4d9e8b693 Update README.md
Add link to Pico Fido2
2025-05-30 11:22:17 +02:00
Pol Henarejos
93523faf02 Fix bool build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-25 19:20:14 +02:00
Pol Henarejos
a018a7f66c Update pointer to support dynamic AID
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-25 19:15:45 +02:00
Pol Henarejos
9b75c5c175 Check OpenPGP and PIV dynamically as it can be loaded separately.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-25 19:07:52 +02:00
Pol Henarejos
513642663b Move PRODUCT def to another file.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-24 14:49:15 +02:00
Pol Henarejos
e4ed703b6b Rename scan_files to scan_files_fido
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-24 14:25:33 +02:00
Pol Henarejos
91aaee5beb Force 8-digit serial number
Fixes #149.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-05-19 10:01:07 +02:00
Pol Henarejos
a61bb91824 Fix eddsa output folder.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-10 19:56:06 +02:00
Pol Henarejos
cfe1321d62 Upgrade to v6.6
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-10 18:37:48 +02:00
Pol Henarejos
2cbea57c86 Update build script to automatize EdDSA builds.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-10 18:37:09 +02:00
Pol Henarejos
b6bf2e6c66 Do not update CFG_FLAGS if slot is ChalResp.
Fixes #142

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-10 16:23:20 +02:00
Pol Henarejos
3212f95915 Fixes update OTP when LT_CHAL is enabled.
Fixes #141.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-08 18:59:50 +02:00
Pol Henarejos
21b12a7bff Define MCU for emulation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-08 18:58:49 +02:00
Pol Henarejos
c8dbc213a0 Fix EPNUM counting for ESP32. It fixes the problem of not sending KB.
Fixes #130 #138.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-04-06 18:32:33 +02:00
Pol Henarejos
0a2ee6523f Build all boards with secure boot pkey.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-31 00:53:44 +02:00
Pol Henarejos
c3ea413592 Do not return extensions if they are not requested OR are false.
Fixes #136

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-30 19:32:25 +02:00
Pol Henarejos
64f371e6e5 Despite it is described in the spec 2.1, do not return epAtt if is false, return only when it's true. It fixes a bug with Firefox and Linux that blocked the possibility to make credentials.
Fixes #129.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-30 18:13:58 +02:00
Pol Henarejos
fdd4afb993 CTAP_RESP should be 0ed before sending.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-30 18:12:18 +02:00
Pol Henarejos
fef46dc1c5 OATH Rename requires security validation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-23 23:55:50 +01:00
Pol Henarejos
23a45ac297 Rename returns error if new credential name is equal to previous.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-23 23:51:21 +01:00
Pol Henarejos
b152ff15a8 Fix challenge length calculation for LT64.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-23 23:27:52 +01:00
Pol Henarejos
751fcf0538 Fix HMAC-SHA1 calculation.
Fixes #127.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-23 23:13:21 +01:00
Pol Henarejos
4e4c28a479 Fix CONFIG_TOUCH status report.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-23 22:44:35 +01:00
Pol Henarejos
23b60beb2e When OTP interface is disabled, it also disables KEYBOARD interface to avoid incompatibilities with smart phones.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-22 23:26:19 +01:00
Pol Henarejos
37d7d7faeb OTP can flow through FIDO interface as a report type 3.
Fixes #123.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 19:05:06 +01:00
Pol Henarejos
49c0179ccf Fix swap files.
When a dynamic file is deleted, all scoped references to other dynamic files are invalidated.

Fixes #124

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 13:33:35 +01:00
Pol Henarejos
eacb8a040c Increase config_seq on swap and update.
Fixes #124.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 11:07:02 +01:00
Pol Henarejos
cb99b8f401 Fix emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 01:28:07 +01:00
Pol Henarejos
94f8d5f65f Add support for Require Touch in ChalResp OTP slots.
Fixes #123 #104

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 01:22:14 +01:00
Pol Henarejos
38d332f450 Restore led mode when finishing button press.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-19 01:19:24 +01:00
Pol Henarejos
c67f5e3a1f Fix Pico Commissioner when new fields are added. It breaks backward compatibility but ensures forward.
Fixes #118.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-17 11:39:27 +01:00
Pol Henarejos
bfb8a4cb20 Only send secp256k1 if explicitly enabled.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-11 19:28:22 +01:00
Pol Henarejos
0f5a24c9b6 Fix encoding get info with variable curves.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-11 19:19:28 +01:00
Pol Henarejos
dd207bd031 Fix emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-11 19:11:49 +01:00
Pol Henarejos
6069cf949b ES256K1 is disabled by default for compatibility. It can be enabled via Pico Commissioner.
Fixes #109.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-11 19:05:28 +01:00
Pol Henarejos
297c34914b Do not report EDDSA on get info if not supported.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-11 15:19:49 +01:00
Pol Henarejos
529a12e7a3 Only pin to core in ESP32-S3 since it is multicore.
Fixes #100

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-03-07 19:37:06 +01:00
Pol Henarejos
bdbdd92be8 Enable alwaysUv if pin is set and alwaysUv is a device options or there's current Uv in memory. It will force the prompt of a PIN.
Fixes #113.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-24 12:02:03 +01:00
Pol Henarejos
3807e23914 Fix silent authentication with resident keys.
It requires a new silent format, so silent credentials must be reissued.

Related with #113.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-23 22:03:06 +01:00
Pol Henarejos
ce7d3ea72f Silent credential shall be mixed with RP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-23 20:22:47 +01:00
Pol Henarejos
eb857df3e1 Fix build name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-23 00:56:27 +01:00
Pol Henarejos
2842944d90 Fix commissioned values for LED.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-23 00:49:56 +01:00
Pol Henarejos
7be92f5331 Fix autobuild.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 19:57:08 +01:00
Pol Henarejos
403b26b60a Build EDDSA tests by default.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 19:07:10 +01:00
Pol Henarejos
b91ece8ec3 Add EDDSA support as a conditional build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 19:00:44 +01:00
Pol Henarejos
d54bc1b0f3 Fix ESP32 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 18:59:44 +01:00
Pol Henarejos
e2dbbe2cc3 Merge branch 'eddsa' into development 2025-02-21 18:11:43 +01:00
Pol Henarejos
8aa9d1c5a3 Fix cyw43 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 17:28:39 +01:00
Pol Henarejos
2d2814cefc Fix emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 17:08:37 +01:00
Pol Henarejos
89a9d013f0 Build cyw43 driver with RP2350.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-21 17:02:26 +01:00
Pol Henarejos
964184cd9f Upgrade to v6.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 20:15:06 +01:00
Pol Henarejos
3969fd5136 Upgrade to v6.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 15:15:16 +01:00
Pol Henarejos
01b197d8ec Fix led driver build for Pimoroni.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 15:14:42 +01:00
Pol Henarejos
8f7b52a387 Fix rename board name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 14:34:18 +01:00
Pol Henarejos
565ceb7dc4 Take led_driver on build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 14:33:37 +01:00
Pol Henarejos
b7590b12d1 Enable fastest supported clock.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 13:36:11 +01:00
Pol Henarejos
d8da775218 Add file & line to debug info.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 11:43:56 +01:00
Pol Henarejos
13c7ade20d Add support for older PCSC.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 11:19:28 +01:00
Pol Henarejos
d925e89127 Add support for ESP32-S2 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 11:17:58 +01:00
Pol Henarejos
7a1131cb1a Modify build script to build all supported boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 10:58:59 +01:00
Pol Henarejos
d169f001b6 Upgrade to Pico SDK 2.1.1
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-19 10:58:43 +01:00
Pol Henarejos
250de29c3c Added support for OATH rename.
Fixes #107.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-17 19:54:56 +01:00
Pol Henarejos
7c4a020dc1 Merge PR #7 & #8 from @imkuang.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-09 19:18:31 +01:00
Pol Henarejos
88063d5d6d Added tests for silent authentication.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-08 15:01:25 +01:00
Pol Henarejos
f43bc9701f Added support for silent authentication.
Fixes #91.

It requires FIDO22 credential protocol, meaning that old credentials have to be reissued.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-02-08 15:00:12 +01:00
Pol Henarejos
353d782970 Fix OTP command issues in Linux.
Fixes #96.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-31 12:01:29 +01:00
Pol Henarejos
cdd2f486aa Added phy_save() and phy_load() to save and load PHY.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-29 17:09:47 +01:00
Pol Henarejos
a381e94dda Added phy_save() and phy_load() to save and load PHY.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-29 17:07:03 +01:00
Pol Henarejos
e78ec82435 Do not init PHY on modifying a single value.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-29 16:58:49 +01:00
Pol Henarejos
584d2f3b33 Add option to keep the LED steady.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-29 16:27:45 +01:00
Pol Henarejos
18676990cb Fix USB keyboard descriptor in Windows.
Fixes #97.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-29 13:22:21 +01:00
Pol Henarejos
ed9c46ded0 Fix slot deletion.
Fixes #89.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-19 19:55:16 +01:00
Pol Henarejos
d6a060f214 Upgrade to v6.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-15 15:38:55 +01:00
Pol Henarejos
6265992162 Upgrade to v6.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-15 15:23:29 +01:00
Pol Henarejos
63b7b9b8d2 Merge branch 'development' 2025-01-15 15:13:14 +01:00
Pol Henarejos
8db06bf3ac Add rollback version to 1.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-15 15:12:28 +01:00
Pol Henarejos
77dd1c4b98 Fix OTP/MKEK secure system.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-08 17:25:04 +01:00
Pol Henarejos
6a67800057 Add support for PIN hash storage and MKEK.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-01-03 01:20:58 +01:00
Pol Henarejos
a70e259a90 Use partition bounds if available.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-30 21:42:44 +01:00
Pol Henarejos
7800056597 Fix bin name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-27 02:23:24 +01:00
Pol Henarejos
eeecf513cb Fix bin name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-27 02:23:11 +01:00
Pol Henarejos
9b0b584c14 Add nightly build of esp32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-27 02:11:53 +01:00
Pol Henarejos
1c45685926 Add nightly build of esp32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-27 02:11:31 +01:00
Pol Henarejos
cff544b485 Fix TX/RX buffers to align them with USB buffers and avoid overflows.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-24 02:06:50 +01:00
Pol Henarejos
1f805b1df2 Use more uint16 funcs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-23 21:25:46 +01:00
Pol Henarejos
1d20321d69 Add BE/LE functions to pack uint16, uint32 and uint64.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-23 20:51:09 +01:00
Pol Henarejos
b42a664ac6 Add support for displaying memory usage via "pico-fido-tool.py memory" command.
Fixes #82.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-23 19:56:13 +01:00
Pol Henarejos
2d356a315e Increase TinyUSB stack size for ESP32 boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-23 19:54:11 +01:00
Pol Henarejos
9bfbc45f84 Add support for variable USB product name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-18 20:18:41 +01:00
Pol Henarejos
a5a0f3508c Remove NFC references.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-17 11:58:39 +01:00
Pol Henarejos
9c9074c1ef Do not debug after write the buffer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-16 18:43:04 +01:00
Pol Henarejos
022503fdc0 In pure U2F mode, no keepalive is sent by authenticator. Instead, client sends commands to know the status. Fixes #72.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-11 22:36:41 +01:00
Pol Henarejos
dba805dc04 Fix potential overflow due to bad initialization. Might fix #72.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-11 21:58:48 +01:00
Pol Henarejos
bbf474811b Add sanity checks.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-11 21:58:25 +01:00
Pol Henarejos
2eca08161d ESP32-S3 only supports 4 IN endpoints. Fixes #77.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-11 12:15:00 +01:00
Pol Henarejos
46ada2c1f7 Add support for tinyusb 0.17 in ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-01 01:24:01 +01:00
Pol Henarejos
5faab169a8 Add option to disable power cycle on reset via Commissioner.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-12-01 01:07:33 +01:00
Pol Henarejos
3148649f86 Fix RP2350 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 23:48:35 +01:00
Pol Henarejos
3c40706aae Fix ESP32 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 22:59:08 +01:00
Pol Henarejos
4a64c11740 Add support for Pico SDK 2.1.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 22:44:22 +01:00
Pol Henarejos
2319abe44e Merge branch 'main' into development 2024-11-25 13:14:55 +01:00
Pol Henarejos
a5fe9b5d47 Build for Pico SDK 2.0.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 13:14:45 +01:00
Pol Henarejos
d5af2cd8ed Remove ENABLE_UP_BUTTON macro.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 12:59:25 +01:00
Pol Henarejos
e994078790 Add UP button timeout to PHY.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 12:59:12 +01:00
Pol Henarejos
d99bcc90ec Add CCID SET_CLOCK_AND_FREQUENCY command for latest IFD version.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-25 12:56:29 +01:00
Pol Henarejos
c443dec4a0 Upgrade to version 6.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 01:50:22 +01:00
Pol Henarejos
7a59b51849 Upgrade to v6.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 01:21:51 +01:00
Pol Henarejos
10c58b4be7 Update README
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 01:20:52 +01:00
Pol Henarejos
730e76af75 Enable OTP master key for ESP32-S3.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 01:07:31 +01:00
Pol Henarejos
ee80462a4a Merge branch 'development'
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 00:51:52 +01:00
Pol Henarejos
4ecb325e07 Upgrade Pico Keys SDK v7.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-10 00:50:27 +01:00
Pol Henarejos
646b423fe4 Add compiler flags for optimized builds in ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-09 00:24:47 +01:00
Pol Henarejos
77c3568885 Add PICO_PRODUCT.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-09 00:23:04 +01:00
Pol Henarejos
3b43c5112b Add command to reset device via management app.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-08 19:33:40 +01:00
Pol Henarejos
244c18fb51 Fix esp32 build with wcid.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-06 17:11:44 +01:00
Pol Henarejos
78604f820d Always enable WCID interface.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-06 17:02:51 +01:00
Pol Henarejos
a68fbd65e9 Compact PHY config.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 18:57:28 +01:00
Pol Henarejos
bc0e022d85 Fix version header.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 18:37:11 +01:00
Pol Henarejos
3fad6baf89 Rename CCID_ code names to PICOKEY_
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 18:21:42 +01:00
Pol Henarejos
df2977e6ad Add rescue app.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 18:21:11 +01:00
Pol Henarejos
1fbf3da4f5 Fix usb initialization for emulation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 09:43:07 +01:00
Pol Henarejos
4ce6b2df5c Refactor PHY to support more flexible and scalable architecture.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 00:29:58 +01:00
Pol Henarejos
e5910b1cba Enable WCID by default.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-05 00:29:32 +01:00
Pol Henarejos
0df1330f92 Add support for commissioning.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-11-04 18:25:42 +01:00
Pol Henarejos
3ce8496faa Update workflows.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-10-04 17:53:59 +02:00
Pol Henarejos
ef49560d0a Fix nightly build
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-10-02 11:55:34 +02:00
Pol Henarejos
53ed3a46c4 Add autobuild for local.
Harmonize with other repos.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-10-01 09:34:22 +02:00
Pol Henarejos
dc07653ae7 Fix emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-27 21:00:39 +02:00
Pol Henarejos
2d09a5c8e5 Added support to configure LED GPIO, LED brightness and LED dimming.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-27 20:56:33 +02:00
Pol Henarejos
720c2e45f3 Add support to LED_GPIO and LED_BTNESS vendor options.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-27 20:21:03 +02:00
Pol Henarejos
aeea3c7183 Fix ESP & emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 19:40:29 +02:00
Pol Henarejos
8838ac9e54 Improve led driver support.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 19:29:08 +02:00
Pol Henarejos
623db840d3 Fix autobuild picotool
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 15:36:45 +02:00
Pol Henarejos
e2b06b908e Do not add SHA to filename, since it not will be able to rm.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 15:16:57 +02:00
Pol Henarejos
b9e791ca90 Fix nightly build
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 13:49:20 +02:00
Pol Henarejos
ed560f10a4 Install picotool
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 13:24:44 +02:00
Pol Henarejos
1f839c5f99 Append sha to nightly builds.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 13:24:29 +02:00
Pol Henarejos
effb8e4063 Fix build for WS2812 boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 12:01:55 +02:00
Pol Henarejos
b2e45b0f7f Fix build for boards with WS2812.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 11:33:29 +02:00
Pol Henarejos
24521dff4b Add nightly builds to main
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 11:25:21 +02:00
Pol Henarejos
7bc4a70319 Fix nightly build
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 11:20:43 +02:00
Pol Henarejos
cbef14beec Add manual trigger to workflows
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 11:09:34 +02:00
Pol Henarejos
0e54998d58 Add nightly deploy workflow
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-25 11:09:13 +02:00
Pol Henarejos
2e16036bb5 Update pico_sdk_import
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-24 00:44:58 +02:00
Pol Henarejos
f98df743f9 Upgrade CodeQL to v3
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 20:27:00 +02:00
Pol Henarejos
4fe1c0804c Add set target to ESP32-S3
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 20:12:52 +02:00
Pol Henarejos
7071949a1f More fixes
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 19:55:18 +02:00
Pol Henarejos
e07b5194e3 Fix again...
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 19:47:27 +02:00
Pol Henarejos
e05115ffac Fix autobuild for ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 19:37:01 +02:00
Pol Henarejos
38eca2fdd4 Fix permissions.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 19:30:03 +02:00
Pol Henarejos
f276e99342 Add autobuild for ESP32
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 19:26:04 +02:00
Pol Henarejos
6f517e8fca Fix header in Linux. Fixes #63
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-19 18:26:04 +02:00
Pol Henarejos
39e2ff40c3 Add support for dynamic VIDPID via PHY.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-18 19:44:02 +02:00
Pol Henarejos
ffbe3fcbad Add OTP support and sha256 hardware acceleration.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-18 19:43:54 +02:00
Pol Henarejos
cf5dbc9ae5 Add support for dynamic VIDPID via PHY.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-18 19:42:14 +02:00
Pol Henarejos
2fca44540a Add sha256 hardware accelerator.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-13 21:04:21 +02:00
Pol Henarejos
ec612a451d Fix ssh-keygen creation.
Fixes #59

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-13 21:03:58 +02:00
Pol Henarejos
c43006f8c2 Protect keydev if available (only for RP2350).
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-12 19:01:04 +02:00
Pol Henarejos
8ae4ab5af4 Upgrade to version 5.12
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-02 20:21:58 +02:00
Pol Henarejos
95cae29206 Upgrade to version 5.12
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-02 17:12:11 +02:00
Pol Henarejos
11c28adbb0 Add more boards with RP2350.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-02 17:11:57 +02:00
Pol Henarejos
661442956d Update readme to add Passkey term.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-02 12:02:42 +02:00
Pol Henarejos
778c6b038a Fix BOOT press with RP2350.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-09-02 09:48:27 +02:00
Pol Henarejos
de1c50db4f Replace sdkconfig
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-30 14:47:49 +02:00
Pol Henarejos
c1e985c9af Use mutex/semaphores for emulation, like in Pico and ESP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-30 12:42:33 +02:00
Pol Henarejos
4f787eaaba Fix otp in Pico
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-30 00:34:14 +02:00
Pol Henarejos
b77277b72e Add RP2350 support.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-29 16:57:59 +02:00
Pol Henarejos
02556fcde1 Fix buffer initialization.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-25 20:21:43 +02:00
Pol Henarejos
f234b0dc26 Fix emulation run
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-25 01:31:19 +02:00
Pol Henarejos
8ba9116454 Fix test
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-25 01:30:54 +02:00
Pol Henarejos
5a31405244 Improving tests
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-25 00:10:23 +02:00
Pol Henarejos
902a988350 Fix memory cleanups.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-24 02:34:15 +02:00
Pol Henarejos
6256a9547d Fix build emulation
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-24 00:11:40 +02:00
Pol Henarejos
5568aa7b69 Fixed thread synchronization.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 19:25:20 +02:00
Pol Henarejos
5e86745672 Add missing files for ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 15:23:10 +02:00
Pol Henarejos
cffa8e29ff Fix windows build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 14:24:03 +02:00
Pol Henarejos
6c74db9763 Fix warnings.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 13:17:51 +02:00
Pol Henarejos
dac6407134 Fix windows build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 13:17:29 +02:00
Pol Henarejos
f49833291f Major refactor of USB CCID and USB HID interfaces.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-23 10:04:00 +02:00
Pol Henarejos
8c1e002892 select_app now invokes U2F or FIDO depending on the message.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-20 14:29:25 +02:00
Pol Henarejos
8d49ed5ffc Fix potential crash invoking OTP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-20 14:28:09 +02:00
Pol Henarejos
a0d9ad7a3a Increase vStack depending on the number of interfaces.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-20 12:43:15 +02:00
Pol Henarejos
d2c25b69bc Merge branch 'main' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-20 10:18:08 +02:00
Pol Henarejos
910fb66f3c Fix keepalive
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 16:45:11 +02:00
Pol Henarejos
ed12d6f8e9 Fix emulation build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 13:18:03 +02:00
Pol Henarejos
a9799dc77f Fix CBOR error.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 13:12:04 +02:00
Pol Henarejos
d7d75caecf Fix OATH selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 13:11:48 +02:00
Pol Henarejos
af4eb075c7 Add HID/CCID fixes for ESP32.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 00:09:05 +02:00
Pol Henarejos
0c5280e12a Add support to ESP32 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-19 00:08:31 +02:00
Pol Henarejos
163e936231 Fix potential bug in CBOR encoding.
It happen if a keepalive packet is sent in the middle of an encoding.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-18 23:59:52 +02:00
Pol Henarejos
1b4dd9bed0 Fix ESP32 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-08-18 23:53:18 +02:00
Pol Henarejos
5b95e35ca9 Upgrade to version 5.10
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:29:40 +02:00
Pol Henarejos
69ec242095 Update README.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:28:09 +02:00
Pol Henarejos
6eb6cd35d0 Merge branch 'development'
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:27:01 +02:00
Pol Henarejos
f21e203093 Fix compilation
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:05:00 +02:00
Pol Henarejos
e96da09a84 Fixes for mbedtls 3.6
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:04:48 +02:00
Pol Henarejos
6fe16a63e4 Upgrade Pico Keys SDK
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-07-20 20:04:41 +02:00
Pol Henarejos
d5fe405a87 Fix test bad pub type.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-06-30 00:32:40 +02:00
Pol Henarejos
54bbc0e9ea Fix return value when bad key type is provided. Fixes #47.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-06-30 00:31:29 +02:00
Pol Henarejos
b0b0187919 Fix cleared permissions on make credential when UP is not present.
Following 14.1, flags shall be cleared only when UP == true.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-06-07 20:57:21 +02:00
Pol Henarejos
1f0e1fb8f4 Use latest Pico Keys SDK.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-05-05 00:58:51 +02:00
Pol Henarejos
f3f34cf66b Fix oath crash.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-03-13 22:06:00 +01:00
Pol Henarejos
82ed96b2e2 Fix asn1 struct initialization.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-03-13 21:22:05 +01:00
Pol Henarejos
92d04f9131 Use new asn1 structs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2024-03-13 18:34:14 +01:00
Pol Henarejos
7a71bf48fc Add -DVIDPID=<VALUE> to build a project with a known VID/PID. Supported values: NitroHSM, NitroFIDO2, NitroStart, NitroPro, Nitro3, Yubikey5, YubikeyNeo, YubiHSM, Gnuk, GnuPG
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-12-11 18:13:32 +01:00
Pol Henarejos
21765a6f10 Move pico-keys-sdk pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 13:10:58 +01:00
Pol Henarejos
eb2c92bc5c Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 13:01:10 +01:00
Pol Henarejos
7e2ecdbc56 Upgrade to version 5.8
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
e54df525c4 Removing SHORT_TICKET limitation.
It is not used to return the half of ticket, but to combine with static to produce hex scancodes.

Fixes #29.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
1d9107d4bb OTP callbacks must be initialized on ctor.
Fixes #30.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
a9be759da3 OTP static passwords are 38 bytes length.
A static password uses fixed, uid and key fields (sum 38). However, Yubikey sets short_ticket flag which implies the half of the password is sent.

Fixes #29.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
34bfc3b2ef otp must be initialized when selection fido or management applets.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
d985cf6301 Moving Pico Keys SDK pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
0b00e01187 Fix build in emulation mode.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
bef1922c8f Use new names and defines.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
107e5c34db Use new pico-keys-sdk submodule name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
6157a91fdf Rename old pico-hsm-sdk to the new pico-keys-sdk.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
779db90713 Move some functions from HID to fido callbacks.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
a0f1d2334d Use get_version_major and get_version_minor as pointers.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
5c6f87ab8f Update SDK to new otp.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
cf152c1692 Move some OTP functions from HID to OTP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
04238509ee Generate a secure key if it is not found.
Should fix #23.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
85298062cd python-fido2 has a bug which does not allow to use 0xff as ConfigVendorPrototype.
It encodes an uint8_t to int8_t and thus, the command must be <= 0x7f.

Fixes #22.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
0464ad8964 Fixed AUT permission.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
19197e54a8 Added support for --pin flag.
It loads Vendor/Ctap2Vendor with uv_token based on provided --pin.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
01a6c9f77f Added Windows & Linux backend for backup/restore.
Fixes #21

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
ba57cc4527 Fixed OTP read packet through HID interfaces.
Fixes #19.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
68b5614fb9 Fixed potential crash.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
4fd4d75e21 Fixed potential memory leak.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
599fd706ce Fix AID selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:31 +01:00
Pol Henarejos
28e979939a Adapted to new selection AID method.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:30 +01:00
Pol Henarejos
849221fd95 Added backfall compatibility.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:30 +01:00
Pol Henarejos
011429a982 Update to latest HSM SDK changes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:30 +01:00
Pol Henarejos
b99181a00c Fix pico_w build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:30 +01:00
Pol Henarejos
041bb788f9 Added support for LED in Pico W.
Fixed #17.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:02:30 +01:00
Pol Henarejos
20a8ef08f0 Upgrade to version 5.8
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 12:01:47 +01:00
Pol Henarejos
e757ad2945 Removing SHORT_TICKET limitation.
It is not used to return the half of ticket, but to combine with static to produce hex scancodes.

Fixes #29.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 11:53:47 +01:00
Pol Henarejos
1ce0d98c34 OTP callbacks must be initialized on ctor.
Fixes #30.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-21 11:50:42 +01:00
Pol Henarejos
96de6efed6 OTP static passwords are 38 bytes length.
A static password uses fixed, uid and key fields (sum 38). However, Yubikey sets short_ticket flag which implies the half of the password is sent.

Fixes #29.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-16 20:16:23 +01:00
Pol Henarejos
195096ad52 otp must be initialized when selection fido or management applets.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-16 20:12:48 +01:00
Pol Henarejos
1ee86f8634 Moving Pico Keys SDK pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-16 20:12:01 +01:00
Pol Henarejos
2b9a5829e5 Merge pull request #26 from sylvainpelissier/patch-1
Update pico-fido-patch-vidpid.sh
2023-11-08 14:17:49 +01:00
Sylvain Pelissier
8056e64cab Update pico-fido-patch-vidpid.sh
Match any previous VID:PID in the image and replace with the new ones.
2023-11-08 13:56:37 +01:00
Pol Henarejos
ffb3beb84a Fix build in emulation mode.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 15:32:25 +01:00
Pol Henarejos
d78d9d10aa Use new names and defines.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 15:22:28 +01:00
Pol Henarejos
f8d4f1d02e Use new pico-keys-sdk submodule name.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 14:28:09 +01:00
Pol Henarejos
b493a81ddc Rename old pico-hsm-sdk to the new pico-keys-sdk.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 14:27:57 +01:00
Pol Henarejos
5c20909b03 Move some functions from HID to fido callbacks.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 13:01:10 +01:00
Pol Henarejos
27b9e3954a Use get_version_major and get_version_minor as pointers.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 11:57:08 +01:00
Pol Henarejos
440ec5c854 Update SDK to new otp.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 11:49:42 +01:00
Pol Henarejos
cb2744cab3 Move some OTP functions from HID to OTP.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 11:49:18 +01:00
Pol Henarejos
5db1014850 Generate a secure key if it is not found.
Should fix #23.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-06 11:48:32 +01:00
Pol Henarejos
421bea6421 python-fido2 has a bug which does not allow to use 0xff as ConfigVendorPrototype.
It encodes an uint8_t to int8_t and thus, the command must be <= 0x7f.

Fixes #22.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-02 22:29:28 +01:00
Pol Henarejos
65039c0959 Fixed AUT permission.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-02 22:13:45 +01:00
Pol Henarejos
8e36b4c379 Added support for --pin flag.
It loads Vendor/Ctap2Vendor with uv_token based on provided --pin.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-02 22:08:49 +01:00
Pol Henarejos
3652368542 Added Windows & Linux backend for backup/restore.
Fixes #21

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-11-02 09:32:19 +01:00
Pol Henarejos
e5d1ef29a4 Fixed OTP read packet through HID interfaces.
Fixes #19.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-31 17:35:59 +01:00
Pol Henarejos
0fd36806cc Fixed potential crash.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-31 00:40:56 +01:00
Pol Henarejos
7bf26b28fc Fixed potential memory leak.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-30 16:51:56 +01:00
Pol Henarejos
da94a82487 Fix AID selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-28 20:57:53 +02:00
Pol Henarejos
c24be5a631 Adapted to new selection AID method.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-28 20:53:06 +02:00
Pol Henarejos
46ce9390bf Added backfall compatibility.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-28 20:52:07 +02:00
Pol Henarejos
c1fd5736f9 Update to latest HSM SDK changes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-10-28 20:51:36 +02:00
Pol Henarejos
b1c4ff877e Fix pico_w build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 10:39:21 +02:00
Pol Henarejos
6c85d57412 Added support for LED in Pico W.
Fixed #17.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 10:14:11 +02:00
Pol Henarejos
233c5a7c7d Merge branch 'development' into eddsa 2023-09-18 09:33:56 +02:00
Pol Henarejos
3b4ac12d0f Merge branch 'development' into eddsa 2023-09-18 09:02:26 +02:00
Pol Henarejos
2e5b8f4c71 Upgrade to version 5.6 2023-09-18 09:01:14 +02:00
Pol Henarejos
a9697ba4e0 Upgrade to version 5.6
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 08:57:31 +02:00
Pol Henarejos
aec488f070 Revert "Upgrade to version 5.6"
This reverts commit 45c2cf65fe.
2023-09-18 08:56:43 +02:00
Pol Henarejos
7c5bab8b05 Merge branch 'development' into eddsa 2023-09-18 01:38:39 +02:00
Pol Henarejos
21035d649d Upgrade to version 5.7
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 01:38:31 +02:00
Pol Henarejos
9c90095e96 CBOR errors are not sent through CTAPHID_ERROR command, but in CBOR response instead. Fixes #16
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 01:36:47 +02:00
Pol Henarejos
7c5f2cee4b Do not throw error if not supported but valid algorithm is provided. Just ignore it.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 01:35:13 +02:00
Pol Henarejos
5e0c42a9f9 Use hexa representation for error displaying
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 01:34:47 +02:00
Pol Henarejos
da7b918dc4 Added RS algorithms though are not supported.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-18 01:34:34 +02:00
Pol Henarejos
abe91823c0 Build firmwares with -eddsa1 suffix.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-17 19:29:54 +02:00
Pol Henarejos
91e049b997 Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-17 19:28:41 +02:00
Pol Henarejos
cfcfb941e0 Merge 5.6 changes. 2023-09-17 19:13:43 +02:00
Pol Henarejos
45c2cf65fe Upgrade to version 5.6
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-17 19:13:07 +02:00
Pol Henarejos
1217d82361 Add support for newer boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-17 19:12:57 +02:00
Pol Henarejos
332debea6d Code style.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-09-17 19:11:39 +02:00
Pol Henarejos
8836902dc1 Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 15:32:10 +02:00
Pol Henarejos
bafede2ae5 Add supported curves to README.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 15:31:30 +02:00
Pol Henarejos
a019b54d69 Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:27:35 +02:00
Pol Henarejos
539420b996 Added ES256K tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:23:56 +02:00
Pol Henarejos
0c08590dcc Added support for ES256K tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:23:42 +02:00
Pol Henarejos
cac4ae1751 Adapted test errors to specs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:23:22 +02:00
Pol Henarejos
974868d8e4 FIDO2 Server only uses supported algorithms.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:22:38 +02:00
Pol Henarejos
2bbaf7c274 Adapted pubKeyCredParams verification and return error messages to specs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-22 13:22:16 +02:00
Pol Henarejos
3adb1a8422 Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-21 19:12:51 +02:00
Pol Henarejos
df26040838 Fix loading SECP521R1 key.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-21 19:11:44 +02:00
Pol Henarejos
95a9fe4214 Added flow triggering for eddsa branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 16:49:58 +02:00
Pol Henarejos
8af7cac57a Added authentication tests with EdDSA.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 16:48:12 +02:00
Pol Henarejos
7997eefdc8 Fixed EdDSA signature encapsulation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 16:46:55 +02:00
Pol Henarejos
e18f841a34 Fix Edwards load key.
It did not compute the correct public point.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 16:46:37 +02:00
Pol Henarejos
73b51cabfc Merge branch 'development' into eddsa 2023-08-18 14:10:58 +02:00
Pol Henarejos
539ea61436 Add get assertion test with different algorithms.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 14:10:49 +02:00
Pol Henarejos
75771e5e46 Not used.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 14:10:24 +02:00
Pol Henarejos
8e26ec8bcd Use python-fido2 from my repo, which contains some fixes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 14:10:17 +02:00
Pol Henarejos
ad3b2bbe4b Added EdDSA credential creation test.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 13:07:52 +02:00
Pol Henarejos
b9ad8f4745 Merge branch 'development' into eddsa 2023-08-18 13:07:13 +02:00
Pol Henarejos
26148282e6 Fix credential creation for ES512.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 13:07:06 +02:00
Pol Henarejos
05044b498d Added test for testing algorithms on make credential.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 13:06:51 +02:00
Pol Henarejos
8242dc8d80 Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 12:44:52 +02:00
Pol Henarejos
be44947475 Fix writing COSE key when for curves with kty=1.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 12:43:49 +02:00
Pol Henarejos
0d280ca252 Moving pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-18 12:33:30 +02:00
Pol Henarejos
2f6e4d5568 Upgraded COSE key functions to accept EDDSA.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:40:22 +02:00
Pol Henarejos
911dab031e Merge branch 'development' into eddsa
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:36:35 +02:00
Pol Henarejos
4c3042a8bf Added function for reading COSE keys.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:30:49 +02:00
Pol Henarejos
b7ceec8d49 Using COSE keys write functions.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:19:45 +02:00
Pol Henarejos
63e15b19bb Added functions for writing COSE keys.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:19:27 +02:00
Pol Henarejos
b2c4e0e1c1 Added curve to fido.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-17 01:19:13 +02:00
Pol Henarejos
b72c596aa6 Fix chained response.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 19:20:11 +02:00
Pol Henarejos
2d81a3c472 Update to pyfido2 1.1.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 19:17:13 +02:00
Pol Henarejos
bb20dd7a53 First attempt to include CBOR as CCID.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 19:16:54 +02:00
Pol Henarejos
3a71275bc8 Add EDDSA algorithm in get_info.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 18:06:29 +02:00
Pol Henarejos
c258dad8e6 Fix OTP applet selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 17:32:52 +02:00
Pol Henarejos
9f1e879efe Fix OTP applet selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 17:32:17 +02:00
Pol Henarejos
57bf97196d Updated readme.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 14:48:44 +02:00
Pol Henarejos
e8c8ce4d15 Adding support for EdDSA with Ed25519 curve.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 14:47:34 +02:00
Pol Henarejos
69d618cc6b Point to proper EdDSA branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 13:13:58 +02:00
Pol Henarejos
e057f17180 Using Pico HSM SDK EdDSA branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 13:07:01 +02:00
Pol Henarejos
ce040a79f5 Fix signature computation for algorithms ES384 and ES512.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 12:39:53 +02:00
Pol Henarejos
8ffd1bfe38 Added support for ES256K algorithm.
It uses secp256k1 curve with SHA-256.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-16 12:18:42 +02:00
Pol Henarejos
5105545df0 Added thirdPartyPayment to supported extensions.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 21:44:34 +02:00
Pol Henarejos
d011314500 Add thirdPartyPayment extension to credential manager response.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 21:44:15 +02:00
Pol Henarejos
51cbfe5fe9 Fix enabled cap detection when applet is already selected.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 21:09:54 +02:00
Pol Henarejos
aa7362f88f Fix enabled capabilities detection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 20:49:29 +02:00
Pol Henarejos
2b1227b105 Added support for management via Yubikey Manager to enable/disable specific interfaces individually.
All interfaces are enabled by default.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 19:55:17 +02:00
Pol Henarejos
a79842b33f Fix OTP slot deletion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 18:45:36 +02:00
Pol Henarejos
30f51b8453 Add Nitrokey readme support.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-14 18:44:48 +02:00
Pol Henarejos
c00c83dfe6 Added support for thirdPartyPayment extension.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-08-13 21:12:49 +02:00
Pol Henarejos
cda97259b3 Create FUNDING.yml 2023-05-17 10:22:35 +02:00
Pol Henarejos
c883083a75 Fix for mbedtls 3.4 build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-05-16 09:41:11 +02:00
Pol Henarejos
016780b3de Update pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-05-16 09:23:16 +02:00
Pol Henarejos
24224b78dd Added support to Nitrokey's nitropy tool.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2023-05-15 19:11:02 +02:00
72 changed files with 4309 additions and 2550 deletions

4
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
# These are supported funding model platforms
github: polhenarejos
custom: ["https://www.paypal.me/polhenarejos"]

50
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,50 @@
## Summary
Describe in plain language what this PR does and why.
- What problem does it solve?
- Is it a bug fix, a new feature, a cleanup/refactor…?
## Details / Impact
Please include any relevant details:
- Hardware / board(s) tested:
- Firmware / commit/base version:
- Security impact (if any):
- e.g. changes PIN handling, touches key storage, affects attestation, etc.
- Behavior changes:
- e.g. new command, new API surface, different defaults, etc.
## Testing
How did you test this change?
- Steps to reproduce / validate:
- Expected vs actual results:
- Any logs / traces (please remove secrets):
## Licensing confirmation (required)
By checking the box below, you confirm ALL of the following:
- You are the author of this contribution, or you have the right to contribute it.
- You have read `CONTRIBUTING.md`.
- You agree that this contribution may be merged, used, modified, and redistributed:
- under the AGPLv3 Community Edition, **and**
- under any proprietary / commercial / Enterprise editions of this project,
now or in the future.
- You understand that submitting this PR does not create any support obligation,
SLA, or guarantee of merge.
**I confirm the above licensing terms:**
- [ ] Yes, I agree
## Anything else?
Optional: mention known limitations, follow-ups, or if this is related to an existing Issue.

View File

@@ -13,12 +13,13 @@ name: "CodeQL"
on:
push:
branches: [ "main", "development" ]
branches: [ "main", "development", "eddsa" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main", "development" ]
branches: [ "main", "development", "eddsa" ]
schedule:
- cron: '23 5 * * 4'
workflow_dispatch:
jobs:
analyze:
@@ -35,6 +36,7 @@ jobs:
language: [ 'cpp', 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
mode: [ 'pico', 'local' ]
steps:
- name: Checkout repository
@@ -42,7 +44,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +68,7 @@ jobs:
- run: |
echo "Run, Build Application using script"
./workflows/autobuild.sh
./workflows/autobuild.sh ${{ matrix.mode }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

42
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: "Nightly deploy"
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
nightly:
name: Deploy nightly
strategy:
fail-fast: false
matrix:
refs: [main, development]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ matrix.refs }}
submodules: 'recursive'
- name: Restore private key
run: |
echo "${{ secrets.PRIVATE_KEY_B64 }}" | base64 -d > private.pem
chmod 600 private.pem
- name : Build
env:
PICO_SDK_PATH: ../pico-sdk
SECURE_BOOT_PKEY: ../private.pem
run: |
./workflows/autobuild.sh pico
./build_pico_fido.sh --no-eddsa
./workflows/autobuild.sh esp32
- name: Delete private key
run: rm private.pem
- name: Update nightly release
uses: pyTooling/Actions/releaser@main
with:
tag: nightly-${{ matrix.refs }}
rm: true
token: ${{ secrets.GITHUB_TOKEN }}
files: release/*.*

View File

@@ -13,12 +13,13 @@ name: "Emulation and test"
on:
push:
branches: [ "main", "development" ]
branches: [ "main", "development", "eddsa" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main", "development" ]
branches: [ "main", "development", "eddsa" ]
schedule:
- cron: '23 5 * * 4'
workflow_dispatch:
jobs:
build:

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "pico-hsm-sdk"]
path = pico-hsm-sdk
url = ../pico-hsm-sdk
[submodule "pico-keys-sdk"]
path = pico-keys-sdk
url = https://github.com/polhenarejos/pico-keys-sdk

View File

@@ -17,8 +17,15 @@
cmake_minimum_required(VERSION 3.13)
if(ESP_PLATFORM)
set(DENABLE_POWER_ON_RESET 0)
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
else()
if(ENABLE_EMULATION)
else()
set(PICO_USE_FASTEST_SUPPORTED_CLOCK 1)
include(pico_sdk_import.cmake)
endif()
@@ -27,21 +34,8 @@ project(pico_fido C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
if(ENABLE_EMULATION)
else()
pico_sdk_init()
endif()
add_executable(pico_fido)
option(ENABLE_UP_BUTTON "Enable/disable user presence button" ON)
if(ENABLE_UP_BUTTON)
add_definitions(-DENABLE_UP_BUTTON=1)
message(STATUS "User presence with button: \t enabled")
else()
add_definitions(-DENABLE_UP_BUTTON=0)
message(STATUS "User presence with button: \t disabled")
endif(ENABLE_UP_BUTTON)
endif()
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
if(ENABLE_POWER_ON_RESET)
@@ -72,13 +66,22 @@ endif(ENABLE_OTP_APP)
if(ENABLE_OTP_APP OR ENABLE_OATH_APP)
set(USB_ITF_CCID 1)
set(USB_ITF_WCID 1)
else()
set(USB_ITF_CCID 0)
endif()
set(USB_ITF_HID 1)
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
if(NOT ESP_PLATFORM)
set(SOURCES ${PICO_KEYS_SOURCES})
endif()
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/kek.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_register.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_authenticate.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_version.c
@@ -96,6 +99,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_large_blobs.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/management.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/defs.c
)
if (${ENABLE_OATH_APP})
set(SOURCES ${SOURCES}
@@ -108,44 +112,58 @@ set(SOURCES ${SOURCES}
)
endif()
set(USB_ITF_HID 1)
include(pico-hsm-sdk/pico_hsm_sdk_import.cmake)
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 2)
if(ESP_PLATFORM)
project(pico_fido)
endif()
set(INCLUDES ${INCLUDES}
${CMAKE_CURRENT_LIST_DIR}/src/fido
)
if(NOT ESP_PLATFORM)
target_sources(pico_fido PUBLIC ${SOURCES})
target_include_directories(pico_fido PUBLIC ${INCLUDES})
target_compile_options(pico_fido PUBLIC
-Wall
)
if (NOT MSVC)
target_compile_options(pico_fido PUBLIC
-Werror
)
string(FIND ${CMAKE_C_COMPILER} ":" COMPILER_COLON)
if (${COMPILER_COLON} GREATER_EQUAL 0)
target_compile_options(pico_fido PUBLIC
-Wno-error=use-after-free
)
endif()
endif(NOT MSVC)
if(ENABLE_EMULATION)
target_compile_options(pico_fido PUBLIC
-fdata-sections
-ffunction-sections
)
if(APPLE)
target_link_options(pico_fido PUBLIC
-Wl,-dead_strip
)
else()
target_link_options(pico_fido PUBLIC
-Wl,--gc-sections
)
target_link_libraries(pico_fido PRIVATE m)
endif (APPLE)
if(NOT MSVC)
target_compile_options(pico_fido PUBLIC
-fdata-sections
-ffunction-sections
)
endif(NOT MSVC)
if(APPLE)
target_link_options(pico_fido PUBLIC
-Wl,-dead_strip
)
if(DEBUG_APDU)
target_compile_options(pico_fido PUBLIC
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
target_link_options(pico_fido PUBLIC
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
endif()
else()
target_link_options(pico_fido PUBLIC
-Wl,--gc-sections
)
endif (APPLE)
target_link_libraries(pico_fido PRIVATE pthread m)
else()
pico_add_extra_outputs(pico_fido)
target_link_libraries(pico_fido PRIVATE pico_hsm_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id hardware_rtc tinyusb_device tinyusb_board)
target_link_libraries(pico_fido PRIVATE pico_keys_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id pico_aon_timer tinyusb_device tinyusb_board)
pico_add_extra_outputs(${CMAKE_PROJECT_NAME})
endif()
endif()

105
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,105 @@
# Contributing
Thank you for your interest in contributing to this project.
This repository is published in two forms:
- a Community Edition released under AGPLv3, and
- a proprietary / commercial / Enterprise Edition offered to organizations.
To keep that model legally clean, we need to be explicit about how contributions can be used.
By opening a pull request, you agree to all of the following:
1. **You have the right to contribute this code.**
You are either the original author of the contribution, or you have obtained the necessary rights/permissions to contribute it under these terms.
2. **Dual licensing permission.**
You agree that your contribution may be:
- merged into this repository, and
- used, copied, modified, sublicensed, and redistributed
- under the AGPLv3 Community Edition, and
- under any proprietary / commercial / Enterprise editions of this project,
now or in the future.
In other words: you are granting the project maintainer(s) the right to include
your contribution in both the open-source (AGPLv3) codebase and in closed-source /
commercially licensed builds, without any additional approval or payment.
3. **Attribution.**
The maintainers may keep or add attribution lines such as
`Copyright (c) <your name>` or an AUTHORS / CONTRIBUTORS list.
The maintainers may also make changes for clarity, style, security, refactoring,
or integration reasons.
4. **No automatic SLA.**
Submitting a pull request does *not* create any support obligation,
service-level agreement, warranty, or guarantee that the contribution
will be reviewed, merged, or maintained.
5. **Potential rejection for business reasons.**
Features that fall under "Enterprise / Commercial" functionality
(e.g. multi-tenant provisioning at scale, centralized audit trails,
corporate policy enforcement, attestation/branding flows, key escrow / dual-control,
etc.) may be declined for the public AGPLv3 tree even if technically valid.
That is normal: some functionality is intentionally offered only
under commercial terms.
If you are not comfortable with these terms, **do not open a pull request yet.**
Instead, please open an Issue to start a discussion.
## How to contribute (technical side)
### 1. Bug reports / issues
- Please include:
- hardware / board revision
- firmware / commit hash
- exact steps to reproduce
- expected vs actual behavior
- logs / traces if available (strip secrets)
Security-sensitive findings: do **not** post publicly.
Send a short report by email instead so it can be triaged responsibly.
### 2. Small fixes / minor improvements
- You can open a PR directly for:
- bug fixes
- portability fixes / new board definitions
- clarifications in code comments
- build / tooling cleanup
- documentation of existing behavior
Please keep PRs focused (one logical change per PR if possible).
### 3. Larger features / behavior changes
- Please open an Issue first and describe:
- what problem you're solving (not just "add feature X")
- impact on existing flows / security model
- any new dependencies
This helps avoid doing a bunch of work on something that won't be accepted
in the Community Edition.
### 4. Coding style / security posture
- Aim for clarity and small, auditable changes. This code runs in places
where secrets live.
- No debug backdoors, no "just for testing" shortcuts left enabled.
- Keep external dependencies minimal and license-compatible
(MIT / Apache 2.0 / similarly permissive is usually fine).
### 5. Commit / PR format
- Use descriptive commit messages ("Fix PIN retry counter wrap" is better than "fix stuff").
- In the PR description, please include a short summary of what was changed and why.
- At the bottom of the PR description, **copy/paste and confirm the licensing line below**:
> I confirm that I have read `CONTRIBUTING.md` and I agree that this contribution may be used under both the AGPLv3 Community Edition and any proprietary / commercial / Enterprise editions of this project, now or in the future.
A PR without that confirmation may be delayed or closed without merge.
## Thank you
This project exists because people build on it, break it, fix it,
and push it into places it wasn't originally designed to go.
Whether you are here for research, hacking on hardware,
rolling out secure keys for a team, or building a commercial product:
thank you for helping improve it.

116
ENTERPRISE.md Normal file
View File

@@ -0,0 +1,116 @@
# Enterprise / Commercial Edition
This project is offered under two editions:
## 1. Community Edition (FOSS)
The Community Edition is released under the GNU Affero General Public License v3 (AGPLv3).
Intended for:
- individual users and researchers
- evaluation / prototyping
- internal lab / security testing
You are allowed to:
- read and study the source code
- modify it
- run it internally
Obligations under AGPLv3:
- If you distribute modified firmware/binaries/libraries to third parties, you must provide the corresponding source code of your modifications.
- If you run a modified version of this project as a network-accessible service (internal or external), you must offer the source code of those modifications to the users of that service.
- No warranty, no support, no SLA.
- Enterprise features (bulk provisioning, multi-user policy enforcement, device inventory / revocation, corporate PIN rules, custom attestation/identity, etc.) are NOT included.
The Community Edition will continue to exist.
## 2. Enterprise / Commercial Edition
The Enterprise / Commercial Edition is a proprietary license for organizations that need to:
- deploy this in production at scale (multiple devices / multiple users / multiple teams)
- integrate it into their own physical product or appliance
- run it as an internal service (VM / container / private cloud "HSM / auth backend") for multiple internal teams or tenants
- enforce internal security policy (admin vs user roles, mandatory PIN rules, secure offboarding / revocation)
- avoid any AGPLv3 disclosure obligations for their own modifications and integration code
### What the Enterprise Edition provides
**Base license package (always included):**
- **Commercial license (proprietary).**
You may run and integrate the software/firmware in production — including virtualized / internal-cloud style deployments — without being required to disclose derivative source code under AGPLv3.
- **Official signed builds.**
You receive signed builds from the original developer so you can prove integrity and provenance.
- **Onboarding call (up to 1 hour).**
A live remote session to get you from "we have it" to "its actually running in our environment" with minimal guesswork.
**Optional enterprise components (available on demand, scoped and priced per customer):**
- **Production / multi-user readiness.**
Permission to operate the system with multiple users, multiple devices and multiple teams in real environments.
- **Bulk / fleet provisioning.**
Automated enrollment for many tokens/devices/users at once (CSV / directory import), scripted onboarding of new users, initial PIN assignment / reset workflows, and role-based access (admin vs user).
- **Policy & lifecycle tooling.**
Corporate PIN policy enforcement, per-user / per-team access control, device inventory / traceability, and secure revocation / retirement when someone leaves.
- **Custom attestation / per-organization identity.**
Per-company certificate chains and attestation keys so devices can prove "this token/HSM is officially ours," including anti-cloning / unique device identity for OEM and fleet use.
- **Virtualization / internal cloud deployment support.**
Guidance and components to run this as an internal service (VM, container, private-cloud HSM/auth backend) serving multiple internal teams or tenants under your brand.
- **Post-quantum (PQC) key material handling.**
Integration/roadmap support for PQC algorithms (auth / signing) and secure PQC key storage inside the device or service.
- **Hierarchical deterministic key derivation (HD).**
Wallet-style hierarchical key trees (BIP32-like concepts adapted to this platform) for issuing per-user / per-tenant / per-purpose subkeys without exporting the root secret — e.g. embedded wallet logic, tenant isolation, firmware signing trees, large fleets.
- **Cryptographically signed audit trail / tamper-evident event logging.**
High-assurance logging of sensitive actions (key use, provisioning, PIN resets, revocations) with integrity protection for forensic / compliance needs.
- **Dual-control / two-person approval ("four-eyes").**
Require multi-party authorization for high-risk actions such as firmware signing, key export, or critical configuration changes — standard in high-assurance / regulated environments.
- **Secure key escrow / disaster recovery design.**
Split-secret or escrowed backup strategies so you dont lose critical signing keys if a single admin disappears or hardware is lost.
- **Release-signing / supply-chain hardening pipeline.**
Reference tooling and process so every production firmware/binary is signed with hardware-backed keys, proving origin and preventing tampering in transit or at manufacturing.
- **Policy-locked hardened mode ("FIPS-style profile").**
Restricted algorithms, debug disabled, no raw key export, tamper-evident configuration for regulated / high-assurance deployments.
- **Priority support / security response SLA.**
A direct line and guaranteed response window for production-impacting security issues.
- **White-label demo / pre-sales bundle.**
Branded demo firmware + safe onboarding script so you can show "your product" to your own customers without exposing real production secrets.
These components are NOT automatically bundled. They are available case-by-case depending on your use case and are priced separately.
### Licensing models
- **Internal Use License**
Internal production use within one legal entity (your company), including internal private cloud / virtualized deployments for multiple internal teams.
Optional enterprise components can be added as needed.
- **OEM / Redistribution / Service License**
Integration into a product/appliance you ship to customers, OR operating this as a managed service / hosted feature for external clients or third parties.
Optional enterprise components (attestation branding, PQC support, HD key derivation, multi-tenant service hardening, audit trail, etc.) can be added as required.
Pricing depends on scope, fleet size, number of users/tenants, regulatory requirements, and which optional components you select.
### Request a quote
Email: pol@henarejos.me
Subject: `ENTERPRISE LICENSE <your company name>`
Please include:
- Company name and country
- Intended use:
- Internal private deployment
- OEM / external service to third parties
- Approximate scale (number of devices/tokens, number of users/tenants)
- Which optional components you are interested in (bulk provisioning, policy & lifecycle tooling, attestation branding / anti-cloning, virtualization/cloud, PQC, HD key derivation, audit trail, dual-control, key escrow, supply-chain signing, hardened mode, SLA, white-label demo)
You will receive:
1. A short commercial license agreement naming your company.
2. Access to the base package (and any optional components agreed).
3. Scheduling of the onboarding call.
## Why Enterprise exists
- Companies often need hardware-backed security (HSM, FIDO2, OpenPGP, etc.) under their own control, but cannot or will not open-source their internal security workflows.
- They also need multi-user / fleet-management features that hobby users do not.
- The commercial license funds continued development, maintenance and new hardware support.
The Community Edition remains AGPLv3.
The Enterprise Edition is for production, scale, and legal clarity.

143
LICENSE
View File

@@ -1,5 +1,5 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
@@ -7,17 +7,15 @@
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
@@ -72,7 +60,7 @@ modification follow.
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
GNU Affero General Public License for more details.
You should have received a copy of the GNU General Public License
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

145
README.md
View File

@@ -1,8 +1,10 @@
# Pico FIDO
This project aims at transforming your Raspberry Pico into a FIDO key integrated. The Pico works as a FIDO key, like a normal USB key for authentication.
This project transforms your Raspberry Pi Pico or ESP32 microcontroller into an integrated FIDO Passkey, functioning like a standard USB Passkey for authentication.
If you are looking for a Fido + OpenPGP, see: https://github.com/polhenarejos/pico-fido2
## Features
Pico FIDO has implemented the following features:
Pico FIDO includes the following features:
- CTAP 2.1 / CTAP 1
- WebAuthn
@@ -10,15 +12,16 @@ Pico FIDO has implemented the following features:
- HMAC-Secret extension
- CredProtect extension
- User presence enforcement through physical button
- User Verification with PIN
- Discoverable credentials
- User verification with PIN
- Discoverable credentials (resident keys)
- Credential management
- ECDSA authentication
- ECDSA and EDDSA authentication
- Support for SECP256R1, SECP384R1, SECP521R1, SECP256K1 and Ed25519 curves
- App registration and login
- Device selection
- Support for vendor Config
- Support for vendor configuration
- Backup with 24 words
- Secure lock to protect the device from flash dumpings
- Secure lock to protect the device from flash dumps
- Permissions support (MC, GA, CM, ACFG, LBW)
- Authenticator configuration
- minPinLength extension
@@ -26,49 +29,71 @@ Pico FIDO has implemented the following features:
- Enterprise attestation
- credBlobs extension
- largeBlobKey extension
- largeBlobs support (2048 bytes máx.)
- Large blobs support (2048 bytes max)
- OATH (based on YKOATH protocol specification)
- TOTP / HOTP
- Yubikey OTP
- Yubikey One Time Password
- Challenge-response generation
- Emulated keyboard interface
- Button press generates an OTP that is written directly is it was typed
- Button press generates an OTP that is directly typed
- Yubico YKMAN compatible
- Nitrokey nitropy and nitroapp compatible
- Secure Boot and Secure Lock in RP2350 and ESP32-S3 MCUs
- One Time Programming to store the master key that encrypts all resident keys and seeds.
- Rescue interface to allow recovery of the device if it becomes unresponsive or undetectable.
- LED customization with Pico Commissioner.
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.
## Security considerations
Pico FIDO is an open platform so be careful. The contents in the flash memory may be easily dumpled and obtain the private/master keys. Therefore, it is not possible to encrypt the content. At least, one key (the master, the supreme key) must be stored in clear text.
## Security Considerations
Microcontrollers RP2350 and ESP32-S3 are designed to support secure environments when Secure Boot is enabled, and optionally, Secure Lock. These features allow a master key encryption key (MKEK) to be stored in a one-time programmable (OTP) memory region, which is inaccessible from outside secure code. This master key is then used to encrypt all private and secret keys on the device, protecting sensitive data from potential flash memory dumps.
If the Pico is stolen the contents of private and secret keys can be read.
**However**, the RP2040 microcontroller lacks this level of security hardware, meaning that it cannot provide the same protection. Data stored on its flash memory, including private or master keys, can be easily accessed or dumped, as encryption of the master key itself is not feasible. Consequently, if an RP2040 device is stolen, any stored private or secret keys may be exposed.
## Download
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board.
**If you own an ESP32-S3 board, go to [ESP32 Flasher](https://www.picokeys.com/esp32-flasher/) for flashing your Pico FIDO.**
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Patcher tool](https://www.picokeys.com/pico-patcher/).
If you own a Raspberry Pico (RP2040 or RP2350), go to [Download page](https://www.picokeys.com/getting-started/), select your vendor and model and download the proper firmware; or go to [Release page](https://www.github.com/polhenarejos/pico-fido/releases/) and download the UF2 file for your board.
Alternatively you can use the legacy VID/PID patcher as follows:
`./patch_vidpid.sh VID:PID input_hsm_file.uf2 output_hsm_file.uf2`
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with other proprietary tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner").
You can use whatever VID/PID (i.e., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
Note that the pure-browser option [Pico Patcher tool](https://www.picokeys.com/pico-patcher/) is the most recommended.
Note that the pure-browser option [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner") is the most recommended.
## Build
Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive.
## Build for Raspberry Pico
Before building, ensure you have installed the toolchain for the Pico and that the Pico SDK is properly located on your drive.
git clone https://github.com/polhenarejos/pico-fido
cd pico-fido
mkdir build
cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
make
```sh
git clone https://github.com/polhenarejos/pico-fido
git submodule update --init --recursive
cd pico-fido
mkdir build
cd build
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
make
```
Note that `PICO_BOARD`, `USB_VID` and `USB_PID` are optional. If not provided, `pico` board and VID/PID `FEFF:FCFD` will be used.
Note that PICO_BOARD, USB_VID and USB_PID are optional. If not provided, pico board and VID/PID FEFF:FCFD will be used.
Additionally, you can pass the `VIDPID=value` parameter to build the firmware with a known VID/PID. The supported values are:
After make ends, the binary file pico_fido.uf2 will be generated. Put your pico board into loading mode, by pushing BOOTSEL button while pluging on, and copy the UF2 to the new fresh usb mass storage Pico device. Once copied, the pico mass storage will be disconnected automatically and the pico board will reset with the new firmware. A blinking led will indicate the device is ready to work.
- `NitroHSM`
- `NitroFIDO2`
- `NitroStart`
- `NitroPro`
- `Nitro3`
- `Yubikey5`
- `YubikeyNeo`
- `YubiHSM`
- `Gnuk`
- `GnuPG`
**Remark:** Pico Fido uses HID interface and thus, VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary value or the default ones.
After running `make`, the binary file `pico_fido.uf2` will be generated. To load this onto your Pico board:
1. Put the Pico board into loading mode by holding the `BOOTSEL` button while plugging it in.
2. Copy the `pico_fido.uf2` file to the new USB mass storage device that appears.
3. Once the file is copied, the Pico mass storage device will automatically disconnect, and the Pico board will reset with the new firmware.
4. A blinking LED will indicate that the device is ready to work.
## Led blink
Pico FIDO uses the led to indicate the current status. Four states are available:
@@ -94,23 +119,71 @@ While processing, the Pico FIDO is busy and cannot receive additional commands u
## Driver
Pico FIDO uses the `HID` driver, present in all OS. It should be detected by all OS and browser/applications, like normal USB FIDO keys.
Pico FIDO uses the `HID` driver, which is present in all operating systems. It should be detected by all OS and browser/applications just like normal USB FIDO keys.
## Tests
Tests can be found at `tests` folder. It is based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys, but adapted to [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from previous 0.8 version and includes latests improvements from CTAP 2.1.
Tests can be found in the `tests` folder. They are based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys but adapted to the [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from the previous 0.8 version and includes the latest improvements from CTAP 2.1.
All tests can be run by
To run all tests, use:
```
```sh
pytest
```
or by selecting a subset with `-k <test>` flag:
```
To run a subset of tests, use the `-k <test>` flag:
```sh
pytest -k test_credprotect
```
## License and Commercial Use
This project is available under two editions:
**Community Edition (FOSS)**
- Released under the GNU Affero General Public License v3 (AGPLv3).
- You are free to study, modify, and run the code, including for internal evaluation.
- If you distribute modified binaries/firmware, OR if you run a modified version of this project as a network-accessible service, you must provide the corresponding source code to the users of that binary or service, as required by AGPLv3.
- No warranty. No SLA. No guaranteed support.
**Enterprise / Commercial Edition**
- Proprietary license for organizations that want to:
- run this in production with multiple users/devices,
- integrate it into their own product/appliance,
- enforce corporate policies (PIN policy, admin/user roles, revocation),
- deploy it as an internal virtualized / cloud-style service,
- and *not* be required to publish derivative source code.
- Base package includes:
- commercial license (no AGPLv3 disclosure obligation for your modifications / integration)
- onboarding call
- access to officially signed builds
- Optional / on-demand enterprise components that can be added case-by-case:
- ability to operate in multi-user / multi-device environments
- device inventory, traceability and secure revocation/offboarding
- custom attestation, per-organization device identity / anti-cloning
- virtualization / internal "HSM or auth backend" service for multiple teams or tenants
- post-quantum (PQC) key material handling and secure PQC credential storage
- hierarchical deterministic key derivation (HD walletstyle key trees for per-user / per-tenant keys, firmware signing trees, etc.)
- cryptographically signed audit trail / tamper-evident logging
- dual-control / two-person approval for high-risk operations
- secure key escrow / disaster recovery strategy
- release-signing / supply-chain hardening toolchain
- policy-locked hardened mode ("FIPS-style profile")
- priority security-response SLA
- white-label demo / pre-sales bundle
Typical licensing models:
- Internal use (single legal entity, including internal private cloud / virtualized deployments).
- OEM / Redistribution / Service (ship in your product OR offer it as a service to third parties).
These options are scoped and priced individually depending on which components you actually need.
For commercial licensing and enterprise features, email pol@henarejos.me
Subject: `ENTERPRISE LICENSE <your company name>`
See `ENTERPRISE.md` for details.
## Credits
Pico FIDO uses the following libraries or portion of code:
- MbedTLS for cryptographic operations.

View File

@@ -1,55 +1,48 @@
#!/bin/bash
VERSION_MAJOR="3"
VERSION_MAJOR="7"
VERSION_MINOR="0"
NO_EDDSA=0
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
#if ! [[ -z "${GITHUB_SHA}" ]]; then
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
#fi
rm -rf release/*
if [[ $1 == "--no-eddsa" ]]; then
NO_EDDSA=1
echo "Skipping EDDSA build"
fi
mkdir -p build_release
mkdir -p release
mkdir -p release_eddsa
rm -rf -- release/*
if [[ $NO_EDDSA -eq 0 ]]; then
rm -rf -- release_eddsa/*
fi
cd build_release
for board in adafruit_feather_rp2040 \
adafruit_itsybitsy_rp2040 \
adafruit_kb2040 \
adafruit_macropad_rp2040 \
adafruit_qtpy_rp2040 \
adafruit_trinkey_qt2040 \
arduino_nano_rp2040_connect \
datanoisetv_rp2040_dsp \
eetree_gamekit_rp2040 \
garatronic_pybstick26_rp2040 \
melopero_shake_rp2040 \
pico \
pico_w \
pimoroni_badger2040 \
pimoroni_interstate75 \
pimoroni_keybow2040 \
pimoroni_motor2040 \
pimoroni_pga2040 \
pimoroni_picolipo_4mb \
pimoroni_picolipo_16mb \
pimoroni_picosystem \
pimoroni_plasma2040 \
pimoroni_servo2040 \
pimoroni_tiny2040 \
pimoroni_tiny2040_2mb \
seeed_xiao_rp2040 \
solderparty_rp2040_stamp \
solderparty_rp2040_stamp_carrier \
solderparty_rp2040_stamp_round_carrier \
sparkfun_micromod \
sparkfun_promicro \
sparkfun_thingplus \
vgaboard \
waveshare_rp2040_lcd_0.96 \
waveshare_rp2040_lcd_1.28 \
waveshare_rp2040_one \
waveshare_rp2040_plus_4mb \
waveshare_rp2040_plus_16mb \
waveshare_rp2040_zero \
wiznet_w5100s_evb_pico
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
SECURE_BOOT_PKEY="${SECURE_BOOT_PKEY:-../../ec_private_key.pem}"
board_dir=${PICO_SDK_PATH}/src/boards/include/boards
for board in "$board_dir"/*
do
rm -rf *
PICO_SDK_PATH=../../pico-sdk cmake .. -DPICO_BOARD=$board
make -kj20
mv pico_fido.uf2 ../release/pico_fido_$board-$VERSION_MAJOR.$VERSION_MINOR.uf2
board_name="$(basename -- "$board" .h)"
rm -rf -- ./*
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY}
make -j`nproc`
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX.uf2
done
# Build with EDDSA
if [[ $NO_EDDSA -eq 0 ]]; then
for board in "$board_dir"/*
do
board_name="$(basename -- "$board" .h)"
rm -rf -- ./*
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY} -DENABLE_EDDSA=1
make -j`nproc`
mv pico_fido.uf2 ../release_eddsa/pico_fido_$board_name-$SUFFIX-eddsa1.uf2
done
fi

View File

@@ -87,7 +87,7 @@ fi
LITTLE_VID="\x${VID:2:2}\x${VID:0:2}"
LITTLE_PID="\x${PID:2:2}\x${PID:0:2}"
perl -pi -e "s/\xfe\xca\x31\x42\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/" $UF2_FILE_OF
perl -pi -e "s/[\x00-\xff]{4}\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/" $UF2_FILE_OF
echo "Done!"
echo ""

Submodule pico-hsm-sdk deleted from 46e7d3a181

1
pico-keys-sdk Submodule

Submodule pico-keys-sdk added at d0dea3d0c5

View File

@@ -18,9 +18,20 @@ if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_P
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
@@ -29,11 +40,22 @@ if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)

57
sdkconfig.defaults Normal file
View File

@@ -0,0 +1,57 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
IGNORE_UNKNOWN_FILES_FOR_MANAGED_COMPONENTS=1
CONFIG_TINYUSB=y
CONFIG_TINYUSB_TASK_STACK_SIZE=16384
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_WL_SECTOR_SIZE_512=y
CONFIG_WL_SECTOR_MODE_PERF=y
COMPILER_OPTIMIZATION="Performance"
CONFIG_MBEDTLS_CMAC_C=y
CONFIG_MBEDTLS_CHACHA20_C=y
CONFIG_MBEDTLS_POLY1305_C=y
CONFIG_MBEDTLS_CHACHAPOLY_C=y
CONFIG_MBEDTLS_HKDF_C=y
CONFIG_MBEDTLS_HARDWARE_ECC=y
CONFIG_MBEDTLS_HARDWARE_GCM=y
# CONFIG_MBEDTLS_HARDWARE_MPI is not set
CONFIG_MBEDTLS_HARDWARE_SHA=y
CONFIG_MBEDTLS_HARDWARE_AES=y
# CONFIG_MBEDTLS_ROM_MD5 is not set
CONFIG_MBEDTLS_SHA512_C=y
CONFIG_MBEDTLS_TLS_DISABLED=y
# CONFIG_MBEDTLS_TLS_ENABLED is not set
# CONFIG_ESP_TLS_USE_DS_PERIPHERAL is not set
# CONFIG_ESP_WIFI_ENABLED is not set
# CONFIG_ESP_WIFI_MBEDTLS_CRYPTO is not set
# CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT is not set
# CONFIG_WPA_MBEDTLS_CRYPTO is not set
# CONFIG_MBEDTLS_PSK_MODES is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_RSA is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA is not set
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA is not set
# CONFIG_MBEDTLS_SSL_RENEGOTIATION is not set
# CONFIG_MBEDTLS_SSL_PROTO_TLS1_2 is not set
# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set
# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set
# CONFIG_MBEDTLS_SSL_ALPN is not set
# CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS is not set
# CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS is not set
# CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE is not set
# CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA is not set
# CONFIG_ESP_WIFI_ENABLE_WPA3_SAE is not set
# CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA is not set
CONFIG_ESP_COREDUMP_ENABLE_TO_UART=y

6
src/fido/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS . ../../pico-keys-sdk/src ../../pico-keys-sdk/src/fs ../../pico-keys-sdk/src/rng ../../pico-keys-sdk/src/usb ../../pico-keys-sdk/tinycbor/src
REQUIRES esp_tinyusb mbedtls efuse
)
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)

View File

@@ -3,19 +3,20 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ENABLE_EMULATION
#include "pico_keys.h"
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "hid/ctap_hid.h"
@@ -23,6 +24,9 @@
#include "fido.h"
#include "usb.h"
#include "apdu.h"
#include "management.h"
#include "ctap2_cbor.h"
#include "version.h"
const bool _btrue = true, _bfalse = false;
@@ -38,12 +42,13 @@ int cbor_config(const uint8_t *data, size_t len);
int cbor_vendor(const uint8_t *data, size_t len);
int cbor_large_blobs(const uint8_t *data, size_t len);
const uint8_t aaguid[16] =
{ 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45 }; // First 16 bytes of SHA256("Pico FIDO2")
extern int cmd_read_config();
const uint8_t aaguid[16] = { 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45 }; // First 16 bytes of SHA256("Pico FIDO2")
const uint8_t *cbor_data = NULL;
size_t cbor_len = 0;
uint8_t cmd = 0;
uint8_t cbor_cmd = 0;
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
if (len == 0 && cmd == CTAPHID_CBOR) {
@@ -52,75 +57,211 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
if (len > 0) {
DEBUG_DATA(data + 1, len - 1);
}
driver_prepare_response_hid();
if (cmd == CTAPHID_CBOR) {
if (data[0] == CTAP_MAKE_CREDENTIAL) {
return cbor_make_credential(data + 1, len - 1);
if (cap_supported(CAP_FIDO2)) {
if (cmd == CTAPHID_CBOR) {
if (data[0] == CTAP_MAKE_CREDENTIAL) {
return cbor_make_credential(data + 1, len - 1);
}
if (data[0] == CTAP_GET_INFO) {
return cbor_get_info();
}
else if (data[0] == CTAP_RESET) {
return cbor_reset();
}
else if (data[0] == CTAP_CLIENT_PIN) {
return cbor_client_pin(data + 1, len - 1);
}
else if (data[0] == CTAP_GET_ASSERTION) {
return cbor_get_assertion(data + 1, len - 1, false);
}
else if (data[0] == CTAP_GET_NEXT_ASSERTION) {
return cbor_get_next_assertion(data + 1, len - 1);
}
else if (data[0] == CTAP_SELECTION) {
return cbor_selection();
}
else if (data[0] == CTAP_CREDENTIAL_MGMT || data[0] == 0x41) {
return cbor_cred_mgmt(data + 1, len - 1);
}
else if (data[0] == CTAP_CONFIG) {
return cbor_config(data + 1, len - 1);
}
else if (data[0] == CTAP_LARGE_BLOBS) {
return cbor_large_blobs(data + 1, len - 1);
}
}
if (data[0] == CTAP_GET_INFO) {
return cbor_get_info();
else if (cmd == CTAP_VENDOR_CBOR) {
return cbor_vendor(data, len);
}
else if (data[0] == CTAP_RESET) {
return cbor_reset();
else if (cmd == 0xC2) {
if (cmd_read_config() == 0x9000) {
memmove(res_APDU-1, res_APDU, res_APDU_size);
res_APDU_size -= 1;
return 0;
}
}
else if (data[0] == CTAP_CLIENT_PIN) {
return cbor_client_pin(data + 1, len - 1);
}
else if (data[0] == CTAP_GET_ASSERTION) {
return cbor_get_assertion(data + 1, len - 1, false);
}
else if (data[0] == CTAP_GET_NEXT_ASSERTION) {
return cbor_get_next_assertion(data + 1, len - 1);
}
else if (data[0] == CTAP_SELECTION) {
return cbor_selection();
}
else if (data[0] == CTAP_CREDENTIAL_MGMT || data[0] == 0x41) {
return cbor_cred_mgmt(data + 1, len - 1);
}
else if (data[0] == CTAP_CONFIG) {
return cbor_config(data + 1, len - 1);
}
else if (data[0] == CTAP_LARGE_BLOBS) {
return cbor_large_blobs(data + 1, len - 1);
}
}
else if (cmd == CTAP_VENDOR_CBOR) {
return cbor_vendor(data, len);
}
return CTAP1_ERR_INVALID_CMD;
}
#ifndef ENABLE_EMULATION
void cbor_thread() {
void *cbor_thread(void *arg) {
(void)arg;
card_init_core1();
while (1) {
uint32_t m;
queue_remove_blocking(&usb_to_card_q, &m);
uint32_t flag = m + 1;
queue_add_blocking(&card_to_usb_q, &flag);
if (m == EV_EXIT) {
break;
}
apdu.sw = cbor_parse(cmd, cbor_data, cbor_len);
apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len);
if (apdu.sw == 0) {
DEBUG_DATA(res_APDU + 1, res_APDU_size);
DEBUG_DATA(res_APDU, res_APDU_size);
}
else {
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
res_APDU[-1] = (uint8_t)apdu.sw;
apdu.sw = 0;
}
else {
res_APDU[0] = (uint8_t)apdu.sw;
}
}
finished_data_size = res_APDU_size + 1;
uint32_t flag = EV_EXEC_FINISHED;
flag = EV_EXEC_FINISHED;
queue_add_blocking(&card_to_usb_q, &flag);
}
return NULL;
}
#endif
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
cbor_data = data;
cbor_len = len;
cmd = last_cmd;
cbor_cmd = last_cmd;
ctap_resp->init.data[0] = 0;
res_APDU = ctap_resp->init.data + 1;
res_APDU_size = 0;
return 1;
return 2; // CBOR processing
}
CborError COSE_key_params(int crv, int alg, mbedtls_ecp_group *grp, mbedtls_ecp_point *Q, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
CborError error = CborNoError;
int kty = 1;
if (crv == FIDO2_CURVE_P256 || crv == FIDO2_CURVE_P384 || crv == FIDO2_CURVE_P521 ||
crv == FIDO2_CURVE_P256K1) {
kty = 2;
}
CBOR_CHECK(cbor_encoder_create_map(mapEncoderParent, mapEncoder, kty == 2 ? 5 : 4));
CBOR_CHECK(cbor_encode_uint(mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(mapEncoder, kty));
CBOR_CHECK(cbor_encode_uint(mapEncoder, 3));
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, -alg));
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(mapEncoder, crv));
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 2));
uint8_t pkey[67];
if (kty == 2) {
size_t plen = mbedtls_mpi_size(&grp->P);
CBOR_CHECK(mbedtls_mpi_write_binary(&Q->X, pkey, plen));
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, plen));
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 3));
CBOR_CHECK(mbedtls_mpi_write_binary(&Q->Y, pkey, plen));
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, plen));
}
else {
size_t olen = 0;
CBOR_CHECK(mbedtls_ecp_point_write_binary(grp, Q, MBEDTLS_ECP_PF_COMPRESSED, &olen, pkey,
sizeof(pkey)));
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, olen));
}
CBOR_CHECK(cbor_encoder_close_container(mapEncoderParent, mapEncoder));
err:
return error;
}
CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
CborEncoder *mapEncoder) {
int crv = mbedtls_curve_to_fido(key->grp.id), alg = 0;
if (key->grp.id == MBEDTLS_ECP_DP_SECP256R1) {
alg = FIDO2_ALG_ES256;
}
else if (key->grp.id == MBEDTLS_ECP_DP_SECP384R1) {
alg = FIDO2_ALG_ES384;
}
else if (key->grp.id == MBEDTLS_ECP_DP_SECP521R1) {
alg = FIDO2_ALG_ES512;
}
else if (key->grp.id == MBEDTLS_ECP_DP_SECP256K1) {
alg = FIDO2_ALG_ES256K;
}
else if (key->grp.id == MBEDTLS_ECP_DP_CURVE25519) {
alg = FIDO2_ALG_ECDH_ES_HKDF_256;
}
#ifdef MBEDTLS_EDDSA_C
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
alg = FIDO2_ALG_EDDSA;
}
else if (key->grp.id == MBEDTLS_ECP_DP_ED448) {
alg = FIDO2_ALG_ED448;
}
#endif
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
}
CborError COSE_key_shared(mbedtls_ecdh_context *key,
CborEncoder *mapEncoderParent,
CborEncoder *mapEncoder) {
int crv = mbedtls_curve_to_fido(key->ctx.mbed_ecdh.grp.id), alg = FIDO2_ALG_ECDH_ES_HKDF_256;
return COSE_key_params(crv, alg, &key->ctx.mbed_ecdh.grp, &key->ctx.mbed_ecdh.Q, mapEncoderParent, mapEncoder);
}
CborError COSE_public_key(int alg, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
CborError error = CborNoError;
CBOR_CHECK(cbor_encoder_create_map(mapEncoderParent, mapEncoder, 2));
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "alg"));
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, -alg));
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "type"));
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(mapEncoderParent, mapEncoder));
err:
return error;
}
CborError COSE_read_key(CborValue *f, int64_t *kty, int64_t *alg, int64_t *crv, CborByteString *kax, CborByteString *kay) {
int64_t kkey = 0;
CborError error = CborNoError;
CBOR_PARSE_MAP_START(*f, 0)
{
CBOR_FIELD_GET_INT(kkey, 0);
if (kkey == 1) {
CBOR_FIELD_GET_INT(*kty, 0);
}
else if (kkey == 3) {
CBOR_FIELD_GET_INT(*alg, 0);
}
else if (kkey == -1) {
CBOR_FIELD_GET_INT(*crv, 0);
}
else if (kkey == -2) {
CBOR_FIELD_GET_BYTES(*kax, 0);
}
else if (kkey == -3) {
CBOR_FIELD_GET_BYTES(*kay, 0);
}
else {
CBOR_ADVANCE(0);
}
}
CBOR_PARSE_MAP_END(*f, 0);
err:
return error;
}

View File

@@ -3,19 +3,24 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#ifndef ESP_PLATFORM
#include "common.h"
#else
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#endif
#include "mbedtls/ecp.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/sha256.h"
@@ -23,7 +28,7 @@
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -31,14 +36,15 @@
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
#include "hsm.h"
#include "apdu.h"
#include "kek.h"
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
uint32_t max_usage_time_period = 600 * 1000;
bool needs_power_cycle = false;
static mbedtls_ecdh_context hkey;
static bool hkey_init = false;
extern int encrypt_keydev_f1(const uint8_t keydev[32]);
int beginUsingPinUvAuthToken(bool userIsPresent) {
paut.user_present = userIsPresent;
@@ -100,11 +106,7 @@ int regenerate() {
mbedtls_ecdh_init(&hkey);
hkey_init = true;
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
&hkey.ctx.mbed_ecdh.d,
&hkey.ctx.mbed_ecdh.Q,
random_gen,
NULL);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0) {
return ret;
@@ -120,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
return ret;
}
if (protocol == 1) {
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
buf,
sizeof(buf),
sharedSecret);
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
}
else if (protocol == 2) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
ret = mbedtls_hkdf(md_info,
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 HMAC key",
14,
sharedSecret,
32);
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
if (ret != 0) {
return ret;
}
return mbedtls_hkdf(md_info,
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 AES key",
13,
sharedSecret + 32,
32);
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
}
return -1;
}
@@ -155,61 +138,69 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
mbedtls_mpi z;
mbedtls_mpi_init(&z);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
&z,
Q,
&hkey.ctx.mbed_ecdh.d,
random_gen,
NULL);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z);
return ret;
}
int resetPinUvAuthToken() {
void resetAuthToken(bool persistent) {
uint16_t fid = EF_AUTHTOKEN;
if (persistent) {
fid = EF_PAUTHTOKEN;
}
file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
file_put_data(ef, t, sizeof(t));
low_flash_available();
}
int resetPinUvAuthToken() {
resetAuthToken(false);
paut.permissions = 0;
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
low_flash_available();
return 0;
}
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out) {
int resetPersistentPinUvAuthToken() {
resetAuthToken(true);
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
ppaut.permissions = 0;
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
return 0;
}
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
if (protocol == 1) {
memcpy(out, in, in_len);
return aes_encrypt(key, NULL, 32 * 8, HSM_AES_MODE_CBC, out, in_len);
return aes_encrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
}
else if (protocol == 2) {
random_gen(NULL, out, IV_SIZE);
memcpy(out + IV_SIZE, in, in_len);
return aes_encrypt(key + 32, out, 32 * 8, HSM_AES_MODE_CBC, out + IV_SIZE, in_len);
return aes_encrypt(key + 32, out, 32 * 8, PICO_KEYS_AES_MODE_CBC, out + IV_SIZE, in_len);
}
return -1;
}
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out) {
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
if (protocol == 1) {
memcpy(out, in, in_len);
return aes_decrypt(key, NULL, 32 * 8, HSM_AES_MODE_CBC, out, in_len);
return aes_decrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
}
else if (protocol == 2) {
memcpy(out, in + IV_SIZE, in_len);
return aes_decrypt(key + 32, in, 32 * 8, HSM_AES_MODE_CBC, out, in_len - IV_SIZE);
memcpy(out, in + IV_SIZE, in_len - IV_SIZE);
return aes_decrypt(key + 32, in, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len - IV_SIZE);
}
return -1;
}
int authenticate(uint8_t protocol,
const uint8_t *key,
const uint8_t *data,
size_t len,
uint8_t *sign) {
int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
uint8_t hmac[32];
int ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
@@ -228,20 +219,19 @@ int authenticate(uint8_t protocol,
return 0;
}
int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign) {
uint8_t hmac[32];
//if (paut.in_use == false)
// return -2;
int ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
if (ret != 0) {
return ret;
}
if (protocol == 1) {
return memcmp(sign, hmac, 16);
return ct_memcmp(sign, hmac, 16);
}
else if (protocol == 2) {
return memcmp(sign, hmac, 32);
return ct_memcmp(sign, hmac, 32);
}
return -1;
}
@@ -276,6 +266,18 @@ int pinUvAuthTokenUsageTimerObserver() {
return 0;
}
int check_keydev_encrypted(const uint8_t pin_token[32]) {
if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
uint8_t tmp_keydev[61];
tmp_keydev[0] = 0x02; // Change format to encrypted
encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
low_flash_available();
}
return PICOKEY_OK;
}
uint8_t new_pin_mismatches = 0;
int cbor_client_pin(const uint8_t *data, size_t len) {
@@ -286,11 +288,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CborEncoder encoder, mapEncoder;
CborValue map;
CborError error = CborNoError;
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
kay = { 0 };
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
CborCharString rpId = { 0 };
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
uint8_t keydev[32] = {0};
if (hkey_init == false) {
initialize();
}
@@ -312,30 +314,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_FIELD_GET_UINT(subcommand, 1);
}
else if (val_u == 0x03) {
int64_t key = 0;
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_INT(key, 2);
if (key == 1) {
CBOR_FIELD_GET_INT(kty, 2);
}
else if (key == 3) {
CBOR_FIELD_GET_INT(alg, 2);
}
else if (key == -1) {
CBOR_FIELD_GET_INT(crv, 2);
}
else if (key == -2) {
CBOR_FIELD_GET_BYTES(kax, 2);
}
else if (key == -3) {
CBOR_FIELD_GET_BYTES(kay, 2);
}
else {
CBOR_ADVANCE(2);
}
}
CBOR_PARSE_MAP_END(_f1, 2);
CBOR_CHECK(COSE_read_key(&_f1, &kty, &alg, &crv, &kax, &kay));
}
else if (val_u == 0x04) {
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
@@ -355,7 +334,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
CBOR_PARSE_MAP_END(map, 1);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
@@ -374,21 +353,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
uint8_t pkey[32];
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(COSE_key_shared(&hkey, &mapEncoder, &mapEncoder2));
}
else if (pinUvAuthProtocol == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
@@ -419,18 +384,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len,
pinUvAuthParam.data) != 0) {
if (verify((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, pinUvAuthParam.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
uint8_t paddedNewPin[64];
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
@@ -450,12 +414,22 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
uint8_t hsh[34];
uint8_t hsh[35], dhash[32];
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
flash_write_data_to_file(ef_pin, hsh, 2 + 16);
hsh[2] = 1; // New format indicator
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
pin_derive_verifier(dhash, 16, hsh + 3);
file_put_data(ef_pin, hsh, sizeof(hsh));
low_flash_available();
pin_derive_session(dhash, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
mbedtls_platform_zeroize(hsh, sizeof(hsh));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
goto err; //No return
}
else if (subcommand == 0x4) { //changePIN
@@ -485,7 +459,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
@@ -493,25 +467,32 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
uint8_t tmp[80 + 32];
memcpy(tmp, newPinEnc.data, newPinEnc.len);
memcpy(tmp + newPinEnc.len, pinHashEnc.data, pinHashEnc.len);
if (verify(pinUvAuthProtocol, sharedSecret, tmp, newPinEnc.len + pinHashEnc.len,
pinUvAuthParam.data) != 0) {
if (verify((uint8_t)pinUvAuthProtocol, sharedSecret, tmp, (uint16_t)(newPinEnc.len + pinHashEnc.len), pinUvAuthParam.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64];
ret =
decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (memcmp(paddedNewPin, file_get_data(ef_pin) + 2, 16) != 0) {
uint8_t dhash[32], off = 3;
if (file_get_size(ef_pin) == 34) {
off = 2;
double_hash_pin(paddedNewPin, 16, dhash);
}
else {
pin_derive_verifier(paddedNewPin, 16, dhash);
}
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
@@ -525,11 +506,30 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
if (off == 2) {
// Upgrade pin file to new format
pin_data[2] = 1; // New format indicator
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
hash_multi(paddedNewPin, 16, session_pin);
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
}
pin_derive_session(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
new_pin_mismatches = 0;
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
@@ -549,24 +549,44 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
uint8_t hsh[33];
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 16) == 0) {
// New PIN is valid and verified
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
pin_derive_session(dhash, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
low_flash_available();
pin_data[0] = MAX_PIN_RETRIES;
pin_data[1] = pin_len;
pin_data[2] = 1; // New format indicator
pin_derive_verifier(dhash, 16, pin_data + 3);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
flash_write_data_to_file(ef_pin, hsh, 2 + 16);
file_put_data(ef_pin, pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
uint8_t *tmp = (uint8_t *) calloc(1, file_get_size(ef_minpin));
memcpy(tmp, file_get_data(ef_minpin), file_get_size(ef_minpin));
tmp[1] = 0;
flash_write_data_to_file(ef_minpin, tmp, file_get_size(ef_minpin));
free(tmp);
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
memcpy(tmpf, file_get_data(ef_minpin), file_get_size(ef_minpin));
tmpf[1] = 0;
file_put_data(ef_minpin, tmpf, file_get_size(ef_minpin));
free(tmpf);
}
low_flash_available();
resetPinUvAuthToken();
resetPersistentPinUvAuthToken();
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
@@ -587,7 +607,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
}
if (!file_has_data(ef_pin)) {
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
@@ -602,27 +624,35 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = (pinUvAuthProtocol - 1) * IV_SIZE;
ret =
decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (memcmp(paddedNewPin, file_get_data(ef_pin) + 2, 16) != 0) {
uint8_t dhash[32], off = 3;
if (file_get_size(ef_pin) == 34) {
off = 2;
double_hash_pin(paddedNewPin, 16, dhash);
}
else {
pin_derive_verifier(paddedNewPin, 16, dhash);
}
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
}
@@ -634,29 +664,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (off == 2) {
// Upgrade pin file to new format
pin_data[2] = 1; // New format indicator
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
hash_multi(paddedNewPin, 16, session_pin);
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
}
pin_derive_session(paddedNewPin, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
low_flash_available();
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
resetPinUvAuthToken();
beginUsingPinUvAuthToken(false);
if (subcommand == 0x05) {
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
}
paut.permissions = permissions;
if (rpId.present == true) {
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
if (permissions & CTAP_PERMISSION_PCMR) {
ppaut.permissions = CTAP_PERMISSION_PCMR;
pdata = ppaut.data;
}
else {
paut.has_rp_id = false;
resetPinUvAuthToken();
beginUsingPinUvAuthToken(false);
if (subcommand == 0x05) {
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
}
paut.permissions = (uint8_t)permissions;
if (rpId.present == true) {
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
}
else {
paut.has_rp_id = false;
}
pdata = paut.data;
}
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
@@ -673,12 +733,13 @@ err:
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(rpId);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
if (error != CborNoError) {
if (error == CborErrorImproperValue) {
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
@@ -22,24 +23,27 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "hsm.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h"
#include "file.h"
extern uint8_t keydev_dec[32];
extern bool has_keydev_dec;
extern void resetPersistentPinUvAuthToken();
extern void resetPinUvAuthToken();
int cbor_config(const uint8_t *data, size_t len) {
CborParser parser;
CborValue map;
CborError error = CborNoError;
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0;
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
CborCharString minPinLengthRPIDs[32] = { 0 };
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
CborEncoder encoder, mapEncoder;
CborEncoder encoder;
//CborEncoder mapEncoder;
uint8_t *raw_subpara = NULL;
const bool *forceChangePin = NULL;
@@ -64,16 +68,22 @@ int cbor_config(const uint8_t *data, size_t len) {
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
CBOR_PARSE_MAP_START(_f1, 2)
{
if (subcommand == 0xff) {
if (subcommand == 0xFF) { // Vendor
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
}
else if (subpara == 0x02) {
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
CBOR_FIELD_GET_BYTES(vendorParamByteString, 2);
}
else if (subpara == 0x03) {
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
}
else if (subpara == 0x04) {
CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
}
}
else if (subcommand == 0x03) {
else if (subcommand == 0x03) { // Extensions
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(newMinPinLength, 2);
@@ -106,25 +116,24 @@ int cbor_config(const uint8_t *data, size_t len) {
}
CBOR_PARSE_MAP_END(map, 1);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (pinUvAuthProtocol == 0) {
if (pinUvAuthProtocol == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
memset(verify_payload, 0xff, 32);
verify_payload[32] = 0x0d;
verify_payload[33] = subcommand;
verify_payload[33] = (uint8_t)subcommand;
memcpy(verify_payload + 34, raw_subpara, raw_subpara_len);
error = verify(pinUvAuthProtocol,
paut.data,
verify_payload,
32 + 1 + 1 + raw_subpara_len,
pinUvAuthParam.data);
error = verify((uint8_t)pinUvAuthProtocol, paut.data, verify_payload, (uint16_t)(32 + 1 + 1 + raw_subpara_len), pinUvAuthParam.data);
free(verify_payload);
if (error != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
@@ -134,17 +143,23 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (subcommand == 0xff) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
if (subcommand == 0xFF) {
#ifndef ENABLE_EMULATION
const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID ||
vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO ||
vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS ||
vendorCommandId == CTAP_CONFIG_PHY_OPTS);
#endif
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
if (!file_has_data(ef_keydev_enc)) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
if (has_keydev_dec == false) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
flash_write_data_to_file(ef_keydev, keydev_dec, sizeof(keydev_dec));
file_put_data(ef_keydev, keydev_dec, sizeof(keydev_dec));
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
flash_write_data_to_file(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
file_put_data(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
@@ -156,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
}
mbedtls_chachapoly_context chatx;
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
@@ -164,29 +179,71 @@ int cbor_config(const uint8_t *data, size_t len) {
uint8_t key_dev_enc[12 + 32 + 16];
random_gen(NULL, key_dev_enc, 12);
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx,
file_get_size(ef_keydev),
key_dev_enc,
NULL,
0,
file_get_data(ef_keydev),
key_dev_enc + 12,
key_dev_enc + 12 + file_get_size(ef_keydev));
mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data);
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
mbedtls_chachapoly_free(&chatx);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
flash_write_data_to_file(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc));
file_put_data(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc));
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
flash_write_data_to_file(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
file_put_data(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
#ifndef ENABLE_EMULATION
else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
phy_data.vid = (vendorParamInt >> 16) & 0xFFFF;
phy_data.pid = vendorParamInt & 0xFFFF;
phy_data.vidpid_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
phy_data.led_gpio = (uint8_t)vendorParamInt;
phy_data.led_gpio_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
phy_data.led_brightness = (uint8_t)vendorParamInt;
phy_data.led_brightness_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
phy_data.opts = (uint16_t)vendorParamInt;
}
#endif
else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
if (vendorParamByteString.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
if (ef_ee_ea) {
file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len);
}
low_flash_available();
}
else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
if (ef_pin_policy) {
uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
if (val) {
// Not ready yet for integer param
// val[0] = (uint8_t)(vendorParamInt >> 8);
// val[1] = (uint8_t)(vendorParamInt & 0xFF);
memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len);
free(val);
}
}
low_flash_available();
}
else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
}
#ifndef ENABLE_EMULATION
if (is_phy && phy_save() != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
#endif
goto err;
}
else if (subcommand == 0x03) {
@@ -207,17 +264,19 @@ int cbor_config(const uint8_t *data, size_t len) {
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
forceChangePin = ptrue;
}
uint8_t *data = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
data[0] = newMinPinLength;
data[1] = forceChangePin == ptrue ? 1 : 0;
for (int m = 0; m < minPinLengthRPIDs_len; m++) {
mbedtls_sha256((uint8_t *) minPinLengthRPIDs[m].data,
minPinLengthRPIDs[m].len,
data + 2 + m * 32,
0);
if (forceChangePin) {
resetPersistentPinUvAuthToken();
resetPinUvAuthToken();
}
flash_write_data_to_file(ef_minpin, data, 2 + minPinLengthRPIDs_len * 32);
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0;
for (size_t m = 0; m < minPinLengthRPIDs_len; m++) {
mbedtls_sha256((uint8_t *) minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, dataf + 2 + m * 32, 0);
}
file_put_data(ef_minpin, dataf, (uint16_t)(2 + minPinLengthRPIDs_len * 32));
low_flash_available();
free(dataf);
goto err; //No return
}
else if (subcommand == 0x01) {
@@ -227,13 +286,14 @@ int cbor_config(const uint8_t *data, size_t len) {
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
//CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
//resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(vendorAutCt);
for (int i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(vendorParamByteString);
CBOR_FREE_BYTE_STRING(vendorParamTextString);
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
}
@@ -243,6 +303,6 @@ err:
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
@@ -22,7 +23,6 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "hsm.h"
uint8_t rp_counter = 1;
uint8_t rp_total = 0;
@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4)
{
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
transports_len], 4);
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
credentialId.transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
@@ -120,19 +119,25 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1,
pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
if (file_has_data(search_dynamic_file(EF_CRED + i))) {
if (file_has_data(search_dynamic_file((uint16_t)(EF_CRED + i)))) {
existing++;
}
}
@@ -145,13 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL;
if (subcommand == 0x02) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1,
pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
rp_counter = 1;
rp_total = 0;
@@ -163,7 +173,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
uint8_t skip = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *tef = search_dynamic_file(EF_RP + i);
file_t *tef = search_dynamic_file((uint16_t)(EF_RP + i));
if (file_has_data(tef) && *file_get_data(tef) > 0) {
if (++skip == rp_counter) {
if (rp_ef == NULL) {
@@ -202,14 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara - 1, raw_subpara_len + 1,
pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) ||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) ||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
cred_counter = 1;
cred_total = 0;
@@ -223,7 +239,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
file_t *cred_ef = NULL;
uint8_t skip = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *tef = search_dynamic_file(EF_CRED + i);
file_t *tef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (file_has_data(tef) && memcmp(file_get_data(tef), rpIdHash.data, 32) == 0) {
if (++skip == cred_counter) {
if (cred_ef == NULL) {
@@ -243,22 +259,21 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
Credential cred = { 0 };
if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data,
&cred) != 0) {
if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
if (fido_load_key(cred.curve, cred.id.data, &key) != 0) {
mbedtls_ecp_keypair key;
mbedtls_ecp_keypair_init(&key);
if (fido_load_key((int)cred.curve, cred.id.data, &key) != 0) {
credential_free(&cred);
mbedtls_ecdsa_free(&key);
mbedtls_ecp_keypair_free(&key);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
cred_counter++;
uint8_t l = 3;
uint8_t l = 4;
if (subcommand == 0x04) {
l++;
}
@@ -290,40 +305,26 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
if (cred.userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userName.data,
cred.userName.len));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userName.data, cred.userName.len));
}
if (cred.userDisplayName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "displayName"));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userDisplayName.data,
cred.userDisplayName.len));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userDisplayName.data, cred.userDisplayName.len));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(cred.id.data, cred.id.len, cred_idr);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -cred.alg));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, cred.curve));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
uint8_t pkey[66];
mbedtls_mpi_write_binary(&key.Q.X, pkey, mbedtls_mpi_size(&key.Q.X));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, mbedtls_mpi_size(&key.Q.X)));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
mbedtls_mpi_write_binary(&key.Q.Y, pkey, mbedtls_mpi_size(&key.Q.Y));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, mbedtls_mpi_size(&key.Q.Y)));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(COSE_key(&key, &mapEncoder, &mapEncoder2));
if (subcommand == 0x04) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x09));
@@ -345,21 +346,25 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey,
sizeof(largeBlobKey)));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, cred.extensions.thirdPartyPayment == ptrue));
}
else {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
}
credential_free(&cred);
mbedtls_ecdsa_free(&key);
mbedtls_ecp_keypair_free(&key);
}
else if (subcommand == 0x06) {
if (credentialId.id.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
*(raw_subpara - 1) = 0x06;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara - 1, raw_subpara_len + 1,
pinUvAuthParam.data) != CborNoError) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
@@ -368,18 +373,15 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) &&
memcmp(file_get_data(ef) + 32, credentialId.id.data,
MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
uint8_t *rp_id_hash = file_get_data(ef);
if (delete_file(ef) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
for (int j = 0; j < MAX_RESIDENT_CREDENTIALS; j++) {
file_t *rp_ef = search_dynamic_file(EF_RP + j);
if (file_has_data(rp_ef) &&
memcmp(file_get_data(rp_ef) + 1, rp_id_hash, 32) == 0) {
file_t *rp_ef = search_dynamic_file((uint16_t)(EF_RP + j));
if (file_has_data(rp_ef) && memcmp(file_get_data(rp_ef) + 1, rp_id_hash, 32) == 0) {
uint8_t *rp_data = (uint8_t *) calloc(1, file_get_size(rp_ef));
memcpy(rp_data, file_get_data(rp_ef), file_get_size(rp_ef));
rp_data[0] -= 1;
@@ -387,7 +389,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
delete_file(rp_ef);
}
else {
flash_write_data_to_file(rp_ef, rp_data, file_get_size(rp_ef));
file_put_data(rp_ef, rp_data, file_get_size(rp_ef));
}
free(rp_data);
break;
@@ -404,8 +406,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
*(raw_subpara - 1) = 0x07;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara - 1, raw_subpara_len + 1,
pinUvAuthParam.data) != CborNoError) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
@@ -414,27 +415,23 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) &&
memcmp(file_get_data(ef) + 32, credentialId.id.data,
MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
Credential cred = { 0 };
uint8_t *rp_id_hash = file_get_data(ef);
if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash,
&cred) != 0) {
if (credential_load_resident(ef, rp_id_hash, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
if (memcmp(user.id.data, cred.userId.data,
MIN(user.id.len, cred.userId.len)) != 0) {
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t newcred[MAX_CRED_ID_LENGTH];
size_t newcred_len = 0;
uint16_t newcred_len = 0;
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
&user.displayName, &cred.opts, &cred.extensions,
cred.use_sign_count, cred.alg,
cred.curve, newcred, &newcred_len) != 0) {
&user.displayName, &cred.opts, &cred.extensions,
cred.use_sign_count, (int)cred.alg,
(int)cred.curve, newcred, &newcred_len) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -460,7 +457,8 @@ err:
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
CBOR_FREE_BYTE_STRING(credentialId.type);
for (int n = 0; n < credentialId.transports_len; n++) {
CBOR_FREE_BYTE_STRING(credentialId.id);
for (size_t n = 0; n < credentialId.transports_len; n++) {
CBOR_FREE_BYTE_STRING(credentialId.transports[n]);
}
if (error != CborNoError) {
@@ -469,6 +467,6 @@ err:
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,28 +3,28 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "cbor.h"
#include "ctap.h"
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
#include "fido.h"
#include "files.h"
#include "crypto_utils.h"
#include "hsm.h"
#include "apdu.h"
#include "cbor_make_credential.h"
#include "credential.h"
@@ -43,6 +43,8 @@ uint8_t *datax = NULL;
size_t lenx = 0;
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
(void) data;
(void) len;
CborError error = CborNoError;
if (credentialCounter >= numberOfCredentialsx) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
@@ -91,7 +93,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
size_t allowList_len = 0, creds_len = 0;
uint8_t *aut_data = NULL;
bool asserted = false, up = true, uv = false;
bool asserted = false, up = false, uv = false;
int64_t kty = 2, alg = 0, crv = 0;
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
const bool *credBlob = NULL;
@@ -150,30 +152,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
{
CBOR_FIELD_GET_UINT(ukey, 3);
if (ukey == 0x01) {
int64_t kkey = 0;
CBOR_PARSE_MAP_START(_f3, 4)
{
CBOR_FIELD_GET_INT(kkey, 4);
if (kkey == 1) {
CBOR_FIELD_GET_INT(kty, 4);
}
else if (kkey == 3) {
CBOR_FIELD_GET_INT(alg, 4);
}
else if (kkey == -1) {
CBOR_FIELD_GET_INT(crv, 4);
}
else if (kkey == -2) {
CBOR_FIELD_GET_BYTES(kax, 4);
}
else if (kkey == -3) {
CBOR_FIELD_GET_BYTES(kay, 4);
}
else {
CBOR_ADVANCE(4);
}
}
CBOR_PARSE_MAP_END(_f3, 4);
CBOR_CHECK(COSE_read_key(&_f3, &kty, &alg, &crv, &kax, &kay));
}
else if (ukey == 0x02) {
CBOR_FIELD_GET_BYTES(salt_enc, 3);
@@ -193,6 +172,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
@@ -223,7 +203,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
uint8_t flags = 0;
uint8_t rp_id_hash[32];
uint8_t rp_id_hash[32] = {0};
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, rp_id_hash, 0);
bool resident = false;
@@ -255,6 +235,12 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (options.uv == ptrue) { //4.3
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
if (options.uv == NULL || pinUvAuthParam.present == true) {
uv = false;
}
else {
uv = *options.uv;
}
//if (options.up != NULL) { //4.5
// CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
//}
@@ -263,20 +249,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
//else if (options.up == NULL) //5.7
//rup = ptrue;
if (options.uv != NULL) {
uv = *options.uv;
}
if (options.up != NULL) {
up = *options.up;
}
else {
up = true;
}
}
if (pinUvAuthParam.present == true) { //6.1
int ret = verify(pinUvAuthProtocol,
paut.data,
clientDataHash.data,
clientDataHash.len,
pinUvAuthParam.data);
int ret = verify((uint8_t)pinUvAuthProtocol, paut.data, clientDataHash.data, (uint16_t)clientDataHash.len, pinUvAuthParam.data);
if (ret != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
@@ -303,75 +285,150 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
}
bool silent = (up == false && uv == false);
if (allowList_len > 0) {
for (int e = 0; e < allowList_len; e++) {
for (size_t e = 0; e < allowList_len; e++) {
if (allowList[e].type.present == false || allowList[e].id.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash,
&creds[creds_len]) != 0) {
CBOR_FREE_BYTE_STRING(allowList[e].id);
credential_free(&creds[creds_len]);
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
// Should never happen
credential_free(&creds[creds_len]);
continue;
}
resident = true;
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
break;
}
}
}
else {
creds_len++;
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
credential_free(&creds[creds_len]);
}
else {
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
// Even we provide allowList, we need to check if the credential is resident
if (!resident) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
resident = true;
break;
}
}
if (resident) {
break;
}
}
}
}
}
}
else {
for (int i = 0;
i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST;
i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
int ret = credential_load(file_get_data(ef) + 32,
file_get_size(ef) - 32,
rp_id_hash,
&creds[creds_len]);
int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]);
if (ret != 0) {
credential_free(&creds[creds_len]);
}
else {
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
}
}
resident = true;
}
for (int i = 0; i < creds_len; i++) {
for (size_t i = 0; i < creds_len; i++) {
if (creds[i].present == true) {
if (creds[i].extensions.present == true) {
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED &&
!(flags & FIDO2_AUT_FLAG_UV)) {
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
else {
creds[numberOfCredentials++] = creds[i];
if (numberOfCredentials != i) {
creds[numberOfCredentials++] = creds[i];
}
else {
numberOfCredentials++;
}
}
}
else {
creds[numberOfCredentials++] = creds[i];
if (numberOfCredentials != i) {
creds[numberOfCredentials++] = creds[i];
}
else {
numberOfCredentials++;
}
}
}
}
if (numberOfCredentials == 0) {
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
if (silent && allowList_len > 0) {
for (size_t e = 0; e < allowList_len; e++) {
if (allowList[e].type.present == false || allowList[e].id.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) {
numberOfCredentials++;
}
break;
}
}
}
else {
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
numberOfCredentials++;
}
}
}
}
if (numberOfCredentials == 0) {
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
}
}
for (int i = 0; i < numberOfCredentials; i++) {
for (int j = i + 1; j < numberOfCredentials; j++) {
if (creds[j].creation > creds[i].creation) {
Credential tmp = creds[j];
creds[j] = creds[i];
creds[i] = tmp;
if (!silent) {
for (int i = 0; i < numberOfCredentials; i++) {
for (int j = i + 1; j < numberOfCredentials; j++) {
if (creds[j].creation > creds[i].creation) {
Credential tmp = creds[j];
creds[j] = creds[i];
creds[i] = tmp;
}
}
}
}
@@ -401,15 +458,21 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
if (up == false && uv == false) {
selcred = &creds[0];
if (silent && !resident) {
// Silent authentication, do nothing
}
else {
selcred = &creds[0];
if (resident && allowList_len > 1) {
numberOfCredentials = 1;
}
if (numberOfCredentials > 1) {
asserted = true;
residentx = resident;
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
credential_free(&credsx[i]);
}
for (int i = 0; i < numberOfCredentials; i++) {
credsx[i] = creds[i];
}
numberOfCredentialsx = numberOfCredentials;
@@ -428,120 +491,113 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
flags = flagsx;
selcred = &credsx[credentialCounter];
}
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = fido_load_key(selcred->curve, selcred->id.data, &ekey);
if (ret != 0) {
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
}
uint8_t largeBlobKey[32];
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey);
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
int ret = 0;
uint8_t largeBlobKey[32] = {0};
if (selcred) {
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey);
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
}
}
size_t ext_len = 0;
uint8_t ext[512];
if (extensions.present == true) {
uint8_t ext[512] = {0};
if (selcred && extensions.present == true) {
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
int l = 0;
if (options.up == pfalse) {
extensions.hmac_secret = NULL;
}
if (extensions.hmac_secret != NULL) {
if (extensions.hmac_secret == ptrue) {
l++;
}
if (credBlob == ptrue) {
l++;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (credBlob == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
if (selcred->extensions.credBlob.present == true) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data,
selcred->extensions.credBlob.len));
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
}
if (extensions.thirdPartyPayment == ptrue) {
l++;
}
if (extensions.hmac_secret != NULL) {
if (l > 0) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (credBlob == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
if (selcred->extensions.credBlob.present == true) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data,
selcred->extensions.credBlob.len));
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
}
}
if (extensions.hmac_secret == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
uint8_t sharedSecret[64];
mbedtls_ecp_point Qp;
mbedtls_ecp_point_init(&Qp);
mbedtls_mpi_lset(&Qp.Z, 1);
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
uint8_t sharedSecret[64] = {0};
mbedtls_ecp_point Qp;
mbedtls_ecp_point_init(&Qp);
mbedtls_mpi_lset(&Qp.Z, 1);
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
}
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t cred_random[64] = {0}, *crd = NULL;
ret = credential_derive_hmac_key(selcred->id.data, selcred->id.len, cred_random);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (flags & FIDO2_AUT_FLAG_UV) {
crd = cred_random + 32;
}
else {
crd = cred_random;
}
uint8_t out1[64] = {0}, hmac_res[80] = {0};
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
if ((uint8_t)salt_enc.len == 64 + poff) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
}
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
}
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (extensions.thirdPartyPayment == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "thirdPartyPayment"));
if (selcred->extensions.thirdPartyPayment == ptrue) {
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
else {
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
}
}
int ret = ecdh(hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
mbedtls_ecp_point_free(&Qp);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify(hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, salt_enc.len,
salt_auth.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
}
uint8_t salt_dec[64], poff = (hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
ret = decrypt(hmacSecretPinUvAuthProtocol,
sharedSecret,
salt_enc.data,
salt_enc.len,
salt_dec);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t cred_random[64], *crd = NULL;
ret = credential_derive_hmac_key(selcred->id.data, selcred->id.len, cred_random);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (flags & FIDO2_AUT_FLAG_UV) {
crd = cred_random + 32;
}
else {
crd = cred_random;
}
uint8_t out1[64], hmac_res[80];
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
crd,
32,
salt_dec,
32,
out1);
if (salt_enc.len == 64 + poff) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
crd,
32,
salt_dec + 32,
32,
out1 + 32);
}
encrypt(hmacSecretPinUvAuthProtocol, sharedSecret, out1, salt_enc.len - poff, hmac_res);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
uint32_t ctr = get_sign_counter();
@@ -551,50 +607,86 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
*pa++ = ctr >> 24;
*pa++ = ctr >> 16;
*pa++ = ctr >> 8;
*pa++ = ctr & 0xff;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, ext, ext_len); pa += ext_len;
if (pa - aut_data != aut_data_len) {
if ((size_t)(pa - aut_data) != aut_data_len) {
CBOR_ERROR(CTAP1_ERR_OTHER);
}
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
aut_data,
aut_data_len + clientDataHash.len,
hash);
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_ecp_keypair ekey;
mbedtls_ecp_keypair_init(&ekey);
size_t olen = 0;
ret = mbedtls_ecdsa_write_signature(&ekey,
MBEDTLS_MD_SHA256,
hash,
32,
sig,
sizeof(sig),
&olen,
random_gen,
NULL);
mbedtls_ecdsa_free(&ekey);
if (selcred) {
ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey);
if (ret != 0) {
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
mbedtls_ecp_keypair_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
}
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
}
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
}
#ifdef MBEDTLS_EDDSA_C
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
md = NULL;
}
#endif
if (md != NULL) {
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
}
#ifdef MBEDTLS_EDDSA_C
else {
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL);
}
#endif
}
else {
// Bogus signature
olen = 64;
memset(sig, 0x0B, olen);
}
mbedtls_ecp_keypair_free(&ekey);
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
uint8_t lfields = 3;
if (selcred->opts.present == true && selcred->opts.rk == ptrue) {
if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) {
lfields++;
}
if (numberOfCredentials > 1 && next == false) {
if (numberOfCredentials > 1 && next == false && (!resident || allowList_len <= 1)) {
lfields++;
}
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
lfields++;
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
if (selcred) {
if (resident) {
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
}
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
}
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
@@ -604,7 +696,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, sig, olen));
if (selcred->opts.present == true && selcred->opts.rk == ptrue) {
if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
uint8_t lu = 1;
if (numberOfCredentials > 1 && allowList_len == 0) {
@@ -617,8 +709,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
selcred->userId.len));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
@@ -631,11 +722,11 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
if (numberOfCredentials > 1 && next == false) {
if (numberOfCredentials > 1 && next == false && (!resident || allowList_len <= 1)) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
}
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
}
@@ -643,22 +734,26 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
ctr++;
flash_write_data_to_file(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
low_flash_available();
err:
CBOR_FREE_BYTE_STRING(clientDataHash);
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(rpId);
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(salt_enc);
CBOR_FREE_BYTE_STRING(salt_auth);
if (asserted == false) {
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
credential_free(&creds[i]);
}
}
for (int m = 0; m < allowList_len; m++) {
for (size_t m = 0; m < MAX_CREDENTIAL_COUNT_IN_LIST; m++) {
CBOR_FREE_BYTE_STRING(allowList[m].type);
CBOR_FREE_BYTE_STRING(allowList[m].id);
for (int n = 0; n < allowList[m].transports_len; n++) {
for (size_t n = 0; n < 8; n++) {
CBOR_FREE_BYTE_STRING(allowList[m].transports[n]);
}
}
@@ -671,6 +766,6 @@ err:
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
#include "fido.h"
@@ -26,34 +27,56 @@
int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
uint8_t lfields = 14;
#ifndef ENABLE_EMULATION
if (phy_data.vid != 0x1050) {
lfields++;
}
#else
lfields++;
#endif
file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
if (file_has_data(ef_pin_policy)) {
lfields += 2;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 8));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 9));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "alwaysUv"));
if (file_has_data(ef_pin) && (get_opts() & FIDO2_OPT_AUV || !getUserVerifiedFlagValue())) {
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
}
else {
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
}
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "authnrCfg"));
@@ -89,25 +112,33 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES384));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES512));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
uint8_t curves = 3;
#ifdef MBEDTLS_EDDSA_C
curves++;
#endif
#ifndef ENABLE_EMULATION
if (phy_data.enabled_curves & PHY_CURVE_SECP256K1) {
#endif
curves++;
#ifndef ENABLE_EMULATION
}
#endif
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, curves));
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256, &arrayEncoder, &mapEncoder2));
#ifdef MBEDTLS_EDDSA_C
CBOR_CHECK(COSE_public_key(FIDO2_ALG_EDDSA, &arrayEncoder, &mapEncoder2));
#endif
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES384, &arrayEncoder, &mapEncoder2));
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES512, &arrayEncoder, &mapEncoder2));
#ifndef ENABLE_EMULATION
if (phy_data.enabled_curves & PHY_CURVE_SECP256K1) {
#endif
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256K, &arrayEncoder, &mapEncoder2));
#ifndef ENABLE_EMULATION
}
#endif
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
@@ -134,18 +165,41 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
#ifndef ENABLE_EMULATION
if (phy_data.vid != 0x1050) {
#endif
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
uint8_t enabled_cmds = 4;
#ifndef ENABLE_EMULATION
enabled_cmds += 4;
#endif
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
#ifndef ENABLE_EMULATION
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS));
#endif
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
#ifndef ENABLE_EMULATION
}
if (file_has_data(ef_pin_policy)) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
#endif
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err:
if (error != CborNoError) {
return -CTAP2_ERR_INVALID_CBOR;
}
res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
res_APDU_size = (uint16_t)cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
return 0;
}

View File

@@ -3,25 +3,25 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
#include "hsm.h"
#include "mbedtls/sha256.h"
static uint64_t expectedLength = 0, expectedNextOffset = 0;
@@ -79,7 +79,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (get > 0) {
if (length != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
@@ -129,13 +129,9 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
uint8_t verify_data[70] = { 0 };
memset(verify_data, 0xff, 32);
verify_data[32] = 0x0C;
verify_data[34] = offset & 0xff;
verify_data[35] = offset >> 8;
verify_data[36] = offset >> 16;
verify_data[37] = offset >> 24;
put_uint32_t_le((uint32_t)offset, verify_data + 34);
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
if (verify(pinUvAuthProtocol, paut.data, verify_data, sizeof(verify_data),
pinUvAuthParam.data) != 0) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (!(paut.permissions & CTAP_PERMISSION_LBW)) {
@@ -155,7 +151,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
if (expectedLength > 17 && memcmp(sha, temp_lba + expectedLength - 16, 16) != 0) {
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
}
flash_write_data_to_file(ef_largeblob, temp_lba, expectedLength);
file_put_data(ef_largeblob, temp_lba, (uint16_t)expectedLength);
low_flash_available();
}
goto err;
@@ -168,6 +164,6 @@ err:
if (error != CborNoError) {
return -CTAP2_ERR_INVALID_CBOR;
}
res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
res_APDU_size = (uint16_t)cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
return 0;
}

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "cbor_make_credential.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
@@ -25,7 +26,7 @@
#include "credential.h"
#include "mbedtls/sha256.h"
#include "random.h"
#include "hsm.h"
#include "crypto_utils.h"
int cbor_make_credential(const uint8_t *data, size_t len) {
CborParser parser;
@@ -39,13 +40,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
size_t excludeList_len = 0;
CredOptions options = { 0 };
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1;
int64_t kty = 2, hmac_alg = 0, crv = 0;
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
bool hmac_secret_mc = false;
const bool *pin_complexity_policy = NULL;
uint8_t *aut_data = NULL;
size_t resp_size = 0;
CredExtensions extensions = { 0 };
//options.present = true;
//options.up = ptrue;
//options.uv = pfalse;
options.uv = pfalse;
//options.rk = pfalse;
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
@@ -65,7 +70,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_FIELD_GET_BYTES(clientDataHash, 1);
}
else if (val_u == 0x02) { // rp
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "id", rp.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", rp.parent.name);
@@ -73,7 +79,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x03) { // user
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "id", user.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", user.parent.name);
@@ -83,9 +90,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x04) { // pubKeyCredParams
CBOR_PARSE_ARRAY_START(_f1, 2) {
CBOR_PARSE_ARRAY_START(_f1, 2)
{
PublicKeyCredentialParameters *pk = &pubKeyCredParams[pubKeyCredParams_len];
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_PARSE_MAP_START(_f2, 3)
{
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pk->type);
CBOR_FIELD_KEY_TEXT_VAL_INT(3, "alg", pk->alg);
@@ -96,14 +105,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_ARRAY_END(_f1, 2);
}
else if (val_u == 0x05) { // excludeList
CBOR_PARSE_ARRAY_START(_f1, 2) {
CBOR_PARSE_ARRAY_START(_f1, 2)
{
PublicKeyCredentialDescriptor *pc = &excludeList[excludeList_len];
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_PARSE_MAP_START(_f2, 3)
{
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", pc->id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pc->type);
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) {
CBOR_PARSE_ARRAY_START(_f3, 4)
{
CBOR_FIELD_GET_TEXT(pc->transports[pc->transports_len], 4);
pc->transports_len++;
}
@@ -117,20 +129,50 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
else if (val_u == 0x06) { // extensions
extensions.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
if (strcmp(_fd2, "hmac-secret-mc") == 0) {
hmac_secret_mc = true;
uint64_t ukey = 0;
CBOR_PARSE_MAP_START(_f2, 3)
{
CBOR_FIELD_GET_UINT(ukey, 3);
if (ukey == 0x01) {
CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay));
}
else if (ukey == 0x02) {
CBOR_FIELD_GET_BYTES(salt_enc, 3);
}
else if (ukey == 0x03) {
CBOR_FIELD_GET_BYTES(salt_auth, 3);
}
else if (ukey == 0x04) {
CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
}
else {
CBOR_ADVANCE(3);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
continue;
}
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x07) { // options
options.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", options.rk);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "up", options.up);
@@ -152,48 +194,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_MAP_END(map, 1);
uint8_t flags = FIDO2_AUT_FLAG_AT;
uint8_t rp_id_hash[32];
uint8_t rp_id_hash[32] = {0};
mbedtls_sha256((uint8_t *) rp.id.data, rp.id.len, rp_id_hash, 0);
int curve = -1, alg = 0;
if (pubKeyCredParams_len == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
for (int i = 0; i < pubKeyCredParams_len; i++) {
if (pubKeyCredParams[i].type.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
continue;
}
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
curve = FIDO2_CURVE_P256;
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
curve = FIDO2_CURVE_P384;
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
curve = FIDO2_CURVE_P521;
}
else if (pubKeyCredParams[i].alg == 0) { // no present
curve = -1;
}
else {
curve = 0;
}
if (curve > 0) {
alg = pubKeyCredParams[i].alg;
break;
}
}
if (curve == 0) {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
else if (curve == -1) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false) {
@@ -215,6 +218,87 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
}
int curve = -1, alg = 0;
if (pubKeyCredParams_len == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
for (unsigned int i = 0; i < pubKeyCredParams_len; i++) {
if (pubKeyCredParams[i].type.present == false) {
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
}
if (pubKeyCredParams[i].alg == 0) {
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
}
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
}
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) {
if (curve <= 0) {
curve = FIDO2_CURVE_P256;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) {
if (curve <= 0) {
curve = FIDO2_CURVE_P384;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) {
if (curve <= 0) {
curve = FIDO2_CURVE_P521;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP256R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP384R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP512R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
#ifndef ENABLE_EMULATION
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
#endif
) {
if (curve <= 0) {
curve = FIDO2_CURVE_P256K1;
}
}
#ifdef MBEDTLS_EDDSA_C
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED25519;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED448;
}
}
#endif
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
// pass
}
//else {
// CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
//}
if (curve > 0 && alg == 0) {
alg = (int)pubKeyCredParams[i].alg;
}
}
if (curve <= 0) {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_ALGORITHM);
}
if (options.present) {
if (options.uv == ptrue) { //5.3
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
@@ -225,7 +309,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
//else if (options.up == NULL) //5.7
//rup = ptrue;
}
if (pinUvAuthParam.present == false && options.uv != ptrue && file_has_data(ef_pin)) { //8.1
if (pinUvAuthParam.present == false && options.uv == pfalse && file_has_data(ef_pin)) { //8.1
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (enterpriseAttestation > 0) {
@@ -238,11 +322,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
//Unfinished. See 6.1.2.9
}
if (pinUvAuthParam.present == true) { //11.1
int ret = verify(pinUvAuthProtocol,
paut.data,
clientDataHash.data,
clientDataHash.len,
pinUvAuthParam.data);
int ret = verify((uint8_t)pinUvAuthProtocol, paut.data, clientDataHash.data, (uint16_t)clientDataHash.len, pinUvAuthParam.data);
if (ret != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
@@ -262,20 +342,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
for (int e = 0; e < excludeList_len; e++) { //12.1
for (size_t e = 0; e < excludeList_len; e++) { //12.1
if (excludeList[e].type.present == false || excludeList[e].id.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (strcmp(excludeList[e].type.data, "public-key") != 0) {
if (strcmp(excludeList[e].type.data, (char *)"public-key") != 0) {
continue;
}
Credential ecred;
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
&ecred) == 0 &&
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
(flags & FIDO2_AUT_FLAG_UV))) {
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
Credential ecred = {0};
if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) {
continue;
}
uint8_t *cred_idr = file_get_data(ef_cred) + 32;
if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
credential_free(&ecred);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
}
}
}
else {
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
credential_free(&ecred);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
}
credential_free(&ecred);
}
if (extensions.largeBlobKey == pfalse ||
@@ -283,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
if (hmac_secret_mc && extensions.hmac_secret != ptrue) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (options.up == ptrue || options.up == NULL) { //14.1
if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) {
@@ -292,37 +392,37 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
flags |= FIDO2_AUT_FLAG_UP;
clearUserPresentFlag();
clearUserVerifiedFlag();
clearPinUvAuthTokenPermissionsExceptLbw();
if (options.up == ptrue) {
clearUserPresentFlag();
clearUserVerifiedFlag();
clearPinUvAuthTokenPermissionsExceptLbw();
}
}
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH];
size_t cred_id_len = 0;
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
uint16_t cred_id_len = 0;
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options,
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
cred_id, &cred_id_len));
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
if (getUserVerifiedFlagValue()) {
flags |= FIDO2_AUT_FLAG_UV;
}
size_t ext_len = 0;
uint8_t ext[512];
uint8_t ext[512] = {0};
CborEncoder encoder, mapEncoder, mapEncoder2;
if (extensions.present == true) {
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
int l = 0;
uint8_t minPinLen = 0;
if (extensions.hmac_secret != NULL) {
if (extensions.hmac_secret == ptrue) {
l++;
}
if (extensions.credProtect != 0) {
l++;
}
if (extensions.minPinLength != NULL) {
if (extensions.minPinLength == ptrue) {
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin)) {
uint8_t *minpin_data = file_get_data(ef_minpin);
@@ -340,111 +440,198 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (extensions.credBlob.present == true) {
l++;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credBlob.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder,
extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
if (hmac_secret_mc) {
l++;
}
if (extensions.credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
if (pin_complexity_policy == ptrue) {
l++;
}
if (extensions.hmac_secret != NULL) {
if (l > 0) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credBlob.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
}
if (extensions.credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
}
if (extensions.hmac_secret == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
if (minPinLen > 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
}
if (hmac_secret_mc) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
}
if (minPinLen > 0) {
uint8_t sharedSecret[64] = {0};
mbedtls_ecp_point Qp;
mbedtls_ecp_point_init(&Qp);
mbedtls_mpi_lset(&Qp.Z, 1);
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
mbedtls_ecp_point_free(&Qp);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
}
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t cred_random[64] = {0}, *crd = NULL;
ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (flags & FIDO2_AUT_FLAG_UV) {
crd = cred_random + 32;
}
else {
crd = cred_random;
}
uint8_t out1[64] = {0}, hmac_res[80] = {0};
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
if ((uint8_t)salt_enc.len == 64 + poff) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
}
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
}
if (pin_complexity_policy == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
}
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
uint8_t pkey[66];
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
mbedtls_ecp_keypair ekey;
mbedtls_ecp_keypair_init(&ekey);
int ret = fido_load_key(curve, cred_id, &ekey);
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
mbedtls_ecp_keypair_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
if (cinfo == NULL) {
mbedtls_ecdsa_free(&ekey);
mbedtls_ecp_keypair_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
size_t olen = 0;
uint32_t ctr = get_sign_counter();
uint8_t cbor_buf[1024];
uint8_t cbor_buf[1024] = {0};
cbor_encoder_init(&encoder, cbor_buf, sizeof(cbor_buf), 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, -alg));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, curve));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 2));
mbedtls_mpi_write_binary(&ekey.Q.X, pkey, mbedtls_mpi_size(&ekey.Q.X));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, mbedtls_mpi_size(&ekey.Q.X)));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 3));
mbedtls_mpi_write_binary(&ekey.Q.Y, pkey, mbedtls_mpi_size(&ekey.Q.Y));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, mbedtls_mpi_size(&ekey.Q.Y)));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len;
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
*pa++ = ctr >> 24;
*pa++ = ctr >> 16;
*pa++ = ctr >> 8;
*pa++ = ctr & 0xff;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, aaguid, 16); pa += 16;
*pa++ = cred_id_len >> 8;
*pa++ = cred_id_len & 0xff;
memcpy(pa, cred_id, cred_id_len); pa += cred_id_len;
memcpy(pa, cbor_buf, rs); pa += rs;
memcpy(pa, ext, ext_len); pa += ext_len;
if (pa - aut_data != aut_data_len) {
mbedtls_ecdsa_free(&ekey);
if (options.rk == ptrue) {
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
pa += put_uint16_t_be(sizeof(cred_idr), pa);
credential_derive_resident(cred_id, cred_id_len, cred_idr);
memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr);
}
else {
pa += put_uint16_t_be(cred_id_len, pa);
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
}
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
if ((size_t)(pa - aut_data) != aut_data_len) {
mbedtls_ecp_keypair_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
aut_data,
aut_data_len + clientDataHash.len,
hash);
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
}
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
}
#ifdef MBEDTLS_EDDSA_C
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
md = NULL;
}
#endif
if (md != NULL) {
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
}
bool self_attestation = true;
if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) {
mbedtls_ecdsa_free(&ekey);
mbedtls_ecdsa_init(&ekey);
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
mbedtls_ecp_keypair_free(&ekey);
mbedtls_ecp_keypair_init(&ekey);
uint8_t key[32] = {0};
if (load_keydev(key) != 0) {
CBOR_ERROR(CTAP1_ERR_OTHER);
}
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, key, 32);
mbedtls_platform_zeroize(key, sizeof(key));
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
self_attestation = false;
}
ret = mbedtls_ecdsa_write_signature(&ekey,
MBEDTLS_MD_SHA256,
hash,
32,
sig,
sizeof(sig),
&olen,
random_gen,
NULL);
mbedtls_ecdsa_free(&ekey);
if (md != NULL) {
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
}
#ifdef MBEDTLS_EDDSA_C
else {
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL);
}
#endif
mbedtls_ecp_keypair_free(&ekey);
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
uint8_t largeBlobKey[32];
if (user.id.len > 0 && user.parent.name.len > 0 && user.displayName.len > 0) {
if (memcmp(user.parent.name.data, "+pico", 5) == 0) {
options.rk = pfalse;
#ifndef ENABLE_EMULATION
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
if (memcmp(p, "CommissionProfile", 17) == 0) {
ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data);
if (ret == PICOKEY_OK) {
ret = phy_save();
}
}
#endif
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
}
}
uint8_t largeBlobKey[32] = {0};
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey);
if (ret != 0) {
@@ -452,10 +639,15 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder,
extensions.largeBlobKey == ptrue &&
options.rk == ptrue ? 5 : 4));
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
uint8_t lparams = 3;
if (enterpriseAttestation == 2) {
lparams++;
}
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
lparams++;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lparams));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
@@ -463,13 +655,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2,
self_attestation == false ? 3 : 2));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation ? -alg : -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
if (self_attestation == false) {
if (self_attestation == false || is_nk) {
CborEncoder arrEncoder;
file_t *ef_cert = NULL;
if (enterpriseAttestation == 2) {
@@ -480,14 +671,15 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "x5c"));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder2, &arrEncoder, 1));
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_cert),
file_get_size(ef_cert)));
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_cert), file_get_size(ef_cert)));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder2, &arrEncoder));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2));
if (enterpriseAttestation == 2) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
@@ -503,7 +695,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
ctr++;
flash_write_data_to_file(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
low_flash_available();
err:
CBOR_FREE_BYTE_STRING(clientDataHash);
@@ -513,14 +705,21 @@ err:
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
for (int n = 0; n < pubKeyCredParams_len; n++) {
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(salt_enc);
CBOR_FREE_BYTE_STRING(salt_auth);
if (extensions.present == true) {
CBOR_FREE_BYTE_STRING(extensions.credBlob);
}
for (size_t n = 0; n < MAX_CREDENTIAL_COUNT_IN_LIST; n++) {
CBOR_FREE_BYTE_STRING(pubKeyCredParams[n].type);
}
for (int m = 0; m < excludeList_len; m++) {
for (size_t m = 0; m < MAX_CREDENTIAL_COUNT_IN_LIST; m++) {
CBOR_FREE_BYTE_STRING(excludeList[m].type);
CBOR_FREE_BYTE_STRING(excludeList[m].id);
for (int n = 0; n < excludeList[m].transports_len; n++) {
for (size_t n = 0; n < excludeList[m].transports_len; n++) {
CBOR_FREE_BYTE_STRING(excludeList[m].transports[n]);
}
}
@@ -533,6 +732,6 @@ err:
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,16 +3,16 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _CBOR_MAKE_CREDENTIAL_H_

View File

@@ -3,31 +3,36 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "file.h"
#include "fido.h"
#include "ctap.h"
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ESP_PLATFORM
#include "esp_compat.h"
#endif
#include "fs/phy.h"
extern void scan_all();
int cbor_reset() {
#ifndef ENABLE_EMULATION
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET == 1
if (board_millis() > 10000) {
if (!(phy_data.opts & PHY_OPT_DISABLE_POWER_RESET) && board_millis() > 10000) {
return CTAP2_ERR_NOT_ALLOWED;
}
#endif

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"

View File

@@ -3,25 +3,25 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
#include "hsm.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
@@ -37,14 +37,7 @@ int mse_decrypt_ct(uint8_t *data, size_t len) {
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12);
int ret = mbedtls_chachapoly_auth_decrypt(&chatx,
len - 16,
mse.key_enc,
mse.Qpt,
65,
data + len - 16,
data,
data);
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data);
mbedtls_chachapoly_free(&chatx);
return ret;
}
@@ -84,30 +77,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_FIELD_GET_BYTES(vendorParam, 2);
}
else if (subpara == 0x02) {
int64_t key = 0;
CBOR_PARSE_MAP_START(_f2, 3)
{
CBOR_FIELD_GET_INT(key, 3);
if (key == 1) {
CBOR_FIELD_GET_INT(kty, 3);
}
else if (key == 3) {
CBOR_FIELD_GET_INT(alg, 3);
}
else if (key == -1) {
CBOR_FIELD_GET_INT(crv, 3);
}
else if (key == -2) {
CBOR_FIELD_GET_BYTES(kax, 3);
}
else if (key == -3) {
CBOR_FIELD_GET_BYTES(kay, 3);
}
else {
CBOR_ADVANCE(3);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
CBOR_CHECK(COSE_read_key(&_f2, &kty, &alg, &crv, &kax, &kay));
}
else {
CBOR_ADVANCE(2);
@@ -124,7 +94,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
}
CBOR_PARSE_MAP_END(map, 1);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (cmd == CTAP_VENDOR_BACKUP) {
if (vendorCmd == 0x01) {
@@ -135,8 +105,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc),
file_get_size(ef_keydev_enc)));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc)));
}
else if (vendorCmd == 0x02) {
if (vendorParam.present == false) {
@@ -144,9 +113,9 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
}
uint8_t zeros[32];
memset(zeros, 0, sizeof(zeros));
flash_write_data_to_file(ef_keydev_enc, vendorParam.data, vendorParam.len);
flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
file_put_data(ef_keydev_enc, vendorParam.data, (uint16_t)vendorParam.len);
file_put_data(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
goto err;
}
@@ -163,11 +132,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
mbedtls_ecdh_context hkey;
mbedtls_ecdh_init(&hkey);
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
&hkey.ctx.mbed_ecdh.d,
&hkey.ctx.mbed_ecdh.Q,
random_gen,
NULL);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
@@ -183,37 +148,19 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
uint8_t buf[MBEDTLS_ECP_MAX_BYTES];
size_t olen = 0;
ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp,
&hkey.ctx.mbed_ecdh.Qp,
MBEDTLS_ECP_PF_UNCOMPRESSED,
&olen,
mse.Qpt,
sizeof(mse.Qpt));
ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Qp, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, mse.Qpt,sizeof(mse.Qpt));
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
ret = mbedtls_ecdh_calc_secret(&hkey,
&olen,
buf,
MBEDTLS_ECP_MAX_BYTES,
random_gen,
NULL);
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
mbedtls_platform_zeroize(buf, sizeof(buf));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
NULL,
0,
buf,
olen,
mse.Qpt,
sizeof(mse.Qpt),
mse.key_enc,
sizeof(mse.key_enc));
ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, olen, mse.Qpt, sizeof(mse.Qpt), mse.key_enc, sizeof(mse.key_enc));
mbedtls_platform_zeroize(buf, sizeof(buf));
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
@@ -223,22 +170,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
uint8_t pkey[32];
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(COSE_key_shared(&hkey, &mapEncoder, &mapEncoder2));
mbedtls_ecdh_free(&hkey);
}
}
@@ -261,14 +193,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
size_t keyenc_len = file_get_size(ef_keydev_enc);
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, vendorParam.data);
ret = mbedtls_chachapoly_auth_decrypt(&chatx,
sizeof(keydev_dec),
keyenc,
NULL,
0,
keyenc + keyenc_len - 16,
keyenc + 12,
keydev_dec);
ret = mbedtls_chachapoly_auth_decrypt(&chatx, sizeof(keydev_dec), keyenc, NULL, 0, keyenc + keyenc_len - 16, keyenc + 12, keydev_dec);
mbedtls_chachapoly_free(&chatx);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
@@ -281,10 +206,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
uint8_t buffer[1024];
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1,
&ekey,
file_get_data(ef_keydev),
file_get_size(ef_keydev));
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev));
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP2_ERR_PROCESSING);
@@ -294,27 +216,9 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
#ifndef ENABLE_EMULATION
pico_unique_board_id_t rpiid;
pico_get_unique_board_id(&rpiid);
#else
struct {
uint8_t id[8];
} rpiid = { 0 };
#endif
mbedtls_x509write_csr ctx;
mbedtls_x509write_csr_init(&ctx);
snprintf((char *) buffer,
sizeof(buffer),
"C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %02x%02x%02x%02x%02x%02x%02x%02x",
rpiid.id[0],
rpiid.id[1],
rpiid.id[2],
rpiid.id[3],
rpiid.id[4],
rpiid.id[5],
rpiid.id[6],
rpiid.id[7]);
snprintf((char *) buffer, sizeof(buffer), "C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %s", pico_serial_str);
mbedtls_x509write_csr_set_subject_name(&ctx, (char *) buffer);
mbedtls_pk_context key;
mbedtls_pk_init(&key);
@@ -322,12 +226,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
key.pk_ctx = &ekey;
mbedtls_x509write_csr_set_key(&ctx, &key);
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
mbedtls_x509write_csr_set_extension(&ctx,
"\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04",
0xB,
0,
aaguid,
sizeof(aaguid));
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL);
mbedtls_ecdsa_free(&ekey);
if (ret <= 0) {
@@ -338,16 +237,40 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
}
else if (vendorCmd == 0x02) {
if (vendorParam.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
#ifndef ENABLE_EMULATION
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
if (vendorCmd == 0x01) {
uint16_t opts = 0;
if (file_has_data(ef_phy)) {
uint8_t *pdata = file_get_data(ef_phy);
opts = get_uint16_t_be(pdata + PHY_OPTS);
}
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
if (ef_ee_ea) {
flash_write_data_to_file(ef_ee_ea, vendorParam.data, vendorParam.len);
}
low_flash_available();
goto err;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, opts));
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
}
#endif
else if (cmd == CTAP_VENDOR_MEMORY) {
if (vendorCmd == 0x01) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_free_space()));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_used_space()));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_total_space()));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_num_files()));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_size()));
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
}
else {
@@ -366,7 +289,7 @@ err:
}
return error;
}
res_APDU_size = resp_size;
res_APDU_size = (uint16_t)resp_size;
return 0;
}

View File

@@ -3,20 +3,20 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
@@ -26,7 +26,7 @@
int cmd_authenticate() {
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *) apdu.data;
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *) res_APDU;
//if (scan_files(true) != CCID_OK)
//if (scan_files_fido(true) != PICOKEY_OK)
// return SW_EXEC_ERROR();
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
return SW_WRONG_DATA();
@@ -38,66 +38,55 @@ int cmd_authenticate() {
return SW_CONDITIONS_NOT_SATISFIED();
}
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
mbedtls_ecp_keypair key;
mbedtls_ecp_keypair_init(&key);
int ret = 0;
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
if (credential_verify(tmp_kh, req->keyHandleLen, req->appId) == 0) {
if (credential_verify(tmp_kh, req->keyHandleLen, req->appId, false) == 0) {
ret = fido_load_key(FIDO2_CURVE_P256, req->keyHandle, &key);
}
else {
ret = derive_key(req->appId, false, req->keyHandle, MBEDTLS_ECP_DP_SECP256R1, &key);
if (verify_key(req->appId, req->keyHandle, &key) != 0) {
mbedtls_ecdsa_free(&key);
mbedtls_ecp_keypair_free(&key);
free(tmp_kh);
return SW_INCORRECT_PARAMS();
}
}
free(tmp_kh);
if (ret != CCID_OK) {
mbedtls_ecdsa_free(&key);
if (ret != PICOKEY_OK) {
mbedtls_ecp_keypair_free(&key);
return SW_EXEC_ERROR();
}
if (P1(apdu) == CTAP_AUTH_CHECK_ONLY) {
mbedtls_ecdsa_free(&key);
mbedtls_ecp_keypair_free(&key);
return SW_CONDITIONS_NOT_SATISFIED();
}
resp->flags = 0;
resp->flags |= P1(apdu) == CTAP_AUTH_ENFORCE ? CTAP_AUTH_FLAG_TUP : 0x0;
uint32_t ctr = get_sign_counter();
resp->ctr[0] = ctr >> 24;
resp->ctr[1] = ctr >> 16;
resp->ctr[2] = ctr >> 8;
resp->ctr[3] = ctr & 0xff;
put_uint32_t_be(ctr, resp->ctr);
uint8_t hash[32], sig_base[CTAP_APPID_SIZE + 1 + 4 + CTAP_CHAL_SIZE];
memcpy(sig_base, req->appId, CTAP_APPID_SIZE);
memcpy(sig_base + CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
memcpy(sig_base + CTAP_APPID_SIZE + 1, resp->ctr, 4);
memcpy(sig_base + CTAP_APPID_SIZE + 1 + 4, req->chal, CTAP_CHAL_SIZE);
ret =
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
if (ret != 0) {
mbedtls_ecdsa_free(&key);
mbedtls_ecp_keypair_free(&key);
return SW_EXEC_ERROR();
}
size_t olen = 0;
ret = mbedtls_ecdsa_write_signature(&key,
MBEDTLS_MD_SHA256,
hash,
32,
(uint8_t *) resp->sig,
CTAP_MAX_EC_SIG_SIZE,
&olen,
random_gen,
NULL);
mbedtls_ecdsa_free(&key);
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->sig, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
mbedtls_ecp_keypair_free(&key);
if (ret != 0) {
return SW_EXEC_ERROR();
}
res_APDU_size = 1 + 4 + olen;
res_APDU_size = 1 + 4 + (uint16_t)olen;
ctr++;
flash_write_data_to_file(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
low_flash_available();
return SW_OK();
}

View File

@@ -3,25 +3,26 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
#include "files.h"
#include "hid/ctap_hid.h"
#include "management.h"
const uint8_t u2f_aid[] = {
7,
@@ -31,27 +32,25 @@ const uint8_t u2f_aid[] = {
int u2f_unload();
int u2f_process_apdu();
app_t *u2f_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
if (!memcmp(aid, u2f_aid + 1, MIN(aid_len, u2f_aid[0]))) {
a->aid = u2f_aid;
int u2f_select(app_t *a, uint8_t force) {
(void) force;
if (cap_supported(CAP_U2F)) {
a->process_apdu = u2f_process_apdu;
a->unload = u2f_unload;
return a;
return PICOKEY_OK;
}
return NULL;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
void __attribute__((constructor)) u2f_ctor() {
register_app(u2f_select);
INITIALIZER ( u2f_ctor ) {
register_app(u2f_select, u2f_aid);
}
int u2f_unload() {
return CCID_OK;
return PICOKEY_OK;
}
const uint8_t *bogus_firefox =
(const uint8_t *)
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
const uint8_t *bogus_firefox = (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
const uint8_t *bogus_chrome = (const uint8_t *) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
extern int ctap_error(uint8_t error);
@@ -60,7 +59,7 @@ int cmd_register() {
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *) res_APDU;
resp->registerId = CTAP_REGISTER_ID;
resp->keyHandleLen = KEY_HANDLE_LEN;
//if (scan_files(true) != CCID_OK)
//if (scan_files_fido(true) != PICOKEY_OK)
// return SW_EXEC_ERROR();
if (apdu.nc != CTAP_APPID_SIZE + CTAP_CHAL_SIZE) {
return SW_WRONG_LENGTH();
@@ -70,70 +69,50 @@ int cmd_register() {
}
if (memcmp(req->appId, bogus_firefox,
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
#ifndef ENABLE_EMULATION
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
#else
{ return SW_DATA_INVALID(); }
#endif
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
if (ret != CCID_OK) {
if (ret != PICOKEY_OK) {
mbedtls_ecdsa_free(&key);
return SW_EXEC_ERROR();
}
size_t olen = 0;
ret =
mbedtls_ecp_point_write_binary(&key.grp,
&key.Q,
MBEDTLS_ECP_PF_UNCOMPRESSED,
&olen,
(uint8_t *) &resp->pubKey,
CTAP_EC_POINT_SIZE);
ret = mbedtls_ecp_point_write_binary(&key.grp, &key.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (uint8_t *) &resp->pubKey, CTAP_EC_POINT_SIZE);
mbedtls_ecdsa_free(&key);
if (ret != 0) {
return SW_EXEC_ERROR();
}
size_t ef_certdev_size = file_get_size(ef_certdev);
uint16_t ef_certdev_size = file_get_size(ef_certdev);
memcpy(resp->keyHandleCertSig + KEY_HANDLE_LEN, file_get_data(ef_certdev), ef_certdev_size);
uint8_t hash[32],
sign_base[1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN + CTAP_EC_POINT_SIZE];
uint8_t hash[32], sign_base[1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN + CTAP_EC_POINT_SIZE];
sign_base[0] = CTAP_REGISTER_HASH_ID;
memcpy(sign_base + 1, req->appId, CTAP_APPID_SIZE);
memcpy(sign_base + 1 + CTAP_APPID_SIZE, req->chal, CTAP_CHAL_SIZE);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE, resp->keyHandleCertSig,
KEY_HANDLE_LEN);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN,
(uint8_t *) &resp->pubKey,
CTAP_EC_POINT_SIZE);
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
sign_base,
sizeof(sign_base),
hash);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE, resp->keyHandleCertSig, KEY_HANDLE_LEN);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN, (uint8_t *) &resp->pubKey, CTAP_EC_POINT_SIZE);
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sign_base, sizeof(sign_base), hash);
if (ret != 0) {
return SW_EXEC_ERROR();
}
mbedtls_ecdsa_init(&key);
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), 32);
if (ret != CCID_OK) {
uint8_t key_dev[32] = {0};
ret = load_keydev(key_dev);
if (ret != PICOKEY_OK) {
return SW_EXEC_ERROR();
}
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, key_dev, 32);
mbedtls_platform_zeroize(key_dev, sizeof(key_dev));
if (ret != PICOKEY_OK) {
mbedtls_ecdsa_free(&key);
return SW_EXEC_ERROR();
}
ret = mbedtls_ecdsa_write_signature(&key,
MBEDTLS_MD_SHA256,
hash,
32,
(uint8_t *) resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size,
CTAP_MAX_EC_SIG_SIZE,
&olen,
random_gen,
NULL);
ret = mbedtls_ecdsa_write_signature(&key,MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
mbedtls_ecdsa_free(&key);
if (ret != 0) {
return SW_EXEC_ERROR();
}
res_APDU_size = sizeof(CTAP_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN +
ef_certdev_size + olen;
res_APDU_size = sizeof(CTAP_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN + ef_certdev_size + (uint16_t)olen;
return SW_OK();
}
@@ -152,10 +131,12 @@ int u2f_process_apdu() {
if (CLA(apdu) != 0x00) {
return SW_CLA_NOT_SUPPORTED();
}
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
if (cap_supported(CAP_U2F)) {
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
}
}
}
return SW_INS_NOT_SUPPORTED();

View File

@@ -3,23 +3,23 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "apdu.h"
#include "hsm.h"
#include "pico_keys.h"
int cmd_version() {
memcpy(res_APDU, "U2F_V2", strlen("U2F_V2"));
res_APDU_size = strlen("U2F_V2");
res_APDU_size = (uint16_t)strlen("U2F_V2");
return SW_OK();
}

View File

@@ -3,22 +3,23 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h"
#include "credential.h"
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -26,30 +27,59 @@
#include "ctap.h"
#include "random.h"
#include "files.h"
#include "hsm.h"
#include "otp.h"
int credential_derive_chacha_key(uint8_t *outk);
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
static int credential_silent_tag(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, uint8_t *outk) {
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts(&ctx, 0);
if (otp_key_1) {
mbedtls_sha256_update(&ctx, otp_key_1, 32);
}
else {
mbedtls_sha256_update(&ctx, pico_serial.id, sizeof(pico_serial.id));
}
mbedtls_sha256_update(&ctx, rp_id_hash, 32);
mbedtls_sha256_finish(&ctx, outk);
mbedtls_sha256_free(&ctx);
return mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, cred_id, cred_id_len - CRED_SILENT_TAG_LEN, outk);
}
int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, bool silent) {
if (cred_id_len < 4 + 12 + 16) {
return -1;
}
uint8_t key[32], *iv = cred_id + 4, *cipher = cred_id + 4 + 12,
*tag = cred_id + cred_id_len - 16;
memset(key, 0, sizeof(key));
credential_derive_chacha_key(key);
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, key);
int ret = mbedtls_chachapoly_auth_decrypt(&chatx,
cred_id_len - (4 + 12 + 16),
iv,
rp_id_hash,
32,
tag,
cipher,
cipher);
mbedtls_chachapoly_free(&chatx);
uint8_t key[32] = {0}, *iv = cred_id + CRED_PROTO_LEN, *cipher = cred_id + CRED_PROTO_LEN + CRED_IV_LEN,
*tag = cred_id + cred_id_len - CRED_TAG_LEN;
cred_proto_t proto = CRED_PROTO_21;
if (memcmp(cred_id, CRED_PROTO_22_S, CRED_PROTO_LEN) == 0) { // New format
tag = cred_id + cred_id_len - CRED_SILENT_TAG_LEN - CRED_TAG_LEN;
proto = CRED_PROTO_22;
}
int ret = 0;
if (!silent) {
int hdr_len = CRED_PROTO_LEN + CRED_IV_LEN + CRED_TAG_LEN;
if (proto == CRED_PROTO_22) {
hdr_len += CRED_SILENT_TAG_LEN;
}
credential_derive_chacha_key(key, cred_id);
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, key);
ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - hdr_len, iv, rp_id_hash, 32, tag, cipher, cipher);
mbedtls_chachapoly_free(&chatx);
}
else {
if (proto <= CRED_PROTO_21) {
return -1;
}
uint8_t outk[32];
ret = credential_silent_tag(cred_id, cred_id_len, rp_id_hash, outk);
ret = memcmp(outk, cred_id + cred_id_len - CRED_SILENT_TAG_LEN, CRED_SILENT_TAG_LEN);
}
return ret;
}
@@ -63,7 +93,7 @@ int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
size_t *cred_id_len) {
uint16_t *cred_id_len) {
CborEncoder encoder, mapEncoder, mapEncoder2;
CborError error = CborNoError;
uint8_t rp_id_hash[32];
@@ -83,8 +113,7 @@ int credential_create(CborCharString *rpId,
if (extensions->credBlob.present == true &&
extensions->credBlob.len < MAX_CREDBLOB_LENGTH) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credBlob"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, extensions->credBlob.data,
extensions->credBlob.len));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, extensions->credBlob.data, extensions->credBlob.len));
}
if (extensions->credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect"));
@@ -98,6 +127,10 @@ int credential_create(CborCharString *rpId,
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "largeBlobKey"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true));
}
if (extensions->thirdPartyPayment == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "thirdPartyPayment"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
@@ -117,29 +150,25 @@ int credential_create(CborCharString *rpId,
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
*cred_id_len = 4 + 12 + rs + 16;
uint8_t key[32];
memset(key, 0, sizeof(key));
credential_derive_chacha_key(key);
uint8_t iv[12];
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
uint8_t key[32] = {0};
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
uint8_t iv[CRED_IV_LEN] = {0};
random_gen(NULL, iv, sizeof(iv));
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, key);
int ret = mbedtls_chachapoly_encrypt_and_tag(&chatx,
rs,
iv,
rp_id_hash,
32,
cred_id + 4 + 12,
cred_id + 4 + 12,
cred_id + 4 + 12 + rs);
int ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, rs, iv, rp_id_hash, 32,
cred_id + CRED_PROTO_LEN + CRED_IV_LEN,
cred_id + CRED_PROTO_LEN + CRED_IV_LEN,
cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs);
mbedtls_chachapoly_free(&chatx);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_OTHER);
}
memcpy(cred_id, CRED_PROTO, 4);
memcpy(cred_id + 4, iv, 12);
memcpy(cred_id, CRED_PROTO, CRED_PROTO_LEN);
memcpy(cred_id + CRED_PROTO_LEN, iv, CRED_IV_LEN);
credential_silent_tag(cred_id, *cred_id_len, rp_id_hash, cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN);
err:
if (error != CborNoError) {
@@ -151,15 +180,16 @@ err:
return 0;
}
int credential_load(const uint8_t *cred_id,
size_t cred_id_len,
const uint8_t *rp_id_hash,
Credential *cred) {
int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred) {
int ret = 0;
CborError error = CborNoError;
uint8_t *copy_cred_id = (uint8_t *) calloc(1, cred_id_len);
if (!cred) {
CBOR_ERROR(CTAP2_ERR_INVALID_CREDENTIAL);
}
memset(cred, 0, sizeof(Credential));
memcpy(copy_cred_id, cred_id, cred_id_len);
ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash);
ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash, false);
if (ret != 0) { // U2F?
if (cred_id_len != KEY_HANDLE_LEN || verify_key(rp_id_hash, cred_id, NULL) != 0) {
CBOR_ERROR(CTAP2_ERR_INVALID_CREDENTIAL);
@@ -201,6 +231,7 @@ int credential_load(const uint8_t *cred_id,
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", cred->extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", cred->extensions.thirdPartyPayment);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
@@ -246,14 +277,19 @@ err:
}
void credential_free(Credential *cred) {
CBOR_FREE_BYTE_STRING(cred->rpId);
CBOR_FREE_BYTE_STRING(cred->userId);
CBOR_FREE_BYTE_STRING(cred->userName);
CBOR_FREE_BYTE_STRING(cred->userDisplayName);
CBOR_FREE_BYTE_STRING(cred->id);
cred->present = false;
cred->extensions.present = false;
cred->opts.present = false;
if (cred) {
CBOR_FREE_BYTE_STRING(cred->rpId);
CBOR_FREE_BYTE_STRING(cred->userId);
CBOR_FREE_BYTE_STRING(cred->userName);
CBOR_FREE_BYTE_STRING(cred->userDisplayName);
CBOR_FREE_BYTE_STRING(cred->id);
if (cred->extensions.present) {
CBOR_FREE_BYTE_STRING(cred->extensions.credBlob);
}
cred->present = false;
cred->extensions.present = false;
cred->opts.present = false;
}
}
int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
@@ -266,7 +302,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
credential_free(&cred);
return ret;
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
Credential rcred = { 0 };
if (!file_has_data(ef)) {
@@ -278,13 +314,12 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred);
if (ret != 0) {
credential_free(&rcred);
continue;
}
if (memcmp(rcred.userId.data, cred.userId.data,
MIN(rcred.userId.len, cred.userId.len)) == 0) {
if (memcmp(rcred.userId.data, cred.userId.data, MIN(rcred.userId.len, cred.userId.len)) == 0) {
sloti = i;
credential_free(&rcred);
new_record = false;
@@ -295,16 +330,19 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (sloti == -1) {
return -1;
}
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(cred_id, cred_id_len, cred_idr);
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN);
memcpy(data, rp_id_hash, 32);
memcpy(data + 32, cred_id, cred_id_len);
file_t *ef = file_new(EF_CRED + sloti);
flash_write_data_to_file(ef, data, cred_id_len + 32);
memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN);
memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len);
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
free(data);
if (new_record == true) { //increase rps
sloti = -1;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
ef = search_dynamic_file(EF_RP + i);
if (!file_has_data(ef)) {
if (sloti == -1) {
@@ -320,21 +358,21 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (sloti == -1) {
return -1;
}
ef = search_dynamic_file(EF_RP + sloti);
ef = search_dynamic_file((uint16_t)(EF_RP + sloti));
if (file_has_data(ef)) {
data = (uint8_t *) calloc(1, file_get_size(ef));
memcpy(data, file_get_data(ef), file_get_size(ef));
data[0] += 1;
flash_write_data_to_file(ef, data, file_get_size(ef));
file_put_data(ef, data, file_get_size(ef));
free(data);
}
else {
ef = file_new(EF_RP + sloti);
ef = file_new((uint16_t)(EF_RP + sloti));
data = (uint8_t *) calloc(1, 1 + 32 + cred.rpId.len);
data[0] = 1;
memcpy(data + 1, rp_id_hash, 32);
memcpy(data + 1 + 32, cred.rpId.data, cred.rpId.len);
flash_write_data_to_file(ef, data, 1 + 32 + cred.rpId.len);
file_put_data(ef, data, (uint16_t)(1 + 32 + cred.rpId.len));
free(data);
}
}
@@ -352,13 +390,13 @@ int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "hmac-secret", 11, outk);
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
return 0;
}
int credential_derive_chacha_key(uint8_t *outk) {
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *proto) {
memset(outk, 0, 32);
int r = 0;
if ((r = load_keydev(outk)) != 0) {
@@ -367,7 +405,7 @@ int credential_derive_chacha_key(uint8_t *outk) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) (proto ? proto : (const uint8_t *)CRED_PROTO), CRED_PROTO_LEN, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "Encryption key", 14, outk);
return 0;
}
@@ -381,8 +419,38 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len,
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, outk);
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "largeBlobKey", 12, outk);
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
return 0;
}
int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
memset(outk, 0, CRED_RESIDENT_LEN);
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN;
mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk);
memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN);
outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00;
outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00;
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr);
return 0;
}
bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) {
if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) {
return false;
}
return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0;
}
int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) {
if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) {
return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred);
}
return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred);
}

View File

@@ -3,22 +3,23 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _CREDENTIAL_H_
#define _CREDENTIAL_H_
#include "ctap2_cbor.h"
#include "file.h"
typedef struct CredOptions {
const bool *rk;
@@ -33,6 +34,7 @@ typedef struct CredExtensions {
const bool *minPinLength;
CborByteString credBlob;
const bool *largeBlobKey;
const bool *thirdPartyPayment;
bool present;
} CredExtensions;
@@ -55,9 +57,29 @@ typedef struct Credential {
#define CRED_PROT_UV_OPTIONAL_WITH_LIST 0x02
#define CRED_PROT_UV_REQUIRED 0x03
#define CRED_PROTO "\xf1\xd0\x02\x01"
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
#define CRED_PROTO_23_S "\xf1\xd0\x02\x03"
extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
#define CRED_PROTO CRED_PROTO_22_S
#define CRED_PROTO_LEN 4
#define CRED_IV_LEN 12
#define CRED_TAG_LEN 16
#define CRED_SILENT_TAG_LEN 16
#define CRED_PROTO_RESIDENT CRED_PROTO_23_S
#define CRED_PROTO_RESIDENT_LEN 4
#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 6)
#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32)
typedef enum
{
CRED_PROTO_21 = 0x01,
CRED_PROTO_22 = 0x02,
} cred_proto_t;
extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, bool silent);
extern int credential_create(CborCharString *rpId,
CborByteString *userId,
CborCharString *userName,
@@ -68,7 +90,7 @@ extern int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
size_t *cred_id_len);
uint16_t *cred_id_len);
extern void credential_free(Credential *cred);
extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
extern int credential_load(const uint8_t *cred_id,
@@ -79,5 +101,8 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
size_t cred_id_len,
uint8_t *outk);
extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len);
extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred);
#endif // _CREDENTIAL_H_

View File

@@ -3,31 +3,24 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _CTAP_H_
#define _CTAP_H_
#ifdef _MSC_VER // Windows
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long int uint64_t;
#else
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#endif
#ifdef __cplusplus
extern "C" {
@@ -121,6 +114,14 @@ typedef struct {
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897
#ifndef ENABLE_EMULATION
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f
#endif
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
@@ -128,6 +129,8 @@ typedef struct {
#define CTAP_VENDOR_MSE 0x02
#define CTAP_VENDOR_UNLOCK 0x03
#define CTAP_VENDOR_EA 0x04
#define CTAP_VENDOR_PHY_OPTS 0x05
#define CTAP_VENDOR_MEMORY 0x06
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
@@ -135,6 +138,7 @@ typedef struct {
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
typedef struct mse {
uint8_t Qpt[65];

View File

@@ -3,22 +3,29 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _CTAP2_CBOR_H_
#define _CTAP2_CBOR_H_
#include "cbor.h"
#ifndef ESP_PLATFORM
#include "common.h"
#else
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#endif
#include "mbedtls/ecp.h"
#include "mbedtls/ecdh.h"
extern uint8_t *driver_prepare_response();
extern void driver_exec_finished(size_t size_next);
@@ -54,7 +61,7 @@ extern const bool _btrue, _bfalse;
do \
{ \
error = e; \
printf("Cbor ERROR [%s:%d]: %d\n", __FILE__, __LINE__, e); \
printf("Cbor ERROR [%s:%d]: %x\n", __FILE__, __LINE__, e); \
goto err; \
} while (0)
@@ -152,7 +159,7 @@ typedef struct CborCharString {
#define CBOR_FIELD_GET_KEY_TEXT(_n) \
CBOR_ASSERT(cbor_value_is_text_string(&(_f##_n)) == true); \
char _fd##_n[64]; \
char _fd##_n[64] = {0}; \
size_t _fdl##_n = sizeof(_fd##_n); \
CBOR_CHECK(cbor_value_copy_text_string(&(_f##_n), _fd##_n, &_fdl##_n, &(_f##_n)))
@@ -237,4 +244,16 @@ typedef struct CborCharString {
CBOR_CHECK(cbor_encode_boolean(&(p), v == ptrue ? true : false)); \
} } while (0)
extern CborError COSE_key(mbedtls_ecp_keypair *, CborEncoder *, CborEncoder *);
extern CborError COSE_key_shared(mbedtls_ecdh_context *key,
CborEncoder *mapEncoderParent,
CborEncoder *mapEncoder);
extern CborError COSE_public_key(int alg, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder);
extern CborError COSE_read_key(CborValue *f,
int64_t *kty,
int64_t *alg,
int64_t *crv,
CborByteString *kax,
CborByteString *kay);
#endif //_CTAP2_CBOR_H_

24
src/fido/defs.c Normal file
View File

@@ -0,0 +1,24 @@
/*
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "version.h"
uint8_t PICO_PRODUCT = 2; // Pico FIDO
uint8_t PICO_VERSION_MAJOR = PICO_FIDO_VERSION_MAJOR;
uint8_t PICO_VERSION_MINOR = PICO_FIDO_VERSION_MINOR;

View File

@@ -3,20 +3,21 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "kek.h"
#include "apdu.h"
#include "ctap.h"
#include "files.h"
@@ -24,21 +25,28 @@
#include "random.h"
#include "mbedtls/x509_crt.h"
#include "mbedtls/hkdf.h"
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
#if defined(USB_ITF_CCID)
#include "ccid/ccid.h"
#endif
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include <math.h>
#include "management.h"
#include "hid/ctap_hid.h"
#include "version.h"
#include "crypto_utils.h"
#include "otp.h"
int fido_process_apdu();
int fido_unload();
pinUvAuthToken_t paut = { 0 };
persistentPinUvAuthToken_t ppaut = { 0 };
uint8_t keydev_dec[32];
bool has_keydev_dec = false;
uint8_t session_pin[32] = { 0 };
const uint8_t fido_aid[] = {
8,
@@ -51,25 +59,37 @@ const uint8_t atr_fido[] = {
0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x40
};
app_t *fido_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
if (!memcmp(aid, fido_aid + 1, MIN(aid_len, fido_aid[0]))) {
a->aid = fido_aid;
a->process_apdu = fido_process_apdu;
a->unload = fido_unload;
return a;
}
return NULL;
uint8_t fido_get_version_major() {
return PICO_FIDO_VERSION_MAJOR;
}
uint8_t fido_get_version_minor() {
return PICO_FIDO_VERSION_MINOR;
}
void __attribute__((constructor)) fido_ctor() {
int fido_select(app_t *a, uint8_t force) {
(void) force;
if (cap_supported(CAP_FIDO2)) {
a->process_apdu = fido_process_apdu;
a->unload = fido_unload;
return PICOKEY_OK;
}
return PICOKEY_ERR_FILE_NOT_FOUND;
}
extern uint8_t (*get_version_major)();
extern uint8_t (*get_version_minor)();
INITIALIZER ( fido_ctor ) {
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
ccid_atr = atr_fido;
#endif
register_app(fido_select);
get_version_major = fido_get_version_major;
get_version_minor = fido_get_version_minor;
register_app(fido_select, fido_aid);
}
int fido_unload() {
return CCID_OK;
return PICOKEY_OK;
}
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
@@ -91,10 +111,56 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
else if (curve == FIDO2_CURVE_X448) {
return MBEDTLS_ECP_DP_CURVE448;
}
#ifdef MBEDTLS_EDDSA_C
else if (curve == FIDO2_CURVE_ED25519) {
return MBEDTLS_ECP_DP_ED25519;
}
else if (curve == FIDO2_CURVE_ED448) {
return MBEDTLS_ECP_DP_ED448;
}
#endif
else if (curve == FIDO2_CURVE_BP256R1) {
return MBEDTLS_ECP_DP_BP256R1;
}
else if (curve == FIDO2_CURVE_BP384R1) {
return MBEDTLS_ECP_DP_BP384R1;
}
else if (curve == FIDO2_CURVE_BP512R1) {
return MBEDTLS_ECP_DP_BP512R1;
}
return MBEDTLS_ECP_DP_NONE;
}
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
if (id == MBEDTLS_ECP_DP_SECP256R1) {
return FIDO2_CURVE_P256;
}
else if (id == MBEDTLS_ECP_DP_SECP384R1) {
return FIDO2_CURVE_P384;
}
else if (id == MBEDTLS_ECP_DP_SECP521R1) {
return FIDO2_CURVE_P521;
}
else if (id == MBEDTLS_ECP_DP_SECP256K1) {
return FIDO2_CURVE_P256K1;
}
else if (id == MBEDTLS_ECP_DP_CURVE25519) {
return MBEDTLS_ECP_DP_CURVE25519;
}
else if (id == MBEDTLS_ECP_DP_CURVE448) {
return FIDO2_CURVE_X448;
}
#ifdef MBEDTLS_EDDSA_C
else if (id == MBEDTLS_ECP_DP_ED25519) {
return FIDO2_CURVE_ED25519;
}
else if (id == MBEDTLS_ECP_DP_ED448) {
return FIDO2_CURVE_ED448;
}
#endif
return 0;
}
int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key) {
int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
mbedtls_ecp_group_id mbedtls_curve = fido_curve_to_mbedtls(curve);
if (mbedtls_curve == MBEDTLS_ECP_DP_NONE) {
return CTAP2_ERR_UNSUPPORTED_ALGORITHM;
@@ -102,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key)
uint8_t key_path[KEY_PATH_LEN];
memcpy(key_path, cred_id, KEY_PATH_LEN);
*(uint32_t *) key_path = 0x80000000 | 10022;
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
}
return derive_key(NULL, false, key_path, mbedtls_curve, key);
@@ -115,10 +181,9 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
mbedtls_x509write_crt_set_validity(&ctx, "20220901000000", "20720831235959");
mbedtls_x509write_crt_set_issuer_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
mbedtls_x509write_crt_set_subject_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
mbedtls_mpi serial;
mbedtls_mpi_init(&serial);
mbedtls_mpi_fill_random(&serial, 32, random_gen, NULL);
mbedtls_x509write_crt_set_serial(&ctx, &serial);
uint8_t serial[16];
random_gen(NULL, serial, sizeof(serial));
mbedtls_x509write_crt_set_serial_raw(&ctx, serial, sizeof(serial));
mbedtls_pk_context key;
mbedtls_pk_init(&key);
mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
@@ -133,27 +198,62 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
MBEDTLS_X509_KU_DIGITAL_SIGNATURE |
MBEDTLS_X509_KU_KEY_CERT_SIGN);
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, random_gen, NULL);
mbedtls_x509write_crt_free(&ctx);
/* pk cannot be freed, as it is freed later */
//mbedtls_pk_free(&key);
return ret;
}
int load_keydev(uint8_t *key) {
int load_keydev(uint8_t key[32]) {
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
return CCID_ERR_MEMORY_FATAL;
return PICOKEY_ERR_MEMORY_FATAL;
}
if (has_keydev_dec == true) {
memcpy(key, keydev_dec, sizeof(keydev_dec));
}
else {
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
uint16_t fid_size = file_get_size(ef_keydev);
if (fid_size == 32) {
memcpy(key, file_get_data(ef_keydev), 32);
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
}
else if (fid_size == 33 || fid_size == 61) {
uint8_t format = *file_get_data(ef_keydev);
if (format == 0x01 || format == 0x02) { // Format indicator
if (format == 0x02) {
uint8_t tmp_key[61];
memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key);
if (ret != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
}
else {
memcpy(key, file_get_data(ef_keydev) + 1, 32);
}
uint8_t kbase[32];
derive_kbase(kbase);
int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32);
if (ret != PICOKEY_OK) {
mbedtls_platform_zeroize(kbase, sizeof(kbase));
return PICOKEY_EXEC_ERROR;
}
mbedtls_platform_zeroize(kbase, sizeof(kbase));
}
}
}
//return mkek_decrypt(key, file_get_size(ef_keydev));
return CCID_OK;
return PICOKEY_OK;
}
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *key) {
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) {
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
if (!(k & 0x80000000)) {
return -1;
@@ -169,8 +269,9 @@ int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_con
}
}
uint8_t hmac[32], d[32];
int ret = mbedtls_ecp_write_key(key, d, sizeof(d));
if (key == NULL) {
size_t olen = 0;
int ret = mbedtls_ecp_write_key_ext(key, &olen, d, sizeof(d));
if (key == &ctx) {
mbedtls_ecdsa_free(&ctx);
}
if (ret != 0) {
@@ -179,45 +280,28 @@ int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_con
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, appId, CTAP_APPID_SIZE);
memcpy(key_base + CTAP_APPID_SIZE, keyHandle, KEY_PATH_LEN);
ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
d,
32,
key_base,
sizeof(key_base),
hmac);
ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), d, 32, key_base, sizeof(key_base), hmac);
mbedtls_platform_zeroize(d, sizeof(d));
return memcmp(keyHandle + KEY_PATH_LEN, hmac, sizeof(hmac));
}
int derive_key(const uint8_t *app_id,
bool new_key,
uint8_t *key_handle,
int curve,
mbedtls_ecdsa_context *key) {
uint8_t outk[64] = { 0 };
int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int curve, mbedtls_ecp_keypair *key) {
uint8_t outk[67] = { 0 }; //SECP521R1 key is 66 bytes length
int r = 0;
memset(outk, 0, sizeof(outk));
if ((r = load_keydev(outk)) != CCID_OK) {
if ((r = load_keydev(outk)) != PICOKEY_OK) {
printf("Error loading keydev: %d\n", r);
return r;
}
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
if (new_key == true) {
uint32_t val = 0;
random_gen(NULL, (uint8_t *) &val, sizeof(val));
val |= 0x80000000;
memcpy(&key_handle[i * sizeof(uint32_t)], &val, sizeof(uint32_t));
}
r = mbedtls_hkdf(md_info,
&key_handle[i * sizeof(uint32_t)],
sizeof(uint32_t),
outk,
32,
outk + 32,
32,
outk,
sizeof(outk));
r = mbedtls_hkdf(md_info, &key_handle[i * sizeof(uint32_t)], sizeof(uint32_t), outk, 32, outk + 32, 32, outk, sizeof(outk));
if (r != 0) {
mbedtls_platform_zeroize(outk, sizeof(outk));
return r;
@@ -227,9 +311,7 @@ int derive_key(const uint8_t *app_id,
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, app_id, CTAP_APPID_SIZE);
memcpy(key_base + CTAP_APPID_SIZE, key_handle, KEY_PATH_LEN);
if ((r =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, key_base,
sizeof(key_base), key_handle + 32)) != 0) {
if ((r = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, key_base, sizeof(key_base), key_handle + 32)) != 0) {
mbedtls_platform_zeroize(outk, sizeof(outk));
return r;
}
@@ -240,20 +322,46 @@ int derive_key(const uint8_t *app_id,
if (cinfo == NULL) {
return 1;
}
r = mbedtls_ecp_read_key(curve, key, outk, ceil((float) cinfo->bit_size / 8));
if (cinfo->bit_size % 8 != 0) {
outk[0] >>= 8 - (cinfo->bit_size % 8);
}
r = mbedtls_ecp_read_key(curve, key, outk, (size_t)ceil((float) cinfo->bit_size / 8));
mbedtls_platform_zeroize(outk, sizeof(outk));
if (r != 0) {
return r;
}
#ifdef MBEDTLS_EDDSA_C
if (curve == MBEDTLS_ECP_DP_ED25519) {
return mbedtls_ecp_point_edwards(&key->grp, &key->Q, &key->d, random_gen, NULL);
}
#endif
return mbedtls_ecp_mul(&key->grp, &key->Q, &key->d, &key->grp.G, random_gen, NULL);
}
mbedtls_platform_zeroize(outk, sizeof(outk));
return r;
}
int scan_files() {
int encrypt_keydev_f1(const uint8_t keydev[32]) {
uint8_t kdata[33] = {0};
kdata[0] = 0x01; // Format indicator
memcpy(kdata + 1, keydev, 32);
uint8_t kbase[32];
derive_kbase(kbase);
int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32);
mbedtls_platform_zeroize(kbase, sizeof(kbase));
if (ret != PICOKEY_OK) {
return ret;
}
ret = file_put_data(ef_keydev, kdata, 33);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
low_flash_available();
return ret;
}
int scan_files_fido() {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
ef_mkek = search_by_fid(EF_MKEK, NULL, SPECIFY_EF);
if (ef_keydev) {
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
@@ -265,13 +373,18 @@ int scan_files() {
mbedtls_ecdsa_free(&ecdsa);
return ret;
}
uint8_t kdata[32];
int key_size = mbedtls_mpi_size(&ecdsa.d);
mbedtls_mpi_write_binary(&ecdsa.d, kdata, key_size);
ret = flash_write_data_to_file(ef_keydev, kdata, key_size);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
uint8_t keydev[32] = {0};
size_t key_size = 0;
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
if (ret != 0 || key_size != 32) {
mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
}
encrypt_keydev_f1(keydev);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
if (ret != CCID_OK) {
if (ret != PICOKEY_OK) {
return ret;
}
printf(" done!\n");
@@ -283,13 +396,15 @@ int scan_files() {
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
if (ef_certdev) {
if (!file_has_data(ef_certdev)) {
uint8_t cert[4096];
uint8_t cert[2048], outk[32];
memset(outk, 0, sizeof(outk));
int ret = 0;
if ((ret = load_keydev(outk)) != 0) {
return ret;
}
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1,
&key,
file_get_data(ef_keydev),
file_get_size(ef_keydev));
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, outk, sizeof(outk));
if (ret != 0) {
mbedtls_ecdsa_free(&key);
return ret;
@@ -304,7 +419,7 @@ int scan_files() {
if (ret <= 0) {
return ret;
}
flash_write_data_to_file(ef_certdev, cert + sizeof(cert) - ret, ret);
file_put_data(ef_certdev, cert + sizeof(cert) - ret, (uint16_t)ret);
}
}
else {
@@ -314,7 +429,7 @@ int scan_files() {
if (ef_counter) {
if (!file_has_data(ef_counter)) {
uint32_t v = 0;
flash_write_data_to_file(ef_counter, (uint8_t *) &v, sizeof(v));
file_put_data(ef_counter, (uint8_t *) &v, sizeof(v));
}
}
else {
@@ -326,7 +441,7 @@ int scan_files() {
if (!file_has_data(ef_authtoken)) {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
file_put_data(ef_authtoken, t, sizeof(t));
}
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
@@ -334,34 +449,48 @@ int scan_files() {
else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
}
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
if (ef_pauthtoken) {
if (!file_has_data(ef_pauthtoken)) {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
file_put_data(ef_pauthtoken, t, sizeof(t));
}
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
}
else {
printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
}
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
if (!file_has_data(ef_largeblob)) {
flash_write_data_to_file(ef_largeblob,
(const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c",
17);
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
}
low_flash_available();
return CCID_OK;
return PICOKEY_OK;
}
void scan_all() {
scan_flash();
scan_files();
scan_files_fido();
}
extern void init_otp();
void init_fido() {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
}
bool wait_button_pressed() {
uint32_t val = EV_PRESS_BUTTON;
#ifndef ENABLE_EMULATION
#if defined(ENABLE_UP_BUTTON) && ENABLE_UP_BUTTON == 1
#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
queue_try_add(&card_to_usb_q, &val);
do {
queue_remove_blocking(&usb_to_card_q, &val);
} while (val != EV_BUTTON_PRESSED && val != EV_BUTTON_TIMEOUT);
#endif
#endif
return val == EV_BUTTON_TIMEOUT;
}
@@ -369,21 +498,18 @@ bool wait_button_pressed() {
uint32_t user_present_time_limit = 0;
bool check_user_presence() {
#if defined(ENABLE_UP_BUTTON) && ENABLE_UP_BUTTON == 1
if (user_present_time_limit == 0 ||
user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
if (user_present_time_limit == 0 || user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
if (wait_button_pressed() == true) { //timeout
return false;
}
//user_present_time_limit = board_millis();
}
#endif
return true;
}
uint32_t get_sign_counter() {
uint8_t *caddr = file_get_data(ef_counter);
return (*caddr) | (*(caddr + 1) << 8) | (*(caddr + 2) << 16) | (*(caddr + 3) << 24);
return get_uint32_t_le(caddr);
}
uint8_t get_opts() {
@@ -396,29 +522,49 @@ uint8_t get_opts() {
void set_opts(uint8_t opts) {
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
flash_write_data_to_file(ef, &opts, sizeof(uint8_t));
file_put_data(ef, &opts, sizeof(uint8_t));
low_flash_available();
}
extern int cmd_register();
extern int cmd_authenticate();
extern int cmd_version();
extern int cbor_parse(int, uint8_t *, size_t);
extern void driver_init_hid();
#define CTAP_CBOR 0x10
int cmd_cbor() {
uint8_t *old_buf = res_APDU;
driver_init_hid();
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
if (ret != 0) {
return SW_EXEC_ERROR();
}
res_APDU = old_buf;
res_APDU_size += 1;
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
return SW_OK();
}
static const cmd_t cmds[] = {
{ CTAP_REGISTER, cmd_register },
{ CTAP_AUTHENTICATE, cmd_authenticate },
{ CTAP_VERSION, cmd_version },
{ CTAP_CBOR, cmd_cbor },
{ 0x00, 0x0 }
};
int fido_process_apdu() {
if (CLA(apdu) != 0x00) {
if (CLA(apdu) != 0x00 && CLA(apdu) != 0x80) {
return SW_CLA_NOT_SUPPORTED();
}
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
if (cap_supported(CAP_U2F)) {
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
}
}
}
return SW_INS_NOT_SUPPORTED();

View File

@@ -3,67 +3,76 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _FIDO_H_
#define _FIDO_H_
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#ifndef ESP_PLATFORM
#include "common.h"
#include "mbedtls/ecdsa.h"
#ifndef ENABLE_EMULATION
#include "ctap_hid.h"
#else
#include <stdbool.h>
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
#endif
#include "mbedtls/ecdsa.h"
#ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h"
#endif
#include "hid/ctap_hid.h"
#define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32)
#define KEY_PATH_ENTRIES (KEY_PATH_LEN / sizeof(uint32_t))
#define SHA256_DIGEST_LENGTH (32)
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
extern int scan_files();
extern int scan_files_fido();
extern int derive_key(const uint8_t *app_id,
bool new_key,
uint8_t *key_handle,
int,
mbedtls_ecdsa_context *key);
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *);
mbedtls_ecp_keypair *key);
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *);
extern bool wait_button_pressed();
extern void init_fido();
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key);
extern int load_keydev(uint8_t *key);
extern int encrypt(uint8_t protocol,
const uint8_t *key,
const uint8_t *in,
size_t in_len,
uint8_t *out);
extern int decrypt(uint8_t protocol,
const uint8_t *key,
const uint8_t *in,
size_t in_len,
uint8_t *out);
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key);
extern int load_keydev(uint8_t key[32]);
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256
#define FIDO2_ALG_EDDSA -8 //EdDSA
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
#define FIDO2_ALG_ES256K -47
#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384
#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521
#define FIDO2_ALG_ED448 -53 //EDDSA Ed448
#define FIDO2_ALG_RS256 -257
#define FIDO2_ALG_RS384 -258
#define FIDO2_ALG_RS512 -259
#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1
#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1
#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1
#define FIDO2_CURVE_P256 1
#define FIDO2_CURVE_P384 2
@@ -73,6 +82,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
#define FIDO2_CURVE_ED25519 6
#define FIDO2_CURVE_ED448 7
#define FIDO2_CURVE_P256K1 8
#define FIDO2_CURVE_BP256R1 9
#define FIDO2_CURVE_BP384R1 10
#define FIDO2_CURVE_BP512R1 11
#define FIDO2_AUT_FLAG_UP 0x1
#define FIDO2_AUT_FLAG_UV 0x4
@@ -80,6 +92,7 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
#define FIDO2_AUT_FLAG_ED 0x80
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
#define FIDO2_OPT_AUV 0x02 // User Verification
#define MAX_PIN_RETRIES 8
extern bool getUserPresentFlagValue();
@@ -123,13 +136,19 @@ typedef struct pinUvAuthToken {
bool user_verified;
} pinUvAuthToken_t;
typedef struct persistentPinUvAuthToken {
uint8_t *data;
size_t len;
uint8_t permissions;
} persistentPinUvAuthToken_t;
extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut;
extern int verify(uint8_t protocol,
const uint8_t *key,
const uint8_t *data,
size_t len,
uint8_t *sign);
extern persistentPinUvAuthToken_t ppaut;
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
extern uint8_t session_pin[32];
#endif //_FIDO_H

View File

@@ -3,51 +3,36 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "files.h"
file_t file_entries[] = {
{ .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL,
.ef_structure = 0, .acl = { 0 } }, // MF
{ .fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH,
.data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key
{ .fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key Enc
{ .fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH,
.data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Certificate Device
{ .fid = EF_EE_DEV_EA, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Enterprise Attestation Certificate
{ .fid = EF_COUNTER, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH,
.data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH,
.data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL,
.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL,
.ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL,
.ef_structure = 0, .acl = { 0 } } //end
{ .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = { 0 } }, // MF
{ .fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key
{ .fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL,.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key Enc
{ .fid = EF_MKEK, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MKEK
{ .fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Certificate Device
{ .fid = EF_EE_DEV_EA, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Enterprise Attestation Certificate
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
{ .fid = EF_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT AUTH TOKEN
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
{ .fid = EF_OTP_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } },
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_NOT_KNOWN, .data = NULL, .ef_structure = 0, .acl = { 0 } } //end
};
const file_t *MF = &file_entries[0];
@@ -59,3 +44,4 @@ file_t *ef_pin = NULL;
file_t *ef_authtoken = NULL;
file_t *ef_keydev_enc = NULL;
file_t *ef_largeblob = NULL;
file_t *ef_mkek = NULL;

View File

@@ -3,16 +3,16 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _FILES_H_
@@ -22,13 +22,17 @@
#define EF_KEY_DEV 0xCC00
#define EF_KEY_DEV_ENC 0xCC01
#define EF_MKEK 0xCC0F
#define EF_EE_DEV 0xCE00
#define EF_EE_DEV_EA 0xCE01
#define EF_COUNTER 0xC000
#define EF_OPTS 0xC001
#define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090
#define EF_PAUTHTOKEN 0x1091
#define EF_MINPINLEN 0x1100
#define EF_PIN_COMPLEXITY_POLICY 0x1102
#define EF_DEV_CONF 0x1122
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
#define EF_LARGEBLOB 0x1101 // Large Blob Array
@@ -36,6 +40,7 @@
#define EF_OATH_CODE 0xBAFF
#define EF_OTP_SLOT1 0xBB00
#define EF_OTP_SLOT2 0xBB01
#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN
extern file_t *ef_keydev;
extern file_t *ef_certdev;
@@ -44,5 +49,6 @@ extern file_t *ef_pin;
extern file_t *ef_authtoken;
extern file_t *ef_keydev_enc;
extern file_t *ef_largeblob;
extern file_t *ef_mkek;
#endif //_FILES_H_

97
src/fido/kek.c Normal file
View File

@@ -0,0 +1,97 @@
/*
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "stdlib.h"
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "kek.h"
#include "crypto_utils.h"
#include "random.h"
#include "mbedtls/md.h"
#include "mbedtls/cmac.h"
#include "mbedtls/rsa.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/chachapoly.h"
#include "files.h"
#include "otp.h"
extern uint8_t session_pin[32];
uint8_t mkek_mask[MKEK_KEY_SIZE];
bool has_mkek_mask = false;
#define POLY 0xedb88320
uint32_t crc32c(const uint8_t *buf, size_t len) {
uint32_t crc = 0xffffffff;
while (len--) {
crc ^= *buf++;
for (int k = 0; k < 8; k++) {
crc = (crc >> 1) ^ (POLY & (0 - (crc & 1)));
}
}
return ~crc;
}
void mkek_masked(uint8_t *mkek, const uint8_t *mask) {
if (mask) {
for (int i = 0; i < MKEK_KEY_SIZE; i++) {
MKEK_KEY(mkek)[i] ^= mask[i];
}
}
}
int load_mkek(uint8_t *mkek) {
file_t *tf = search_file(EF_MKEK);
if (file_has_data(tf)) {
memcpy(mkek, file_get_data(tf), MKEK_SIZE);
}
if (has_mkek_mask) {
mkek_masked(mkek, mkek_mask);
}
if (file_get_size(tf) == MKEK_SIZE) {
int ret = aes_decrypt_cfb_256(session_pin, MKEK_IV(mkek), MKEK_KEY(mkek), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
if (ret != 0) {
return PICOKEY_EXEC_ERROR;
}
if (crc32c(MKEK_KEY(mkek), MKEK_KEY_SIZE) != *(uint32_t *) MKEK_CHECKSUM(mkek)) {
return PICOKEY_WRONG_DKEK;
}
if (otp_key_1) {
mkek_masked(mkek, otp_key_1);
}
}
return PICOKEY_OK;
}
void release_mkek(uint8_t *mkek) {
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
}
int mkek_decrypt(uint8_t *data, uint16_t len) {
int r;
uint8_t mkek[MKEK_SIZE + 4];
if ((r = load_mkek(mkek)) != PICOKEY_OK) {
return r;
}
r = aes_decrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
release_mkek(mkek);
return r;
}

46
src/fido/kek.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _KEK_H_
#define _KEK_H_
#include "crypto_utils.h"
#if defined(ENABLE_EMULATION) || defined(ESP_PLATFORM)
#include <stdbool.h>
#endif
extern int load_mkek(uint8_t *);
extern int store_mkek(const uint8_t *);
extern void init_mkek();
extern void release_mkek(uint8_t *);
extern int mkek_encrypt(uint8_t *data, uint16_t len);
extern int mkek_decrypt(uint8_t *data, uint16_t len);
#define MKEK_IV_SIZE (IV_SIZE)
#define MKEK_KEY_SIZE (32)
#define MKEK_KEY_CS_SIZE (4)
#define MKEK_SIZE (MKEK_IV_SIZE + MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE)
#define MKEK_IV(p) (p)
#define MKEK_KEY(p) (MKEK_IV(p) + MKEK_IV_SIZE)
#define MKEK_CHECKSUM(p) (MKEK_KEY(p) + MKEK_KEY_SIZE)
#define DKEK_KEY_SIZE (32)
extern uint8_t mkek_mask[MKEK_KEY_SIZE];
extern bool has_mkek_mask;
#endif

View File

@@ -3,18 +3,19 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap2_cbor.h"

View File

@@ -3,22 +3,25 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "version.h"
#include "files.h"
#include "asn1.h"
#include "management.h"
int man_process_apdu();
int man_unload();
@@ -27,66 +30,125 @@ const uint8_t man_aid[] = {
8,
0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17
};
app_t *man_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
if (!memcmp(aid, man_aid + 1, MIN(aid_len, man_aid[0]))) {
a->aid = man_aid;
a->process_apdu = man_process_apdu;
a->unload = man_unload;
sprintf((char *)res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
res_APDU_size = strlen((char *)res_APDU);
apdu.ne = res_APDU_size;
return a;
extern void scan_all();
extern void init_otp();
int man_select(app_t *a, uint8_t force) {
a->process_apdu = man_process_apdu;
a->unload = man_unload;
sprintf((char *) res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
res_APDU_size = (uint16_t)strlen((char *) res_APDU);
apdu.ne = res_APDU_size;
if (force) {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
}
return NULL;
return PICOKEY_OK;
}
void __attribute__((constructor)) man_ctor() {
register_app(man_select);
INITIALIZER ( man_ctor ) {
register_app(man_select, man_aid);
}
int man_unload() {
return CCID_OK;
return PICOKEY_OK;
}
bool cap_supported(uint16_t cap) {
file_t *ef = search_dynamic_file(EF_DEV_CONF);
if (file_has_data(ef)) {
uint16_t tag = 0x0;
uint8_t *tag_data = NULL, *p = NULL;
uint16_t tag_len = 0;
asn1_ctx_t ctxi;
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
while (walk_tlv(&ctxi, &p, &tag, &tag_len, &tag_data)) {
if (tag == TAG_USB_ENABLED) {
uint16_t ecaps = tag_data[0];
if (tag_len == 2) {
ecaps = get_uint16_t_be(tag_data);
}
return ecaps & cap;
}
}
}
return true;
}
static uint8_t _openpgp_aid[] = {
6,
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
};
static uint8_t _piv_aid[] = {
5,
0xA0, 0x00, 0x00, 0x03, 0x8,
};
int man_get_config() {
file_t *ef = search_dynamic_file(EF_DEV_CONF);
res_APDU_size = 0;
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
res_APDU[res_APDU_size++] = 0x01;
res_APDU[res_APDU_size++] = TAG_USB_SUPPORTED;
res_APDU[res_APDU_size++] = 2;
res_APDU[res_APDU_size++] = 0x02;
res_APDU[res_APDU_size++] = 0x01 | 0x02 | 0x20;
res_APDU[res_APDU_size++] = 0x02;
uint16_t caps = CAP_FIDO2 | CAP_OTP | CAP_U2F | CAP_OATH;
if (app_exists(_openpgp_aid + 1, _openpgp_aid[0])) {
caps |= CAP_OPENPGP;
}
if (app_exists(_piv_aid + 1, _piv_aid[0])) {
caps |= CAP_PIV;
}
res_APDU[res_APDU_size++] = caps >> 8;
res_APDU[res_APDU_size++] = caps & 0xFF;
res_APDU[res_APDU_size++] = TAG_SERIAL;
res_APDU[res_APDU_size++] = 4;
#ifndef ENABLE_EMULATION
pico_get_unique_board_id_string((char *) res_APDU + res_APDU_size, 4);
#endif
memcpy(res_APDU + res_APDU_size, pico_serial.id, 4);
res_APDU[res_APDU_size] &= ~0xFC; // Force 8-digit serial number
res_APDU_size += 4;
res_APDU[res_APDU_size++] = 0x03;
res_APDU[res_APDU_size++] = 2;
res_APDU[res_APDU_size++] = 0x02;
res_APDU[res_APDU_size++] = 0x01 | 0x02 | 0x20;
res_APDU[res_APDU_size++] = 0x04;
res_APDU[res_APDU_size++] = TAG_FORM_FACTOR;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x01;
res_APDU[res_APDU_size++] = 0x05;
res_APDU[res_APDU_size++] = TAG_VERSION;
res_APDU[res_APDU_size++] = 3;
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
res_APDU[res_APDU_size++] = 0;
res_APDU[res_APDU_size++] = 0x08;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x80;
res_APDU[res_APDU_size++] = 0x0A;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x00;
res_APDU[res_APDU_size++] = 0x0D;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x00;
res_APDU[res_APDU_size++] = 0x0E;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x00;
res_APDU[0] = res_APDU_size - 1;
if (!file_has_data(ef)) {
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
res_APDU[res_APDU_size++] = 2;
caps = 0;
if (cap_supported(CAP_FIDO2)) {
caps |= CAP_FIDO2;
}
if (cap_supported(CAP_OTP)) {
caps |= CAP_OTP;
}
if (cap_supported(CAP_U2F)) {
caps |= CAP_U2F;
}
if (cap_supported(CAP_OATH)) {
caps |= CAP_OATH;
}
if (cap_supported(CAP_OPENPGP)) {
caps |= CAP_OPENPGP;
}
if (cap_supported(CAP_PIV)) {
caps |= CAP_PIV;
}
res_APDU[res_APDU_size++] = caps >> 8;
res_APDU[res_APDU_size++] = caps & 0xFF;
res_APDU[res_APDU_size++] = TAG_DEVICE_FLAGS;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = FLAG_EJECT;
res_APDU[res_APDU_size++] = TAG_CONFIG_LOCK;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x00;
}
else {
memcpy(res_APDU + res_APDU_size, file_get_data(ef), file_get_size(ef));
res_APDU_size += file_get_size(ef);
}
res_APDU[0] = (uint8_t)(res_APDU_size - 1);
return 0;
}
@@ -95,10 +157,39 @@ int cmd_read_config() {
return SW_OK();
}
int cmd_write_config() {
if (apdu.data[0] != apdu.nc - 1) {
return SW_WRONG_DATA();
}
file_t *ef = file_new(EF_DEV_CONF);
file_put_data(ef, apdu.data + 1, (uint16_t)(apdu.nc - 1));
low_flash_available();
#ifndef ENABLE_EMULATION
if (cap_supported(CAP_OTP)) {
phy_data.enabled_usb_itf |= PHY_USB_ITF_KB;
}
else {
phy_data.enabled_usb_itf &= ~PHY_USB_ITF_KB;
}
phy_save();
#endif
return SW_OK();
}
extern int cbor_reset();
int cmd_factory_reset() {
cbor_reset();
return SW_OK();
}
#define INS_READ_CONFIG 0x1D
#define INS_WRITE_CONFIG 0x1C
#define INS_RESET 0x1E // Reset device
static const cmd_t cmds[] = {
{ INS_READ_CONFIG, cmd_read_config },
{ INS_WRITE_CONFIG, cmd_write_config },
{ INS_RESET, cmd_factory_reset },
{ 0x00, 0x0 }
};

55
src/fido/management.h Normal file
View File

@@ -0,0 +1,55 @@
/*
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef _MANAGEMENT_H_
#define _MANAGEMENT_H_
#include <stdlib.h>
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#define TAG_USB_SUPPORTED 0x01
#define TAG_SERIAL 0x02
#define TAG_USB_ENABLED 0x03
#define TAG_FORM_FACTOR 0x04
#define TAG_VERSION 0x05
#define TAG_AUTO_EJECT_TIMEOUT 0x06
#define TAG_CHALRESP_TIMEOUT 0x07
#define TAG_DEVICE_FLAGS 0x08
#define TAG_APP_VERSIONS 0x09
#define TAG_CONFIG_LOCK 0x0A
#define TAG_UNLOCK 0x0B
#define TAG_REBOOT 0x0C
#define TAG_NFC_SUPPORTED 0x0D
#define TAG_NFC_ENABLED 0x0E
#define CAP_OTP 0x01
#define CAP_U2F 0x02
#define CAP_FIDO2 0x200
#define CAP_OATH 0x20
#define CAP_PIV 0x10
#define CAP_OPENPGP 0x08
#define CAP_HSMAUTH 0x100
#define FLAG_REMOTE_WAKEUP 0x40
#define FLAG_EJECT 0x80
extern bool cap_supported(uint16_t cap);
extern int man_get_config();
#endif //_MANAGEMENT_H

View File

@@ -3,28 +3,32 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
#include "version.h"
#include "asn1.h"
#include "crypto_utils.h"
#include "management.h"
extern bool is_nk;
#define MAX_OATH_CRED 255
#define CHALLENGE_LEN 8
#define MAX_OTP_COUNTER 3
#define TAG_NAME 0x71
#define TAG_NAME_LIST 0x72
@@ -34,10 +38,17 @@
#define TAG_T_RESPONSE 0x76
#define TAG_NO_RESPONSE 0x77
#define TAG_PROPERTY 0x78
#define TAG_VERSION 0x79
#define TAG_T_VERSION 0x79
#define TAG_IMF 0x7a
#define TAG_ALGO 0x7b
#define TAG_TOUCH_RESPONSE 0x7c
#define TAG_PASSWORD 0x80
#define TAG_NEW_PASSWORD 0x81
#define TAG_PIN_COUNTER 0x82
#define TAG_PWS_LOGIN 0x83
#define TAG_PWS_PASSWORD 0x84
#define TAG_PWS_METADATA 0x85
#define TAG_SERIAL_NUMBER 0x8F
#define ALG_HMAC_SHA1 0x01
#define ALG_HMAC_SHA256 0x02
@@ -50,6 +61,7 @@
#define PROP_INC 0x01
#define PROP_TOUCH 0x02
#define PROP_PIN 0x03
int oath_process_apdu();
int oath_unload();
@@ -62,25 +74,20 @@ const uint8_t oath_aid[] = {
0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01
};
app_t *oath_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
if (!memcmp(aid, oath_aid + 1, MIN(aid_len, oath_aid[0]))) {
a->aid = oath_aid;
int oath_select(app_t *a, uint8_t force) {
(void) force;
if (cap_supported(CAP_OATH)) {
a->process_apdu = oath_process_apdu;
a->unload = oath_unload;
res_APDU_size = 0;
res_APDU[res_APDU_size++] = TAG_VERSION;
res_APDU[res_APDU_size++] = TAG_T_VERSION;
res_APDU[res_APDU_size++] = 3;
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
res_APDU[res_APDU_size++] = 0;
res_APDU[res_APDU_size++] = TAG_NAME;
res_APDU[res_APDU_size++] = 8;
#ifndef ENABLE_EMULATION
pico_get_unique_board_id((pico_unique_board_id_t *) (res_APDU + res_APDU_size));
res_APDU_size += 8;
#else
memset(res_APDU + res_APDU_size, 0, 8); res_APDU_size += 8;
#endif
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); res_APDU_size += 8;
if (file_has_data(search_dynamic_file(EF_OATH_CODE)) == true) {
random_gen(NULL, challenge, sizeof(challenge));
res_APDU[res_APDU_size++] = TAG_CHALLENGE;
@@ -88,29 +95,42 @@ app_t *oath_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge));
res_APDU_size += sizeof(challenge);
}
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
if (file_has_data(ef_otp_pin)) {
const uint8_t *pin_data = file_get_data(ef_otp_pin);
res_APDU[res_APDU_size++] = TAG_PIN_COUNTER;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = *pin_data;
}
res_APDU[res_APDU_size++] = TAG_ALGO;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
if (is_nk) {
res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
res_APDU[res_APDU_size++] = 8;
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
res_APDU_size += 8;
}
apdu.ne = res_APDU_size;
return a;
return PICOKEY_OK;
}
return NULL;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
void __attribute__((constructor)) oath_ctor() {
register_app(oath_select);
INITIALIZER ( oath_ctor ) {
register_app(oath_select, oath_aid);
}
int oath_unload() {
return CCID_OK;
return PICOKEY_OK;
}
file_t *find_oath_cred(const uint8_t *name, size_t name_len) {
size_t ef_tag_len = 0;
uint8_t *ef_tag_data = NULL;
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file(EF_OATH_CRED + i);
if (file_has_data(ef) &&
asn1_find_tag(file_get_data(ef), file_get_size(ef), TAG_NAME, &ef_tag_len,
&ef_tag_data) == true && ef_tag_len == name_len &&
memcmp(ef_tag_data, name, name_len) == 0) {
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
asn1_ctx_t ctxi, ef_tag = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (file_has_data(ef) && asn1_find_tag(&ctxi, TAG_NAME, &ef_tag) == true && ef_tag.len == name_len && memcmp(ef_tag.data, name, name_len) == 0) {
return ef;
}
}
@@ -121,40 +141,40 @@ int cmd_put() {
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
size_t key_len = 0, imf_len = 0, name_len = 0;
uint8_t *key = NULL, *imf = NULL, *name = NULL;
if (asn1_find_tag(apdu.data, apdu.nc, TAG_KEY, &key_len, &key) == false) {
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, imf = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_NAME, &name_len, &name) == false) {
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_INCORRECT_PARAMS();
}
if ((key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
if (asn1_find_tag(apdu.data, apdu.nc, TAG_IMF, &imf_len, &imf) == false) {
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
if (asn1_find_tag(&ctxi, TAG_IMF, &imf) == false) {
memcpy(apdu.data + apdu.nc, "\x7a\x08\x00\x00\x00\x00\x00\x00\x00\x00", 10);
apdu.nc += 10;
}
else { //prepend zero-valued bytes
if (imf_len < 8) {
memmove(imf + (8 - imf_len), imf, imf_len);
memset(imf, 0, 8 - imf_len);
*(imf - 1) = 8;
apdu.nc += (8 - imf_len);
if (imf.len < 8) {
memmove(imf.data + (8 - imf.len), imf.data, imf.len);
memset(imf.data, 0, 8 - imf.len);
*(imf.data - 1) = 8;
apdu.nc += (8 - imf.len);
}
}
}
file_t *ef = find_oath_cred(name, name_len);
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef)) {
flash_write_data_to_file(ef, apdu.data, apdu.nc);
file_put_data(ef, apdu.data, (uint16_t)apdu.nc);
low_flash_available();
}
else {
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file(EF_OATH_CRED + i);
if (!file_has_data(ef)) {
ef = file_new(EF_OATH_CRED + i);
flash_write_data_to_file(ef, apdu.data, apdu.nc);
file_t *tef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (!file_has_data(tef)) {
tef = file_new((uint16_t)(EF_OATH_CRED + i));
file_put_data(tef, apdu.data, (uint16_t)apdu.nc);
low_flash_available();
return SW_OK();
}
@@ -166,13 +186,13 @@ int cmd_put() {
int cmd_delete() {
size_t tag_len = 0;
uint8_t *tag_data = NULL;
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_NAME, &tag_len, &tag_data) == true) {
file_t *ef = find_oath_cred(tag_data, tag_len);
asn1_ctx_t ctxi, ctxo = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &ctxo) == true) {
file_t *ef = find_oath_cred(ctxo.data, ctxo.len);
if (ef) {
delete_file(ef);
return SW_OK();
@@ -204,38 +224,38 @@ int cmd_set_code() {
validated = true;
return SW_OK();
}
size_t key_len = 0, chal_len = 0, resp_len = 0;
uint8_t *key = NULL, *chal = NULL, *resp = NULL;
if (asn1_find_tag(apdu.data, apdu.nc, TAG_KEY, &key_len, &key) == false) {
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
return SW_INCORRECT_PARAMS();
}
if (key_len == 0) {
if (key.len == 0) {
delete_file(search_dynamic_file(EF_OATH_CODE));
validated = true;
return SW_OK();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_CHALLENGE, &chal_len, &chal) == false) {
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_RESPONSE, &resp_len, &resp) == false) {
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
return SW_INCORRECT_PARAMS();
}
const mbedtls_md_info_t *md_info = get_oath_md_info(key[0]);
const mbedtls_md_info_t *md_info = get_oath_md_info(key.data[0]);
if (md_info == NULL) {
return SW_INCORRECT_PARAMS();
}
uint8_t hmac[64];
int r = mbedtls_md_hmac(md_info, key + 1, key_len - 1, chal, chal_len, hmac);
int r = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, chal.data, chal.len, hmac);
if (r != 0) {
return SW_EXEC_ERROR();
}
if (memcmp(hmac, resp, resp_len) != 0) {
if (memcmp(hmac, resp.data, resp.len) != 0) {
return SW_DATA_INVALID();
}
random_gen(NULL, challenge, sizeof(challenge));
file_t *ef = file_new(EF_OATH_CODE);
flash_write_data_to_file(ef, key, key_len);
file_put_data(ef, key.data, key.len);
low_flash_available();
validated = false;
return SW_OK();
@@ -246,34 +266,43 @@ int cmd_reset() {
return SW_INCORRECT_P1P2();
}
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file(EF_OATH_CRED + i);
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
delete_file(ef);
}
}
delete_file(search_dynamic_file(EF_OATH_CODE));
flash_clear_file(search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF));
low_flash_available();
validated = true;
return SW_OK();
}
int cmd_list() {
size_t name_len = 0, key_len = 0;
uint8_t *name = NULL, *key = NULL;
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file(EF_OATH_CRED + i);
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
uint8_t *data = file_get_data(ef);
size_t data_len = file_get_size(ef);
if (asn1_find_tag(data, data_len, TAG_NAME, &name_len,
&name) == true &&
asn1_find_tag(data, data_len, TAG_KEY, &key_len, &key) == true) {
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
res_APDU[res_APDU_size++] = name_len + 1;
res_APDU[res_APDU_size++] = key[0];
memcpy(res_APDU + res_APDU_size, name, name_len); res_APDU_size += name_len;
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0));
res_APDU[res_APDU_size++] = key.data[0];
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
if (ext) {
uint8_t props = 0x0;
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
props |= 0x4;
}
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
props |= 0x1;
}
res_APDU[res_APDU_size++] = props;
}
}
}
}
@@ -282,12 +311,12 @@ int cmd_list() {
}
int cmd_validate() {
size_t chal_len = 0, resp_len = 0, key_len = 0;
uint8_t *chal = NULL, *resp = NULL, *key = NULL;
if (asn1_find_tag(apdu.data, apdu.nc, TAG_CHALLENGE, &chal_len, &chal) == false) {
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_RESPONSE, &resp_len, &resp) == false) {
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
return SW_INCORRECT_PARAMS();
}
file_t *ef = search_dynamic_file(EF_OATH_CODE);
@@ -295,21 +324,21 @@ int cmd_validate() {
validated = true;
return SW_DATA_INVALID();
}
key = file_get_data(ef);
key_len = file_get_size(ef);
const mbedtls_md_info_t *md_info = get_oath_md_info(key[0]);
key.data = file_get_data(ef);
key.len = file_get_size(ef);
const mbedtls_md_info_t *md_info = get_oath_md_info(key.data[0]);
if (md_info == NULL) {
return SW_INCORRECT_PARAMS();
}
uint8_t hmac[64];
int ret = mbedtls_md_hmac(md_info, key + 1, key_len - 1, challenge, sizeof(challenge), hmac);
int ret = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, challenge, sizeof(challenge), hmac);
if (ret != 0) {
return SW_EXEC_ERROR();
}
if (memcmp(hmac, resp, resp_len) != 0) {
if (memcmp(hmac, resp.data, resp.len) != 0) {
return SW_DATA_INVALID();
}
ret = mbedtls_md_hmac(md_info, key + 1, key_len - 1, chal, chal_len, hmac);
ret = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, chal.data, chal.len, hmac);
if (ret != 0) {
return SW_EXEC_ERROR();
}
@@ -322,11 +351,7 @@ int cmd_validate() {
return SW_OK();
}
int calculate_oath(uint8_t truncate,
const uint8_t *key,
size_t key_len,
const uint8_t *chal,
size_t chal_len) {
int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const uint8_t *chal, size_t chal_len) {
const mbedtls_md_info_t *md_info = get_oath_md_info(key[0]);
if (md_info == NULL) {
return SW_INCORRECT_PARAMS();
@@ -335,7 +360,7 @@ int calculate_oath(uint8_t truncate,
int r = mbedtls_md_hmac(md_info, key + 2, key_len - 2, chal, chal_len, hmac);
size_t hmac_size = mbedtls_md_get_size(md_info);
if (r != 0) {
return CCID_EXEC_ERROR;
return PICOKEY_EXEC_ERROR;
}
if (truncate == 0x01) {
res_APDU[res_APDU_size++] = 4 + 1;
@@ -347,77 +372,62 @@ int calculate_oath(uint8_t truncate,
res_APDU[res_APDU_size++] = hmac[offset + 3];
}
else {
res_APDU[res_APDU_size++] = hmac_size + 1;
res_APDU[res_APDU_size++] = (uint8_t)(hmac_size + 1);
res_APDU[res_APDU_size++] = key[1];
memcpy(res_APDU + res_APDU_size, hmac, hmac_size); res_APDU_size += hmac_size;
memcpy(res_APDU + res_APDU_size, hmac, hmac_size); res_APDU_size += (uint16_t)hmac_size;
}
apdu.ne = res_APDU_size;
return CCID_OK;
return PICOKEY_OK;
}
int cmd_calculate() {
size_t chal_len = 0, name_len = 0, key_len = 0;
uint8_t *chal = NULL, *name = NULL, *key = NULL;
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
return SW_INCORRECT_P1P2();
}
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_CHALLENGE, &chal_len, &chal) == false) {
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_NAME, &name_len, &name) == false) {
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_INCORRECT_PARAMS();
}
file_t *ef = find_oath_cred(name, name_len);
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef) == false) {
return SW_DATA_INVALID();
}
if (asn1_find_tag(file_get_data(ef), file_get_size(ef), TAG_KEY, &key_len, &key) == false) {
asn1_ctx_t ctxe;
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
return SW_INCORRECT_PARAMS();
}
if ((key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
if (asn1_find_tag(file_get_data(ef), file_get_size(ef), TAG_IMF, &chal_len,
&chal) == false) {
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
}
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
int ret = calculate_oath(P2(apdu), key, key_len, chal, chal_len);
if (ret != CCID_OK) {
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
if (ret != PICOKEY_OK) {
return SW_EXEC_ERROR();
}
if ((key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
uint64_t v =
((uint64_t) chal[0] <<
56) |
((uint64_t) chal[1] <<
48) |
((uint64_t) chal[2] <<
40) |
((uint64_t) chal[3] <<
32) |
((uint64_t) chal[4] <<
24) | ((uint64_t) chal[5] << 16) | ((uint64_t) chal[6] << 8) | (uint64_t) chal[7];
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
uint64_t v = get_uint64_t_be(chal.data);
size_t ef_size = file_get_size(ef);
v++;
uint8_t *tmp = (uint8_t *) calloc(1, ef_size);
memcpy(tmp, file_get_data(ef), ef_size);
asn1_find_tag(tmp, ef_size, TAG_IMF, &chal_len, &chal);
chal[0] = v >> 56;
chal[1] = v >> 48;
chal[2] = v >> 40;
chal[3] = v >> 32;
chal[4] = v >> 24;
chal[5] = v >> 16;
chal[6] = v >> 8;
chal[7] = v & 0xff;
flash_write_data_to_file(ef, tmp, ef_size);
asn1_ctx_t ctxt;
asn1_ctx_init(tmp, (uint16_t)ef_size, &ctxt);
asn1_find_tag(&ctxt, TAG_IMF, &chal);
put_uint64_t_be(v, chal.data);
file_put_data(ef, tmp, (uint16_t)ef_size);
low_flash_available();
free(tmp);
}
@@ -426,47 +436,47 @@ int cmd_calculate() {
}
int cmd_calculate_all() {
size_t chal_len = 0, name_len = 0, key_len = 0, prop_len = 0;
uint8_t *chal = NULL, *name = NULL, *key = NULL, *prop = NULL;
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, prop = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
return SW_INCORRECT_P1P2();
}
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
if (asn1_find_tag(apdu.data, apdu.nc, TAG_CHALLENGE, &chal_len, &chal) == false) {
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
res_APDU_size = 0;
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file(EF_OATH_CRED + i);
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
const uint8_t *ef_data = file_get_data(ef);
size_t ef_len = file_get_size(ef);
if (asn1_find_tag(ef_data, ef_len, TAG_NAME, &name_len,
&name) == false ||
asn1_find_tag(ef_data, ef_len, TAG_KEY, &key_len, &key) == false) {
asn1_ctx_t ctxe;
asn1_ctx_init((uint8_t *)ef_data, (uint16_t)ef_len, &ctxe);
if (asn1_find_tag(&ctxe, TAG_NAME, &name) == false || asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
continue;
}
res_APDU[res_APDU_size++] = TAG_NAME;
res_APDU[res_APDU_size++] = name_len;
memcpy(res_APDU + res_APDU_size, name, name_len); res_APDU_size += name_len;
if ((key[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
res_APDU[res_APDU_size++] = (uint8_t)name.len;
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
res_APDU[res_APDU_size++] = TAG_NO_RESPONSE;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = key[1];
res_APDU[res_APDU_size++] = key.data[1];
}
else if (asn1_find_tag(ef_data, ef_len, TAG_PROPERTY, &prop_len,
&prop) == true && (prop[0] & PROP_TOUCH)) {
else if (asn1_find_tag(&ctxe, TAG_PROPERTY, &prop) == true && (prop.data[0] & PROP_TOUCH)) {
res_APDU[res_APDU_size++] = TAG_TOUCH_RESPONSE;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = key[1];
res_APDU[res_APDU_size++] = key.data[1];
}
else {
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
int ret = calculate_oath(P2(apdu), key, key_len, chal, chal_len);
if (ret != CCID_OK) {
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
if (ret != PICOKEY_OK) {
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = key[1];
res_APDU[res_APDU_size++] = key.data[1];
}
}
}
@@ -479,26 +489,245 @@ int cmd_send_remaining() {
return SW_OK();
}
int cmd_set_otp_pin() {
uint8_t hsh[33] = { 0 };
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
if (file_has_data(ef_otp_pin)) {
return SW_CONDITIONS_NOT_SATISFIED();
}
asn1_ctx_t ctxi, pw = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
return SW_INCORRECT_PARAMS();
}
hsh[0] = MAX_OTP_COUNTER;
double_hash_pin(pw.data, pw.len, hsh + 1);
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
low_flash_available();
return SW_OK();
}
int cmd_change_otp_pin() {
uint8_t hsh[33] = { 0 };
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
if (!file_has_data(ef_otp_pin)) {
return SW_CONDITIONS_NOT_SATISFIED();
}
asn1_ctx_t ctxi, pw = { 0 }, new_pw = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
return SW_INCORRECT_PARAMS();
}
double_hash_pin(pw.data, pw.len, hsh + 1);
if (memcmp(file_get_data(ef_otp_pin) + 1, hsh + 1, 32) != 0) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
if (asn1_find_tag(&ctxi, TAG_NEW_PASSWORD, &new_pw) == false) {
return SW_INCORRECT_PARAMS();
}
hsh[0] = MAX_OTP_COUNTER;
double_hash_pin(new_pw.data, new_pw.len, hsh + 1);
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
low_flash_available();
return SW_OK();
}
int cmd_verify_otp_pin() {
uint8_t hsh[33] = { 0 }, data_hsh[33];
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
if (!file_has_data(ef_otp_pin)) {
return SW_CONDITIONS_NOT_SATISFIED();
}
asn1_ctx_t ctxi, pw = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
return SW_INCORRECT_PARAMS();
}
double_hash_pin(pw.data, pw.len, hsh + 1);
memcpy(data_hsh, file_get_data(ef_otp_pin), sizeof(data_hsh));
if (data_hsh[0] == 0 || memcmp(data_hsh + 1, hsh + 1, 32) != 0) {
if (data_hsh[0] > 0) {
data_hsh[0] -= 1;
}
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
low_flash_available();
validated = false;
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
data_hsh[0] = MAX_OTP_COUNTER;
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
low_flash_available();
validated = true;
return SW_OK();
}
int cmd_verify_hotp() {
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, code = { 0 };
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
uint32_t code_int = 0;
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_INCORRECT_PARAMS();
}
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef) == false) {
return SW_DATA_INVALID();
}
asn1_ctx_t ctxe;
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
return SW_INCORRECT_PARAMS();
}
if ((key.data[0] & OATH_TYPE_MASK) != OATH_TYPE_HOTP) {
return SW_DATA_INVALID();
}
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &code) == true) {
code_int = get_uint32_t_be(code.data);
}
int ret = calculate_oath(0x01, key.data, key.len, chal.data, chal.len);
if (ret != PICOKEY_OK) {
return SW_EXEC_ERROR();
}
uint32_t res_int = get_uint32_t_be(res_APDU + 2);
if (res_APDU[1] == 6) {
res_int %= (uint32_t) 1e6;
}
else {
res_int %= (uint32_t) 1e8;
}
if (res_int != code_int) {
return SW_WRONG_DATA();
}
res_APDU_size = 0;
apdu.ne = 0;
return SW_OK();
}
int cmd_rename() {
asn1_ctx_t ctxi, name = { 0 }, new_name = { 0 };
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
if (apdu.data[0] != TAG_NAME) {
return SW_WRONG_DATA();
}
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
asn1_ctx_init(name.data + name.len, (uint16_t)(apdu.nc - (name.data + name.len - apdu.data)), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &new_name) == false) {
return SW_WRONG_DATA();
}
if (memcmp(name.data, new_name.data, name.len) == 0) {
return SW_WRONG_DATA();
}
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef) == false) {
return SW_DATA_INVALID();
}
uint8_t *fdata = file_get_data(ef);
uint16_t fsize = file_get_size(ef);
asn1_ctx_init(fdata, fsize, &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
memcpy(new_data, fdata, name.data - fdata);
*(new_data + (name.data - fdata) - 1) = new_name.len;
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
memcpy(new_data + (name.data - fdata) + new_name.len, name.data + name.len, fsize - (name.data + name.len - fdata));
file_put_data(ef, new_data, fsize + new_name.len - name.len);
low_flash_available();
free(new_data);
return SW_OK();
}
int cmd_get_credential() {
asn1_ctx_t ctxi, name = { 0 };
if (apdu.nc < 3) {
return SW_INCORRECT_PARAMS();
}
if (apdu.data[0] != TAG_NAME) {
return SW_WRONG_DATA();
}
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef) == false) {
return SW_DATA_INVALID();
}
asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) {
res_APDU[res_APDU_size++] = TAG_NAME;
res_APDU[res_APDU_size++] = (uint8_t)(name.len);
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_LOGIN;
res_APDU[res_APDU_size++] = (uint8_t)(login.len);
memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD;
res_APDU[res_APDU_size++] = (uint8_t)(pw.len);
memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_METADATA;
res_APDU[res_APDU_size++] = (uint8_t)(meta.len);
memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len;
}
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) {
res_APDU[res_APDU_size++] = TAG_PROPERTY;
res_APDU[res_APDU_size++] = (uint8_t)(prop.len);
memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len;
}
apdu.ne = res_APDU_size;
return SW_OK();
}
#define INS_PUT 0x01
#define INS_DELETE 0x02
#define INS_SET_CODE 0x03
#define INS_RESET 0x04
#define INS_RENAME 0x05
#define INS_LIST 0xa1
#define INS_CALCULATE 0xa2
#define INS_VALIDATE 0xa3
#define INS_CALC_ALL 0xa4
#define INS_SEND_REMAINING 0xa5
#define INS_VERIFY_CODE 0xb1
#define INS_VERIFY_PIN 0xb2
#define INS_CHANGE_PIN 0xb3
#define INS_SET_PIN 0xb4
#define INS_GET_CREDENTIAL 0xb5
static const cmd_t cmds[] = {
{ INS_PUT, cmd_put },
{ INS_DELETE, cmd_delete },
{ INS_SET_CODE, cmd_set_code },
{ INS_RESET, cmd_reset },
{ INS_RENAME, cmd_rename },
{ INS_LIST, cmd_list },
{ INS_VALIDATE, cmd_validate },
{ INS_CALCULATE, cmd_calculate },
{ INS_CALC_ALL, cmd_calculate_all },
{ INS_SEND_REMAINING, cmd_send_remaining },
{ INS_SET_PIN, cmd_set_otp_pin },
{ INS_CHANGE_PIN, cmd_change_otp_pin },
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
{ INS_VERIFY_CODE, cmd_verify_hotp },
{ INS_GET_CREDENTIAL, cmd_get_credential },
{ 0x00, 0x0 }
};
@@ -506,10 +735,12 @@ int oath_process_apdu() {
if (CLA(apdu) != 0x00) {
return SW_CLA_NOT_SUPPORTED();
}
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
if (cap_supported(CAP_OATH)) {
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
}
}
}
return SW_INS_NOT_SUPPORTED();

View File

@@ -3,30 +3,38 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
#include "version.h"
#include "asn1.h"
#include "hid/ctap_hid.h"
#ifndef ENABLE_EMULATION
#include "usb.h"
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ENABLE_EMULATION
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
#else
#include "tusb.h"
#endif
#include "mbedtls/aes.h"
#include "management.h"
#define FIXED_SIZE 16
#define KEY_SIZE 16
@@ -50,7 +58,8 @@
#define ALLOW_UPDATE 0x20 // Allow update of existing configuration (selected flags + access code)
#define DORMANT 0x40 // Dormant config (woken up, flag removed, requires update flag)
#define LED_INV 0x80 // LED idle state is off rather than on
#define EXTFLAG_UPDATE_MASK (SERIAL_BTN_VISIBLE | SERIAL_USB_VISIBLE | SERIAL_API_VISIBLE | USE_NUMERIC_KEYPAD | FAST_TRIG | ALLOW_UPDATE | DORMANT | LED_INV)
#define EXTFLAG_UPDATE_MASK (SERIAL_BTN_VISIBLE | SERIAL_USB_VISIBLE | SERIAL_API_VISIBLE | \
USE_NUMERIC_KEYPAD | FAST_TRIG | ALLOW_UPDATE | DORMANT | LED_INV)
/* TKT Flags */
#define TAB_FIRST 0x01 // Send TAB before first part
@@ -62,7 +71,8 @@
#define OATH_HOTP 0x40 // OATH HOTP mode
#define CHAL_RESP 0x40 // Challenge-response enabled (both must be set)
#define PROTECT_CFG2 0x80 // Block update of config 2 unless config 2 is configured and has this bit set
#define TKTFLAG_UPDATE_MASK (TAB_FIRST | APPEND_TAB1 | APPEND_TAB2 | APPEND_DELAY1 | APPEND_DELAY2 | APPEND_CR)
#define TKTFLAG_UPDATE_MASK (TAB_FIRST | APPEND_TAB1 | APPEND_TAB2 | APPEND_DELAY1 | APPEND_DELAY2 | \
APPEND_CR)
/* CFG Flags */
#define SEND_REF 0x01 // Send reference string (0..F) before data
@@ -89,6 +99,7 @@
static uint8_t config_seq = { 1 };
PACK(
typedef struct otp_config {
uint8_t fixed_data[FIXED_SIZE];
uint8_t uid[UID_SIZE];
@@ -100,22 +111,27 @@ typedef struct otp_config {
uint8_t cfg_flags;
uint8_t rfu[2];
uint16_t crc;
} __attribute__((packed)) otp_config_t;
}) otp_config_t;
static const size_t otp_config_size = sizeof(otp_config_t);
uint16_t otp_status();
#define otp_config_size sizeof(otp_config_t)
uint16_t otp_status(bool is_otp);
int otp_process_apdu();
int otp_unload();
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
const uint8_t otp_aid[] = {
7,
0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
};
app_t *otp_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
if (!memcmp(aid, otp_aid + 1, MIN(aid_len, otp_aid[0]))) {
a->aid = otp_aid;
int otp_select(app_t *a, uint8_t force) {
(void) force;
if (cap_supported(CAP_OTP)) {
a->process_apdu = otp_process_apdu;
a->unload = otp_unload;
if (file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ||
@@ -125,18 +141,16 @@ app_t *otp_select(app_t *a, const uint8_t *aid, uint8_t aid_len) {
else {
config_seq = 0;
}
otp_status();
memmove(res_APDU, res_APDU + 1, 6);
res_APDU_size = 6;
apdu.ne = res_APDU_size;
return a;
otp_status(false);
return PICOKEY_OK;
}
return NULL;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
uint8_t modhex_tab[] = {'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v'};
uint8_t modhex_tab[] =
{ 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' };
int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
for (int l = 0; l < len; l++) {
for (size_t l = 0; l < len; l++) {
*out++ = modhex_tab[in[l] >> 4];
*out++ = modhex_tab[in[l] & 0xf];
}
@@ -147,18 +161,18 @@ extern void scan_all();
void init_otp() {
if (scanned == false) {
scan_all();
for (int i = 0; i < 2; i++) {
for (uint8_t i = 0; i < 2; i++) {
file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
uint8_t *data = file_get_data(ef);
otp_config_t *otp_config = (otp_config_t *)data;
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) && !(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
uint16_t counter = (data[otp_config_size] << 8) | data[otp_config_size + 1];
otp_config_t *otp_config = (otp_config_t *) data;
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) &&
!(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
uint16_t counter = get_uint16_t_be(data + otp_config_size);
if (++counter <= 0x7fff) {
uint8_t new_data[otp_config_size + 8];
memcpy(new_data, data, sizeof(new_data));
new_data[otp_config_size] = counter >> 8;
new_data[otp_config_size + 1] = counter & 0xff;
flash_write_data_to_file(ef, new_data, sizeof(new_data));
put_uint16_t_be(counter, new_data + otp_config_size);
file_put_data(ef, new_data, sizeof(new_data));
}
}
}
@@ -171,79 +185,94 @@ extern int calculate_oath(uint8_t truncate,
size_t key_len,
const uint8_t *chal,
size_t chal_len);
#ifndef ENABLE_EMULATION
static uint8_t session_counter[2] = {0};
#endif
uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
uint16_t crc = 0xFFFF;
for (size_t idx = 0; idx < data_len; idx++) {
crc ^= data[idx];
for (uint8_t i = 0; i < 8; i++) {
uint16_t j = crc & 0x1;
crc >>= 1;
if (j == 1) {
crc ^= 0x8408;
}
}
}
return crc & 0xFFFF;
}
static uint8_t session_counter[2] = { 0 };
int otp_button_pressed(uint8_t slot) {
init_otp();
#ifndef ENABLE_EMULATION
if (!cap_supported(CAP_OTP)) {
return 3;
}
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
const uint8_t *data = file_get_data(ef);
otp_config_t *otp_config = (otp_config_t *)data;
otp_config_t *otp_config = (otp_config_t *) data;
if (file_has_data(ef) == false) {
return 1;
}
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
return 2;
}
#ifdef ENABLE_OATH_APP
if (otp_config->tkt_flags & OATH_HOTP) {
uint8_t tmp_key[KEY_SIZE + 2];
tmp_key[0] = 0x01;
memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
uint64_t imf = 0;
const uint8_t *p = data + otp_config_size;
imf |= (uint64_t)*p++ << 56;
imf |= (uint64_t)*p++ << 48;
imf |= (uint64_t)*p++ << 40;
imf |= (uint64_t)*p++ << 32;
imf |= *p++ << 24;
imf |= *p++ << 16;
imf |= *p++ << 8;
imf |= *p++;
imf = get_uint64_t_be(p);
p += 8;
if (imf == 0) {
imf = ((otp_config->uid[4] << 8) | otp_config->uid[5]) << 4;
imf = get_uint16_t_be(otp_config->uid + 4);
}
uint8_t chal[8] = {imf >> 56, imf >> 48, imf >> 40, imf >> 32, imf >> 24, imf >> 16, imf >> 8, imf & 0xff};
uint8_t chal[8];
put_uint64_t_be(imf, chal);
res_APDU_size = 0;
int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
if (ret == CCID_OK) {
if (ret == PICOKEY_OK) {
uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
uint32_t number = (res_APDU[2] << 24) | (res_APDU[3] << 16) | (res_APDU[4] << 8) | res_APDU[5];
uint32_t number = get_uint16_t_be(res_APDU + 2);
number %= base;
char number_str[9];
if (otp_config->cfg_flags & OATH_HOTP8) {
sprintf(number_str, "%08lu", (long unsigned int)number);
add_keyboard_buffer((const uint8_t *)number_str, 8, true);
sprintf(number_str, "%08lu", (long unsigned int) number);
add_keyboard_buffer((const uint8_t *) number_str, 8, true);
}
else {
sprintf(number_str, "%06lu", (long unsigned int)number);
add_keyboard_buffer((const uint8_t *)number_str, 6, true);
sprintf(number_str, "%06lu", (long unsigned int) number);
add_keyboard_buffer((const uint8_t *) number_str, 6, true);
}
imf++;
uint8_t new_chal[8] = {imf >> 56, imf >> 48, imf >> 40, imf >> 32, imf >> 24, imf >> 16, imf >> 8, imf & 0xff};
uint8_t new_chal[8];
put_uint64_t_be(imf, new_chal);
uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
memcpy(new_otp_config, otp_config, otp_config_size);
memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
flash_write_data_to_file(ef, new_otp_config, sizeof(new_otp_config));
file_put_data(ef, new_otp_config, sizeof(new_otp_config));
low_flash_available();
}
if (otp_config->tkt_flags & APPEND_CR) {
append_keyboard_buffer((const uint8_t *)"\r", 1);
append_keyboard_buffer((const uint8_t *) "\r", 1);
}
}
#endif
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
if (otp_config->cfg_flags & SHORT_TICKET) {
otp_config->fixed_size /= 2;
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
//fixed_size /= 2;
}
add_keyboard_buffer(otp_config->fixed_data, otp_config->fixed_size, false);
add_keyboard_buffer(otp_config->fixed_data, fixed_size, false);
if (otp_config->tkt_flags & APPEND_CR) {
append_keyboard_buffer((const uint8_t *)"\x28", 1);
append_keyboard_buffer((const uint8_t *) "\x28", 1);
}
}
else {
uint8_t otpk[22], *po = otpk;
bool update_counter = false;
uint16_t counter = (data[otp_config_size] << 8) | data[otp_config_size + 1], crc = 0;
uint16_t counter = get_uint16_t_be(data + otp_config_size), crc = 0;
uint32_t ts = board_millis() / 1000;
if (counter == 0) {
update_counter = true;
@@ -253,9 +282,8 @@ int otp_button_pressed(uint8_t slot) {
po += 6;
memcpy(po, otp_config->uid, UID_SIZE);
po += UID_SIZE;
*po++ = counter & 0xff;
*po++ = counter >> 8;
ts >>= 3;
po += put_uint16_t_le(counter, po);
ts >>= 1;
*po++ = ts & 0xff;
*po++ = ts >> 8;
*po++ = ts >> 16;
@@ -263,8 +291,7 @@ int otp_button_pressed(uint8_t slot) {
random_gen(NULL, po, 2);
po += 2;
crc = calculate_crc(otpk + 6, 14);
*po++ = ~crc & 0xff;
*po++ = ~crc >> 8;
po += put_uint16_t_le(~crc, po);
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
@@ -272,9 +299,9 @@ int otp_button_pressed(uint8_t slot) {
mbedtls_aes_free(&ctx);
uint8_t otp_out[44];
encode_modhex(otpk, sizeof(otpk), otp_out);
add_keyboard_buffer((const uint8_t *)otp_out, sizeof(otp_out), true);
add_keyboard_buffer((const uint8_t *) otp_out, sizeof(otp_out), true);
if (otp_config->tkt_flags & APPEND_CR) {
append_keyboard_buffer((const uint8_t *)"\r", 1);
append_keyboard_buffer((const uint8_t *) "\r", 1);
}
if (++session_counter[slot - 1] == 0) {
@@ -285,61 +312,83 @@ int otp_button_pressed(uint8_t slot) {
if (update_counter == true) {
uint8_t new_data[otp_config_size + 8];
memcpy(new_data, data, sizeof(new_data));
new_data[otp_config_size] = counter >> 8;
new_data[otp_config_size + 1] = counter & 0xff;
flash_write_data_to_file(ef, new_data, sizeof(new_data));
put_uint16_t_be(counter, new_data + otp_config_size);
file_put_data(ef, new_data, sizeof(new_data));
low_flash_available();
}
}
#endif
return 0;
}
void __attribute__((constructor)) otp_ctor() {
register_app(otp_select);
INITIALIZER( otp_ctor ) {
register_app(otp_select, otp_aid);
button_pressed_cb = otp_button_pressed;
hid_set_report_cb = otp_hid_set_report_cb;
hid_get_report_cb = otp_hid_get_report_cb;
}
int otp_unload() {
return CCID_OK;
return PICOKEY_OK;
}
uint16_t otp_status() {
uint8_t status_byte = 0x0;
uint16_t otp_status(bool is_otp) {
if (scanned == false) {
scan_all();
scanned = true;
}
res_APDU_size = 0;
res_APDU[1] = PICO_FIDO_VERSION_MAJOR;
res_APDU[2] = PICO_FIDO_VERSION_MINOR;
res_APDU[3] = 0;
res_APDU[4] = config_seq;
res_APDU[5] = (CONFIG2_TOUCH | CONFIG1_TOUCH) |
(file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ? CONFIG1_VALID :
0x00) |
(file_has_data(search_dynamic_file(EF_OTP_SLOT2)) ? CONFIG2_VALID :
0x00);
res_APDU[6] = 0;
if (is_otp) {
res_APDU_size++;
}
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
res_APDU[res_APDU_size++] = 0;
res_APDU[res_APDU_size++] = config_seq;
uint8_t opts = 0;
file_t *ef = search_dynamic_file(EF_OTP_SLOT1);
if (file_has_data(ef)) {
opts |= CONFIG1_VALID;
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
opts |= CONFIG1_TOUCH;
}
}
ef = search_dynamic_file(EF_OTP_SLOT2);
if (file_has_data(ef)) {
opts |= CONFIG2_VALID;
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
opts |= CONFIG2_TOUCH;
}
}
res_APDU[res_APDU_size++] = opts;
res_APDU[res_APDU_size++] = 0;
res_APDU[res_APDU_size++] = status_byte;
if (is_otp) {
res_APDU_size = 0;
}
else {
apdu.ne = res_APDU_size;
}
return SW_OK();
}
bool check_crc(const otp_config_t *data) {
uint16_t crc = calculate_crc((const uint8_t *)data, otp_config_size);
uint16_t crc = calculate_crc((const uint8_t *) data, otp_config_size);
return crc == 0xF0B8;
}
extern int man_get_config();
bool _is_otp = false;
int cmd_otp() {
uint8_t p1 = P1(apdu), p2 = P2(apdu);
if (p2 != 0x00) {
return SW_INCORRECT_P1P2();
}
if (p1 == 0x01 || p1 == 0x03) { // Configure slot
otp_config_t *odata = (otp_config_t *)apdu.data;
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
return SW_WRONG_DATA();
}
otp_config_t *odata = (otp_config_t *) apdu.data;
file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
if (file_has_data(ef)) {
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
@@ -349,23 +398,23 @@ int cmd_otp() {
}
for (int c = 0; c < otp_config_size; c++) {
if (apdu.data[c] != 0) {
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
return SW_WRONG_DATA();
}
memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
flash_write_data_to_file(ef, apdu.data, otp_config_size + 8);
file_put_data(ef, apdu.data, otp_config_size + 8);
low_flash_available();
config_seq++;
return otp_status();
return otp_status(_is_otp);
}
}
// Delete slot
delete_file(ef);
if (!file_has_data(search_dynamic_file(EF_OTP_SLOT1)) &&
!file_has_data(search_dynamic_file(EF_OTP_SLOT2))) {
config_seq = 0;
}
return otp_status();
config_seq++;
return otp_status(_is_otp);
}
else if (p1 == 0x04 || p1 == 0x05) {
otp_config_t *odata = (otp_config_t *)apdu.data;
else if (p1 == 0x04 || p1 == 0x05) { // Update slot
otp_config_t *odata = (otp_config_t *) apdu.data;
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
return SW_WRONG_DATA();
}
@@ -377,14 +426,24 @@ int cmd_otp() {
}
memcpy(apdu.data, file_get_data(ef), FIXED_SIZE + UID_SIZE + KEY_SIZE);
odata->fixed_size = otpc->fixed_size;
odata->ext_flags = (otpc->ext_flags & ~EXTFLAG_UPDATE_MASK) | (odata->ext_flags & EXTFLAG_UPDATE_MASK);
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) | (odata->tkt_flags & TKTFLAG_UPDATE_MASK);
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) | (odata->cfg_flags & CFGFLAG_UPDATE_MASK);
flash_write_data_to_file(ef, apdu.data, otp_config_size);
odata->ext_flags = (otpc->ext_flags & ~EXTFLAG_UPDATE_MASK) |
(odata->ext_flags & EXTFLAG_UPDATE_MASK);
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) |
(odata->tkt_flags & TKTFLAG_UPDATE_MASK);
if (!(otpc->tkt_flags & CHAL_RESP)) {
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) |
(odata->cfg_flags & CFGFLAG_UPDATE_MASK);
}
else {
odata->cfg_flags = otpc->cfg_flags;
}
file_put_data(ef, apdu.data, otp_config_size);
low_flash_available();
config_seq++;
}
return otp_status(_is_otp);
}
else if (p1 == 0x06) {
else if (p1 == 0x06) { // Swap slots
uint8_t tmp[otp_config_size + 8];
bool ef1_data = false;
file_t *ef1 = file_new(EF_OTP_SLOT1);
@@ -394,48 +453,78 @@ int cmd_otp() {
ef1_data = true;
}
if (file_has_data(ef2)) {
flash_write_data_to_file(ef1, file_get_data(ef2), file_get_size(ef2));
file_put_data(ef1, file_get_data(ef2), file_get_size(ef2));
}
else {
delete_file(ef1);
// When a dynamic file is deleted, existing referenes are invalidated
ef2 = file_new(EF_OTP_SLOT2);
}
if (ef1_data) {
flash_write_data_to_file(ef2, tmp, sizeof(tmp));
file_put_data(ef2, tmp, sizeof(tmp));
}
else {
delete_file(ef2);
}
low_flash_available();
config_seq++;
return otp_status(_is_otp);
}
else if (p1 == 0x10) {
#ifndef ENABLE_EMULATION
pico_get_unique_board_id_string((char *) res_APDU, 4);
#endif
memcpy(res_APDU, pico_serial.id, 4);
res_APDU[0] &= ~0xFC; // Force 8-digit serial number
res_APDU_size = 4;
}
else if (p1 == 0x13) {
else if (p1 == 0x13) { // Get config
man_get_config();
}
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) {
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP
file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
if (file_has_data(ef)) {
otp_config_t *otp_config = (otp_config_t *)file_get_data(ef);
if (!(otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP)) {
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
if (!(otp_config->tkt_flags & CHAL_RESP)) {
return SW_WRONG_DATA();
}
int ret = 0;
uint8_t *rdata_bk = apdu.rdata;
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
status_byte = 0x20;
otp_status(_is_otp);
#ifndef ENABLE_EMULATION
if (wait_button() == true) {
status_byte = 0x00;
otp_status(_is_otp);
return SW_CONDITIONS_NOT_SATISFIED();
}
#endif
status_byte = 0x10;
apdu.rdata = rdata_bk;
}
if (p1 == 0x30 || p1 == 0x38) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), otp_config->aes_key, KEY_SIZE, apdu.data, 8, res_APDU);
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
return SW_WRONG_DATA();
}
uint8_t aes_key[KEY_SIZE + UID_SIZE];
memcpy(aes_key, otp_config->aes_key, KEY_SIZE);
memcpy(aes_key + KEY_SIZE, otp_config->uid, UID_SIZE);
uint8_t chal_len = 64;
if (otp_config->cfg_flags & HMAC_LT64) {
while (chal_len > 0 && apdu.data[63] == apdu.data[chal_len - 1]) {
chal_len--;
}
}
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), aes_key, sizeof(aes_key), apdu.data, chal_len, res_APDU);
if (ret == 0) {
res_APDU_size = 20;
}
}
else if (p1 == 0x20 || p1 == 0x28) {
if (!(otp_config->cfg_flags & CHAL_YUBICO)) {
return SW_WRONG_DATA();
}
uint8_t challenge[16];
memcpy(challenge, apdu.data, 6);
#ifndef ENABLE_EMULATION
pico_get_unique_board_id_string((char *) challenge + 6, 10);
#endif
memcpy(challenge + 6, pico_serial_str, 10);
mbedtls_aes_context ctx;
mbedtls_aes_init(&ctx);
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
@@ -445,6 +534,9 @@ int cmd_otp() {
res_APDU_size = 16;
}
}
if (ret == 0) {
status_byte = 0x00;
}
}
}
return SW_OK();
@@ -461,11 +553,115 @@ int otp_process_apdu() {
if (CLA(apdu) != 0x00) {
return SW_CLA_NOT_SUPPORTED();
}
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
if (cap_supported(CAP_OTP)) {
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
if (cmd->ins == INS(apdu)) {
int r = cmd->cmd_handler();
return r;
}
}
}
return SW_INS_NOT_SUPPORTED();
}
uint8_t otp_frame_rx[70] = {0};
uint8_t otp_frame_tx[70] = {0};
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
uint8_t otp_header[4] = {0};
extern uint16_t *get_send_buffer_size(uint8_t itf);
int otp_send_frame(uint8_t *frame, size_t frame_len) {
uint16_t crc = calculate_crc(frame, frame_len);
frame_len += put_uint16_t_le(~crc, frame + frame_len);
*get_send_buffer_size(ITF_KEYBOARD) = frame_len;
otp_exp_seq = (frame_len / 7);
if (frame_len % 7) {
otp_exp_seq++;
}
otp_curr_seq = 0;
return 0;
}
int otp_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
if (report_type == 3) {
DEBUG_PAYLOAD(buffer, bufsize);
if (buffer[7] == 0xFF) { // reset
*get_send_buffer_size(ITF_KEYBOARD) = 0;
otp_curr_seq = otp_exp_seq = 0;
memset(otp_frame_tx, 0, sizeof(otp_frame_tx));
}
else if (buffer[7] & 0x80) { // a frame
uint8_t rseq = buffer[7] & 0x1F;
if (rseq < 10) {
if (rseq == 0) {
memset(otp_frame_rx, 0, sizeof(otp_frame_rx));
}
memcpy(otp_frame_rx + rseq * 7, buffer, 7);
if (rseq == 9) {
DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
DEBUG_PAYLOAD(otp_frame_rx, sizeof(otp_frame_rx));
uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = get_uint16_t_le(otp_frame_rx + 65);
uint8_t slot_id = otp_frame_rx[64];
if (residual_crc == rcrc) {
uint8_t hdr[5];
apdu.header = hdr;
apdu.data = otp_frame_rx;
apdu.nc = 64;
apdu.rdata = otp_frame_tx;
apdu.header[0] = 0;
apdu.header[1] = 0x01;
apdu.header[2] = slot_id;
apdu.header[3] = 0;
_is_otp = true;
int ret = otp_process_apdu();
if (ret == 0x9000 && res_APDU_size > 0) {
otp_send_frame(apdu.rdata, apdu.rlen);
}
_is_otp = false;
}
else {
printf("[OTP] Bad CRC!\n");
}
}
}
}
return 1;
}
return 0;
}
uint16_t otp_hid_get_report_cb(uint8_t itf,
uint8_t report_id,
hid_report_type_t report_type,
uint8_t *buffer,
uint16_t reqlen) {
// TODO not Implemented
(void) itf;
(void) report_id;
(void) report_type;
(void) buffer;
(void) reqlen;
uint16_t send_buffer_size = *get_send_buffer_size(ITF_KEYBOARD);
if (send_buffer_size > 0) {
uint8_t seq = otp_curr_seq++;
memset(buffer, 0, 8);
memcpy(buffer, otp_frame_tx + 7 * seq, MIN(7, send_buffer_size));
buffer[7] = 0x40 | seq;
DEBUG_DATA(buffer, 8);
*get_send_buffer_size(ITF_KEYBOARD) -= MIN(7, send_buffer_size);
}
else if (otp_curr_seq == otp_exp_seq && otp_exp_seq > 0) {
memset(buffer, 0, 7);
buffer[7] = 0x40;
DEBUG_DATA(buffer,8);
otp_curr_seq = otp_exp_seq = 0;
}
else {
res_APDU = buffer;
otp_status(true);
DEBUG_DATA(buffer, 8);
}
return reqlen;
}

View File

@@ -3,22 +3,22 @@
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
* Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef __VERSION_H_
#define __VERSION_H_
#define PICO_FIDO_VERSION 0x0504
#define PICO_FIDO_VERSION 0x0700
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)

View File

@@ -3,5 +3,5 @@
source tests/docker_env.sh
#run_in_docker rm -rf CMakeFiles
run_in_docker mkdir -p build_in_docker
run_in_docker -w "$PWD/build_in_docker" cmake -DENABLE_EMULATION=1 ..
run_in_docker -w "$PWD/build_in_docker" cmake -DENABLE_EMULATION=1 -DENABLE_EDDSA=1 ..
run_in_docker -w "$PWD/build_in_docker" make -j ${NUM_PROC}

View File

@@ -20,12 +20,13 @@
from http import client
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError, _Ctap1ClientBackend
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector
from fido2.attestation import FidoU2FAttestation
from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server
from fido2.ctap import CtapError
from fido2.webauthn import CollectedClientData, AttestedCredentialData
from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions
from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension
from utils import *
from fido2.cose import ES256
import sys
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
pass
class Device():
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
self.__user = None
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
self.__set_server(rp=rp, attestation=attestation)
def __verify_rp(rp_id, origin):
return True
def __set_client(self, origin, user_interaction, uv):
self.__uv = uv
@@ -101,14 +104,23 @@ class Device():
sys.exit(1)
# Set up a FIDO 2 client using the origin https://example.com
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
extensions = [
HmacSecretExtension(allow_hmac_secret=True),
LargeBlobExtension(),
CredBlobExtension(),
CredProtectExtension(),
MinPinLengthExtension(),
CredPropsExtension(),
ThirdPartyPaymentExtension()
]
self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions)
# Prefer UV if supported and configured
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
self.__uv = "preferred"
print("Authenticator supports User Verification")
self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction)
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
self.ctap1 = self.__client1._backend.ctap1
@@ -116,6 +128,10 @@ class Device():
self.__rp = rp
self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
self.__server.allowed_algorithms = [
PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
for p in self.__client._backend.info.algorithms
]
def client(self):
return self.__client
@@ -212,9 +228,7 @@ class Device():
'key_params':key_params}}
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
)
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
rp = rp if rp is not Ellipsis else self.__rp
user = user if user is not Ellipsis else self.user()
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
@@ -222,22 +236,31 @@ class Device():
client = self.__client1
else:
client = self.__client
result = client._backend.do_make_credential(
client_data=client_data,
rp=rp,
user=user,
key_params=key_params,
exclude_list=exclude_list,
options=PublicKeyCredentialCreationOptions(
rp=PublicKeyCredentialRpEntity.from_dict(rp),
user=PublicKeyCredentialUserEntity.from_dict(user),
pub_key_cred_params=key_params,
exclude_credentials=exclude_list,
extensions=extensions,
rk=rk,
user_verification=user_verification,
enterprise_attestation=enterprise_attestation,
challenge=os.urandom(32),
authenticator_selection=AuthenticatorSelectionCriteria(
require_resident_key=rk,
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED
),
attestation=enterprise_attestation
)
client_data, rp_id = client_data.collect_client_data(options=options)
result = client._backend.do_make_credential(
options=options,
client_data=client_data,
rp_id=rp_id,
enterprise_rpid_list=None,
event=event
)
return {'res':result,'req':{'client_data':client_data,
return {'res':result.response,'req':{'client_data':client_data,
'rp':rp,
'user':user,
'key_params':key_params}}
'key_params':key_params},'client_extension_results':result.client_extension_results}
def try_make_credential(self, options=None):
if (options is None):
@@ -263,14 +286,14 @@ class Device():
# Complete registration
auth_data = self.__server.register_complete(
state, result.client_data, result.attestation_object
state=state, response=result
)
credentials = [auth_data.credential_data]
print("New credential created!")
print("CLIENT DATA:", result.client_data)
print("ATTESTATION OBJECT:", result.attestation_object)
print("CLIENT DATA:", result.response.client_data)
print("ATTESTATION OBJECT:", result.response.attestation_object)
print()
print("CREDENTIAL DATA:", auth_data.credential_data)
@@ -290,17 +313,14 @@ class Device():
self.__server.authenticate_complete(
state,
credentials,
result.credential_id,
result.client_data,
result.authenticator_data,
result.signature,
result
)
print("Credential authenticated!")
print("CLIENT DATA:", result.client_data)
print("CLIENT DATA:", result.response.client_data)
print()
print("AUTH DATA:", result.authenticator_data)
print("AUTH DATA:", result.response.authenticator_data)
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
@@ -321,21 +341,31 @@ class Device():
return self.__client._backend.ctap2.get_next_assertion()
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
)
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
options=PublicKeyCredentialRequestOptions(
challenge=os.urandom(32),
rp_id=rp_id,
allow_credentials=allow_list,
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED,
extensions=extensions
)
client_data, rp_id = client_data.collect_client_data(options=options)
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
try:
result = client._backend.do_get_assertion(
options=options,
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
except ClientError as e:
@@ -343,11 +373,9 @@ class Device():
client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN)
result = client._backend.do_get_assertion(
options=options,
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
else:
@@ -412,8 +440,8 @@ def AuthRes(device, RegRes, *args):
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
aut_data = res['res'].get_response(0)
m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
return aut_data
@pytest.fixture(scope="class")
@@ -438,7 +466,7 @@ def ccid_card():
@pytest.fixture(scope="class")
def select_oath(ccid_card):
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01]
resp = send_apdu(ccid_card, 0xA4, 0x04, 0x00, aid)
return ccid_card

View File

@@ -0,0 +1,33 @@
FROM debian:bookworm
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y
RUN apt install -y apt-utils
RUN apt install -y libccid \
libpcsclite-dev \
git \
autoconf \
pkg-config \
libtool \
help2man \
automake \
gcc \
make \
build-essential \
opensc \
python3 \
python3-pip \
swig \
cmake \
libfuse-dev \
python3-pyscard \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography inputimeout fido2==2.0.0 --break-system-packages
WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard
RUN autoreconf --verbose --install
RUN ./configure --sysconfdir=/etc
RUN make && make install
WORKDIR /

View File

@@ -22,7 +22,8 @@ RUN apt install -y libccid \
cmake \
libfuse-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography pyscard fido2 inputimeout
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard
RUN autoreconf --verbose --install

View File

@@ -27,16 +27,17 @@
from __future__ import annotations
from .base import HidDescriptor
from ..ctap import CtapDevice, CtapError, STATUS
from ..utils import LOG_LEVEL_TRAFFIC
from threading import Event
from enum import IntEnum, IntFlag, unique
from typing import Tuple, Optional, Callable, Iterator
import logging
import os
import struct
import sys
import os
import logging
from enum import IntEnum, IntFlag, unique
from threading import Event
from typing import Callable, Iterator
from ..ctap import STATUS, CtapDevice, CtapError
from ..utils import LOG_LEVEL_TRAFFIC
from .base import HidDescriptor
logger = logging.getLogger(__name__)
@@ -49,18 +50,24 @@ elif sys.platform.startswith("darwin"):
from . import macos as backend
elif sys.platform.startswith("freebsd"):
from . import freebsd as backend
elif sys.platform.startswith("netbsd"):
from . import netbsd as backend
elif sys.platform.startswith("openbsd"):
from . import openbsd as backend
else:
raise Exception("Unsupported platform")
from . import emulation as backend
from . import emulation as backend
list_descriptors = backend.list_descriptors
get_descriptor = backend.get_descriptor
open_connection = backend.open_connection
class ConnectionFailure(Exception):
"""The CTAP connection failed or returned an invalid response."""
@unique
class CTAPHID(IntEnum):
PING = 0x01
@@ -108,7 +115,7 @@ class CtapHidDevice(CtapDevice):
response = self.call(CTAPHID.INIT, nonce)
r_nonce, response = response[:8], response[8:]
if r_nonce != nonce:
raise Exception("Wrong nonce")
raise ConnectionFailure("Wrong nonce")
(
self._channel_id,
self._u2fhid_version,
@@ -128,7 +135,7 @@ class CtapHidDevice(CtapDevice):
return self._u2fhid_version
@property
def device_version(self) -> Tuple[int, int, int]:
def device_version(self) -> tuple[int, int, int]:
"""Device version number."""
return self._device_version
@@ -138,12 +145,12 @@ class CtapHidDevice(CtapDevice):
return self._capabilities
@property
def product_name(self) -> Optional[str]:
def product_name(self) -> str | None:
"""Product name of device."""
return self.descriptor.product_name
@property
def serial_number(self) -> Optional[str]:
def serial_number(self) -> str | None:
"""Serial number of device."""
return self.descriptor.serial_number
@@ -158,10 +165,22 @@ class CtapHidDevice(CtapDevice):
self,
cmd: int,
data: bytes = b"",
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
event: Event | None = None,
on_keepalive: Callable[[STATUS], None] | None = None,
) -> bytes:
event = event or Event()
while True:
try:
return self._do_call(cmd, data, event, on_keepalive)
except CtapError as e:
if e.code == CtapError.ERR.CHANNEL_BUSY:
if not event.wait(0.1):
logger.warning("CTAP channel busy, trying again...")
continue # Keep retrying on BUSY while not cancelled
raise
def _do_call(self, cmd, data, event, on_keepalive):
remaining = data
seq = 0
@@ -193,7 +212,7 @@ class CtapHidDevice(CtapDevice):
r_channel = struct.unpack_from(">I", recv)[0]
recv = recv[4:]
if r_channel != self._channel_id:
raise Exception("Wrong channel")
raise ConnectionFailure("Wrong channel")
if not response: # Initialization packet
r_cmd, r_len = struct.unpack_from(">BH", recv)
@@ -201,13 +220,12 @@ class CtapHidDevice(CtapDevice):
if r_cmd == TYPE_INIT | cmd:
pass # first data packet
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
ka_status = struct.unpack_from(">B", recv)[0]
logger.debug(f"Got keepalive status: {ka_status:02x}")
try:
ka_status = STATUS(struct.unpack_from(">B", recv)[0])
logger.debug(f"Got keepalive status: {ka_status:02x}")
except ValueError:
raise ConnectionFailure("Invalid keepalive status")
if on_keepalive and ka_status != last_ka:
try:
ka_status = STATUS(ka_status)
except ValueError:
pass # Unknown status value
last_ka = ka_status
on_keepalive(ka_status)
continue
@@ -219,7 +237,7 @@ class CtapHidDevice(CtapDevice):
r_seq = struct.unpack_from(">B", recv)[0]
recv = recv[1:]
if r_seq != seq:
raise Exception("Wrong sequence number")
raise ConnectionFailure("Wrong sequence number")
seq += 1
response += recv

View File

@@ -46,7 +46,7 @@
# default values, can be overridden by the environment
: ${MBEDTLS_DOCKER_GUEST:=bullseye}
: ${MBEDTLS_DOCKER_GUEST:=bookworm}
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"

View File

@@ -19,7 +19,8 @@
from fido2.client import CtapError
from fido2.cose import ES256
from fido2.cose import ES256, ES384, ES512, EdDSA
from utils import ES256K
import pytest
@@ -31,7 +32,7 @@ def test_make_credential():
pass
def test_attestation_format(MCRes):
assert MCRes['res'].attestation_object.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
assert MCRes['res'].attestation_object.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
def test_authdata_length(MCRes):
assert len(MCRes['res'].attestation_object.auth_data) >= 77
@@ -48,13 +49,13 @@ def test_bad_type_cdh(device):
def test_missing_user(device):
with pytest.raises(CtapError) as e:
device.doMC(user=None)
device.MC(user=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_user_user(device):
with pytest.raises(CtapError) as e:
device.doMC(user=b"12345678")
device.MC(user=b"12345678")
def test_missing_rp(device):
with pytest.raises(CtapError) as e:
@@ -68,7 +69,7 @@ def test_bad_type_rp(device):
def test_missing_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=None)
device.MC(key_params=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
@@ -90,54 +91,51 @@ def test_bad_type_options(device):
def test_bad_type_rp_name(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"})
def test_bad_type_rp_id(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
def test_bad_type_rp_icon(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
def test_bad_type_user_name(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": b"user_id", "name": 8})
device.MC(user={"id": b"user_id", "name": 8})
def test_bad_type_user_id(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name"})
device.MC(user={"id": "user_id", "name": "name"})
def test_bad_type_user_displayName(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
def test_bad_type_user_icon(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
def test_bad_type_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=["wrong"])
@pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
)
def test_algorithms(device, info, alg):
if ({'alg': alg, 'type': 'public-key'} in info.algorithms):
device.doMC(key_params=[{"alg": alg, "type": "public-key"}])
def test_missing_pubKeyCredParams_type(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
device.MC(key_params=[{"alg": ES256.ALGORITHM}])
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
assert e.value.code == CtapError.ERR.INVALID_CBOR
def test_missing_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"type": "public-key"}])
device.MC(key_params=[{"type": "public-key"}])
assert e.value.code in [
CtapError.ERR.MISSING_PARAMETER,
CtapError.ERR.INVALID_CBOR,
CtapError.ERR.UNSUPPORTED_ALGORITHM,
]
def test_bad_type_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
device.MC(key_params=[{"alg": "7", "type": "public-key"}])
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
def test_unsupported_algorithm(device):
with pytest.raises(CtapError) as e:
@@ -146,26 +144,26 @@ def test_unsupported_algorithm(device):
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
def test_exclude_list(resetdevice):
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}])
def test_exclude_list2(resetdevice):
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
def test_bad_type_exclude_list(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=["1234"])
device.MC(exclude_list=["1234"])
def test_missing_exclude_list_type(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"id": b"1234"}])
device.MC(exclude_list=[{"id": b"1234"}])
def test_missing_exclude_list_id(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": "public-key"}])
device.MC(exclude_list=[{"type": "public-key"}])
def test_bad_type_exclude_list_id(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
device.MC(exclude_list=[{"type": "public-key", "id": "1234"}])
def test_bad_type_exclude_list_type(device):
with pytest.raises(CtapError) as e:

View File

@@ -18,8 +18,9 @@
"""
from fido2.utils import sha256
from fido2.client import CtapError
from fido2.cose import ES256, ES384, ES512, EdDSA
from utils import verify, ES256K
import pytest
def test_authenticate(device):
@@ -30,10 +31,10 @@ def test_authenticate(device):
AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes):
assert len(GARes['res'].get_response(0).authenticator_data) == 37
assert len(GARes['res'].get_response(0).response.authenticator_data) == 37
def test_Check_that_AT_flag_is_not_set(GARes):
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[
@@ -47,12 +48,23 @@ def test_empty_allowList(device):
device.doGA(allow_list=[])
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
@pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
)
def test_algorithms(device, info, alg):
if ({'alg': alg, 'type': 'public-key'} in info.algorithms):
MCRes = device.doMC(key_params=[{"alg": alg, "type": "public-key"}])
res = device.GA(allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
verify(MCRes['res'].attestation_object, res['res'], res['req']['client_data_hash'])
def test_get_assertion_allow_list_filtering_and_buffering(device):
""" Check that authenticator filters and stores items in allow list correctly """
allow_list = []
rp1 = {"id": "rp1.com", "name": "rp1.com"}
rp2 = {"id": "rp2.com", "name": "rp2.com"}
rp1 = {"id": "example.com", "name": "rp1.com"}
rp2 = {"id": "example.com", "name": "rp2.com"}
rp1_registrations = []
rp2_registrations = []
@@ -115,7 +127,7 @@ def test_mismatched_rp(device, GARes):
rp_id += ".com"
with pytest.raises(CtapError) as e:
device.doGA(rp_id=rp_id)
device.GA(rp_id=rp_id)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_missing_rp(device):
@@ -124,9 +136,8 @@ def test_missing_rp(device):
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_rp(device):
with pytest.raises(CtapError) as e:
device.doGA(rp_id={"id": {"type": "wrong"}})
device.GA(rp_id={"id": {"type": "wrong"}})
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
@@ -139,11 +150,11 @@ def test_bad_cdh(device):
def test_bad_allow_list(device):
with pytest.raises(CtapError) as e:
device.doGA(allow_list={"type": "wrong"})
device.GA(allow_list={"type": "wrong"})
def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=["wrong"] + [
device.GA(allow_list=["wrong"] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -166,7 +177,7 @@ def test_option_up(device, info, GARes):
assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes):
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
device.GA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],
@@ -174,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes):
def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"id": b"1234"}] + [
device.GA(allow_list=[{"id": b"1234"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -189,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
device.GA(allow_list=[{"type": "public-key", "id": 42}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
@@ -197,16 +208,24 @@ def test_allow_list_id_wrong_type(device, MCRes):
def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key"}] + [
device.GA(allow_list=[{"type": "public-key"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_user_presence_option_false(device, MCRes):
def test_silent_ok(device, MCRes):
res = device.GA(options={"up": False}, allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
def test_silent_ko(device, MCRes):
cred = MCRes['res'].attestation_object.auth_data.credential_data.credential_id + b'\x00'
with pytest.raises(CtapError) as e:
res = device.GA(options={"up": False}, allow_list=[
{"id": cred, "type": "public-key"}
])
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_credential_resets(device, MCRes, GARes):
device.reset()
with pytest.raises(CtapError) as e:

View File

@@ -57,12 +57,12 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
device.reset()
# It returns a silent authentication
with pytest.raises(CtapError) as e:
ga_res = device.doGA(allow_list=allow_list)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_resident_key(MCRes_DC, info):
pass
@@ -86,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
auths = []
regs = []
# Use unique RP to not collide with other credentials
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
rp = {"id": "example.com", "name": "Example"}
for i in range(0, 3):
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
regs.append(res)
@@ -117,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
auths = resGA.get_assertions()
user_max_GA = auths[0]
print(auths)
for y in ("name", "displayName", "id"):
if (y in user_max_GA):
assert user_max_GA.user[y] == user_max[y]
@@ -127,7 +127,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
"""
Test maximum returned capacity of the RK for the given RP
"""
device.reset()
# Try to determine from get_info, or default to 19.
RK_CAPACITY_PER_RP = info.max_creds_in_list
if not RK_CAPACITY_PER_RP:
@@ -141,7 +141,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
return user
# Use unique RP to not collide with other credentials from other tests.
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
rp = {"id": "example.com", "name": "Example"}
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
# res = device.sendGA(*req.toGA())
@@ -184,10 +184,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
"""
rk_rp = {"id": "rk-cred.org", "name": "Example"}
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res']
server_rp = {"id": "server-cred.com", "name": "Example"}
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res']
allow_list_with_different_rp_cred = [
{
@@ -198,7 +198,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
with pytest.raises(CtapError) as e:
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
@@ -209,10 +209,10 @@ def test_same_userId_overwrites_rk(resetdevice):
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user()
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
# Should overwrite the first credential.
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
@@ -228,7 +228,7 @@ def test_larger_icon_than_128(device):
user = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
device.doMC(rp = rp, rk=True, user = user)
device.MC(rp = rp, options={"rk":True}, user = user)
def test_returned_credential(device):
@@ -255,5 +255,5 @@ def test_returned_credential(device):
device.GNA()
# the returned credential should have user id in it
print(ga_res)
assert 'id' in ga_res.user and len(ga_res.user["id"]) > 0
#print(ga_res)
#assert 'id' in ga_res.user and len(ga_res.user["id"]) > 0

View File

@@ -21,6 +21,7 @@
import pytest
from fido2.ctap import CtapError
from fido2.ctap2.pin import PinProtocolV2, ClientPin
from fido2.utils import websafe_decode
from utils import verify
import os
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
@pytest.fixture(scope="function")
def MCLBK(device):
res = device.doMC(
mc = device.doMC(
rk=True,
extensions={'largeBlob':{'support':'required'}}
)['res']
return res
)
res = mc['res']
ext = mc['client_extension_results']
return {'res': res, 'ext': ext}
@pytest.fixture(scope="function")
def GALBRead(device, MCLBK):
res = device.doGA(
allow_list=[
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'read': True}}
)
assertions = res['res'].get_assertions()
for a in assertions:
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res']
@pytest.fixture(scope="function")
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
@pytest.fixture(scope="function")
def GALBReadLB(GALBRead):
print(GALBRead.get_response(0))
return GALBRead.get_response(0)
@pytest.fixture(scope="function")
def GALBWrite(device, MCLBK):
res = device.doGA(
allow_list=[
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'write': LARGE_BLOB}}
)
assertions = res['res'].get_assertions()
for a in assertions:
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res'].get_response(0)
def test_supports_credblob(info):
@@ -136,15 +140,17 @@ def test_supports_largeblobs(info):
assert info.max_large_blob is None or (info.max_large_blob > 1024)
def test_get_largeblobkey_mc(MCLBK):
assert 'supported' in MCLBK.extension_results
assert MCLBK.extension_results['supported'] is True
assert 'largeBlob' in MCLBK['ext']
assert 'supported' in MCLBK['ext']['largeBlob']
assert MCLBK['ext']['largeBlob']['supported'] is True
def test_get_largeblobkey_ga(GALBReadLBK):
assert GALBReadLBK.large_blob_key is not None
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
assert 'written' in GALBWrite.extension_results
assert GALBWrite.extension_results['written'] is True
assert 'largeBlob' in GALBWrite.client_extension_results
assert 'written' in GALBWrite.client_extension_results['largeBlob']
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
assert 'blob' in GALBReadLB.extension_results
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB

View File

@@ -22,6 +22,7 @@ import pytest
from fido2.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError
from utils import generate_random_user
class CredProtect:
UserVerificationOptional = 1
@@ -30,140 +31,139 @@ class CredProtect:
@pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectOptionalList(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
return res
class TestCredProtect(object):
def test_credprotect_make_credential_1(self, MCCredProtectOptional):
assert MCCredProtectOptional.auth_data.extensions
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
def test_credprotect_make_credential_1(MCCredProtectOptional):
assert MCCredProtectOptional.auth_data.extensions
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
assert MCCredProtectOptionalList.auth_data.extensions
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
def test_credprotect_make_credential_2(MCCredProtectOptionalList):
assert MCCredProtectOptionalList.auth_data.extensions
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
def test_credprotect_make_credential_3(self, MCCredProtectRequired):
assert MCCredProtectRequired.auth_data.extensions
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
def test_credprotect_make_credential_3(MCCredProtectRequired):
assert MCCredProtectRequired.auth_data.extensions
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
""" CredProtectOptional Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_optional_excluded(device, MCCredProtectOptional):
""" CredProtectOptional Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList):
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e:
device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired):
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired):
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
# works
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
# works
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
# works
res = device.doGA()['res'].get_assertions()[0]
# works
res = device.doGA()['res'].get_assertions()[0]
# If there's only one credential, this is None
assert res.number_of_credentials == None
# If there's only one credential, this is None
assert res.number_of_credentials == None
def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
# works
res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 2)
def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
# works
res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 2)
results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
results = device.doGA(allow_list=allow_list)['res'].get_assertions()
# the required credProtect is not returned.
for res in results:
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
# the required credProtect is not returned.
for res in results:
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
):
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
for ext in ["credProtect", "hmac-secret"]:
assert res.auth_data.extensions
assert ext in res.auth_data.extensions
assert res.auth_data.extensions[ext] == True
for ext in ["credProtect", "hmac-secret"]:
assert res.auth_data.extensions
assert ext in res.auth_data.extensions
assert res.auth_data.extensions[ext] == True
class TestCredProtectUv:
def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
pin = "12345678"
def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
client_pin.set_pin(pin)
pin = "12345678"
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
client_pin.set_pin(pin)
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 3)
assert res1.number_of_credentials in (None, 3)

View File

@@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension
from fido2.utils import hmac_sha256
from fido2.ctap2.pin import PinProtocolV2
from fido2.webauthn import UserVerificationRequirement
from fido2.client import ClientError
from utils import *
salt1 = b"\xa5" * 32
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
return res['res'].attestation_object
@pytest.fixture(scope="class")
def hmac(resetdevice):
return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
def test_hmac_secret_make_credential(MCHmacSecret):
assert MCHmacSecret.auth_data.extensions
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
@@ -55,51 +52,51 @@ def test_fake_extension(device):
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
def test_hmac_secret_entropy(device, MCHmacSecret, salts
):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results
ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
#print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']))
if len(salts) == 1:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.6
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.6
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
if len(salts) == 2:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.6
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.6
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5
def get_output(device, MCHmacSecret, hmac, salts):
def get_output(device, MCHmacSecret, salts):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results
ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
if len(salts) == 2:
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
else:
return ext["hmacGetSecret"]['output1']
return ext.hmac_get_secret.output1
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
def test_hmac_secret_sanity(device, MCHmacSecret):
output1 = get_output(device, MCHmacSecret, (salt1,))
output12 = get_output(
device, MCHmacSecret, hmac, (salt1, salt2)
device, MCHmacSecret, (salt1, salt2)
)
output21 = get_output(
device, MCHmacSecret, hmac, (salt2, salt1)
device, MCHmacSecret, (salt2, salt1)
)
assert output12[0] == output1
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
assert output21[0] == output12[1]
assert output12[0] != output12[1]
def test_missing_keyAgreement(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_keyAgreement(device):
with pytest.raises(CtapError):
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}})
def test_missing_saltAuth(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_saltAuth(device):
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
device.GA(extensions={"hmac-secret": {2: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_missing_saltEnc(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_saltEnc(device,):
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
device.GA(extensions={"hmac-secret": { 3: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_auth(device, hmac, MCHmacSecret):
def test_bad_auth(device, MCHmacSecret):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
bad_auth = list(hout[3][:])
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
bad_auth = bytes(bad_auth)
key_agreement = {
1: 2,
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
-1: 1,
-2: b'\x00'*32,
-3: b'\x00'*32,
}
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}})
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
def test_invalid_salt_length(device, hmac, salts):
with pytest.raises(ValueError) as e:
def test_invalid_salt_length(device, salts):
with pytest.raises((CtapError,ClientError)) as e:
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
device.doGA(extensions={"hmacGetSecret": hout})
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_get_next_assertion_has_extension(
device, hmac, salts
device, salts
):
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
accounts = 3
regs = []
auths = []
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
rp = {"id": f"example.com", "name": "ExampleRP_2"}
fixed_users = [generate_random_user() for _ in range(accounts)]
for i in range(accounts):
res = device.doMC(extensions={"hmacCreateSecret": True},
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
assert "hmac-secret" in ext
assert isinstance(ext["hmac-secret"], bytes)
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
key = hmac.process_get_output(x)
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
salts = [salt1]
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0
ext_no_uv = auth_no_uv.auth_data.extensions
ext_no_uv = auth_no_uv.response.authenticator_data.extensions
assert ext_no_uv
assert "hmac-secret" in ext_no_uv
assert isinstance(ext_no_uv["hmac-secret"], bytes)
@@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
hout['salt2'] = salts[1]
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
assert auth_uv.authenticator_data.flags & (1 << 2)
ext_uv = auth_uv.extension_results
assert auth_uv.response.authenticator_data.flags & (1 << 2)
ext_uv = auth_uv.client_extension_results
assert ext_uv
assert "hmacGetSecret" in ext_uv
assert len(ext_uv["hmacGetSecret"]) == len(salts)
assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts)
# Now see if the hmac-secrets are different
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']

View File

@@ -69,7 +69,7 @@ def test_minpin(SetMinPin, MCMinPin):
def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin):
assert not MCMinPin.auth_data.extensions
assert "minPinLength" not in MCMinPin.auth_data.extensions
#assert "minPinLength" not in MCMinPin.auth_data.extensions
def test_setminpin(device, SetMinPin, MCMinPin):
cfg = FidoConfig(device)

View File

@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
res = device.doGA(ctap1=False, allow_list=[
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
# Test FIDO2 register works with U2F auth

View File

@@ -196,16 +196,13 @@ class TestHID(object):
device.set_cid(cid2) # send ping on 2nd channel
device.send_raw("\x81\x00\x39")
time.sleep(0.1)
device.send_raw("\x00")
cmd, r = device.recv_raw() # busy response
time.sleep(0.1)
device.set_cid(cid1) # finish 1st channel ping
device.send_raw("\x00")
device.set_cid(cid2)
assert cmd == 0xBF
assert r[0] == CtapError.ERR.CHANNEL_BUSY
@@ -213,9 +210,11 @@ class TestHID(object):
cmd, r = device.recv_raw() # ping response
assert cmd == 0x81
assert len(r) == 0x39
cmd, r = device.recv_raw() # ping response
def test_cid_0(self, device):
device.reset()
time.sleep(0.1)
device.set_cid(b"\x00\x00\x00\x00")
device.send_raw(
"\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00"

View File

@@ -123,7 +123,7 @@ def test_auth(reset_oath):
resp = list_apdu(reset_oath)
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01]
resp = send_apdu(reset_oath, 0xA4, 0x04, 0x00, aid)
assert(resp[15] == TAG_CHALLENGE)
assert(resp[16] == 8)

View File

@@ -3,6 +3,6 @@
/usr/sbin/pcscd &
sleep 2
rm -f memory.flash
cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid
cp -R tests/docker/fido2/* /usr/local/lib/python3.11/dist-packages/fido2/hid
./build_in_docker/pico_fido > /dev/null &
pytest tests

View File

@@ -19,6 +19,11 @@
from fido2.webauthn import AttestedCredentialData
from fido2.cose import CoseKey
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from fido2.utils import bytes2int, int2bytes
from cryptography.hazmat.backends import default_backend
import random
import string
import secrets
@@ -175,3 +180,29 @@ class Timeout(object):
if self.timer:
self.timer.cancel()
self.timer.join()
class ES256K(CoseKey):
ALGORITHM = -47
_HASH_ALG = hashes.SHA256()
def verify(self, message, signature):
if self[-1] != 8:
raise ValueError("Unsupported elliptic curve")
ec.EllipticCurvePublicNumbers(
bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP256K1()
).public_key(default_backend()).verify(
signature, message, ec.ECDSA(self._HASH_ALG)
)
@classmethod
def from_cryptography_key(cls, public_key):
pn = public_key.public_numbers()
return cls(
{
1: 2,
3: cls.ALGORITHM,
-1: 8,
-2: int2bytes(pn.x, 32),
-3: int2bytes(pn.y, 32),
}
)

View File

@@ -1,451 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
/*
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"""
import sys
import argparse
import platform
from binascii import hexlify
from words import words
from threading import Event
from typing import Mapping, Any, Optional, Callable
import struct
import urllib.request
import json
from enum import IntEnum, unique
try:
from fido2.ctap2.config import Config
from fido2.ctap2 import Ctap2
from fido2.hid import CtapHidDevice, CTAPHID
from fido2.utils import bytes2int, int2bytes
from fido2 import cbor
from fido2.ctap import CtapDevice, CtapError
from fido2.ctap2.pin import PinProtocol, _PinUv
from fido2.ctap2.base import args
except:
print('ERROR: fido2 module not found! Install fido2 package.\nTry with `pip install fido2`')
sys.exit(-1)
try:
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives import hashes
from cryptography import x509
except:
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
sys.exit(-1)
from enum import IntEnum
from binascii import hexlify
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
from secure_key import windows as skey
elif (platform.system() == 'Darwin'):
from secure_key import macos as skey
else:
print('ERROR: platform not supported')
sys.exit(-1)
def get_pki_data(url, data=None, method='GET'):
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; '
'rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
method = 'GET'
if (data is not None):
method = 'POST'
req = urllib.request.Request(f"https://www.picokeys.com/pico/pico-fido/{url}/",
method=method,
data=data,
headers={'User-Agent': user_agent, })
response = urllib.request.urlopen(req)
resp = response.read().decode('utf-8')
j = json.loads(resp)
return j
class VendorConfig(Config):
class PARAM(IntEnum):
VENDOR_COMMAND_ID = 0x01
VENDOR_AUT_CT = 0x02
class CMD(IntEnum):
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
class RESP(IntEnum):
KEY_AGREEMENT = 0x01
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
def enable_device_aut(self, ct):
self._call(
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
VendorConfig.PARAM.VENDOR_AUT_CT: ct
},
)
def disable_device_aut(self):
self._call(
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
},
)
class Ctap2Vendor(Ctap2):
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
super().__init__(device=device, strict_cbor=strict_cbor)
def send_vendor(
self,
cmd: int,
data: Optional[Mapping[int, Any]] = None,
*,
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
) -> Mapping[int, Any]:
"""Sends a VENDOR message to the device, and waits for a response.
:param cmd: The command byte of the request.
:param data: The payload to send (to be CBOR encoded).
:param event: Optional threading.Event used to cancel the request.
:param on_keepalive: Optional function called when keep-alive is sent by
the authenticator.
"""
request = struct.pack(">B", cmd)
if data is not None:
request += cbor.encode(data)
response = self.device.call(CTAPHID.VENDOR_FIRST + 1, request, event, on_keepalive)
status = response[0]
if status != 0x00:
raise CtapError(status)
enc = response[1:]
if not enc:
return {}
decoded = cbor.decode(enc)
if self._strict_cbor:
expected = cbor.encode(decoded)
if expected != enc:
raise ValueError(
"Non-canonical CBOR from Authenticator.\n"
f"Got: {enc.hex()}\nExpected: {expected.hex()}"
)
if isinstance(decoded, Mapping):
return decoded
raise TypeError("Decoded value of wrong type")
def vendor(
self,
cmd: int,
sub_cmd: int,
sub_cmd_params: Optional[Mapping[int, Any]] = None,
pin_uv_protocol: Optional[int] = None,
pin_uv_param: Optional[bytes] = None,
) -> Mapping[int, Any]:
"""CTAP2 authenticator vendor command.
This command is used to configure various authenticator features through the
use of its subcommands.
This method is not intended to be called directly. It is intended to be used by
an instance of the Config class.
:param sub_cmd: A Config sub command.
:param sub_cmd_params: Sub command specific parameters.
:param pin_uv_protocol: PIN/UV auth protocol version used.
:param pin_uv_param: PIN/UV Auth parameter.
"""
return self.send_vendor(
cmd,
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
)
class Vendor:
"""Implementation of the CTAP2.1 Authenticator Vendor API. It is vendor implementation.
:param ctap: An instance of a CTAP2Vendor object.
:param pin_uv_protocol: An instance of a PinUvAuthProtocol.
:param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session.
"""
@unique
class CMD(IntEnum):
VENDOR_BACKUP = 0x01
VENDOR_MSE = 0x02
VENDOR_UNLOCK = 0x03
VENDOR_EA = 0x04
@unique
class PARAM(IntEnum):
PARAM = 0x01
COSE_KEY = 0x02
class SUBCMD(IntEnum):
ENABLE = 0x01
DISABLE = 0x02
KEY_AGREEMENT = 0x01
EA_CSR = 0x01
EA_UPLOAD = 0x02
class RESP(IntEnum):
PARAM = 0x01
COSE_KEY = 0x02
def __init__(
self,
ctap: Ctap2Vendor,
pin_uv_protocol: Optional[PinProtocol] = None,
pin_uv_token: Optional[bytes] = None,
):
self.ctap = ctap
self.pin_uv = (
_PinUv(pin_uv_protocol, pin_uv_token)
if pin_uv_protocol and pin_uv_token
else None
)
self.__key_enc = None
self.__iv = None
self.vcfg = VendorConfig(ctap)
def _call(self, cmd, sub_cmd, params=None):
if params:
params = {k: v for k, v in params.items() if v is not None}
else:
params = None
if self.pin_uv:
msg = (
b"\xff" * 32
+ b"\x0d"
+ struct.pack("<b", sub_cmd)
+ (cbor.encode(params) if params else b"")
)
pin_uv_protocol = self.pin_uv.protocol.VERSION
pin_uv_param = self.pin_uv.protocol.authenticate(self.pin_uv.token, msg)
else:
pin_uv_protocol = None
pin_uv_param = None
return self.ctap.vendor(cmd, sub_cmd, params, pin_uv_protocol, pin_uv_param)
def backup_save(self, filename):
ret = self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.ENABLE,
)
data = ret[Vendor.RESP.PARAM]
d = int.from_bytes(skey.get_secure_key(), 'big')
with open(filename, 'wb') as fp:
fp.write(b'\x01')
fp.write(data)
pk = ec.derive_private_key(d, ec.SECP256R1())
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
fp.write(signature)
print('Remember the following words in this order:')
for c in range(24):
coef = (d//(2048**c))%2048
print(f'{(c+1):02d} - {words[coef]}')
def backup_load(self, filename):
d = 0
if (d == 0):
for c in range(24):
word = input(f'Introduce word {(c+1):02d}: ')
while (word not in words):
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
coef = words.index(word)
d = d+(2048**c)*coef
pk = ec.derive_private_key(d, ec.SECP256R1())
pb = pk.public_key()
with open(filename, 'rb') as fp:
format = fp.read(1)[0]
if (format == 0x1):
data = fp.read(60)
signature = fp.read()
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
skey.set_secure_key(pk)
return self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.DISABLE,
{
Vendor.PARAM.PARAM: data
},
)
def mse(self):
sk = ec.generate_private_key(ec.SECP256R1())
pn = sk.public_key().public_numbers()
self.__pb = sk.public_key().public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
key_agreement = {
1: 2,
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
-1: 1,
-2: int2bytes(pn.x, 32),
-3: int2bytes(pn.y, 32),
}
ret = self._call(
Vendor.CMD.VENDOR_MSE,
Vendor.SUBCMD.KEY_AGREEMENT,
{
Vendor.PARAM.COSE_KEY: key_agreement,
},
)
peer_cose_key = ret[VendorConfig.RESP.KEY_AGREEMENT]
x = bytes2int(peer_cose_key[-2])
y = bytes2int(peer_cose_key[-3])
pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
shared_key = sk.exchange(ec.ECDH(), pk)
xkdf = HKDF(
algorithm=hashes.SHA256(),
length=12+32,
salt=None,
info=self.__pb
)
kdf_out = xkdf.derive(shared_key)
self.__key_enc = kdf_out[12:]
self.__iv = kdf_out[:12]
def encrypt_chacha(self, data):
chacha = ChaCha20Poly1305(self.__key_enc)
ct = chacha.encrypt(self.__iv, data, self.__pb)
return ct
def unlock_device(self):
ct = self.get_skey()
self._call(
Vendor.CMD.VENDOR_UNLOCK,
Vendor.SUBCMD.ENABLE,
{
Vendor.PARAM.PARAM: ct
},
)
def _get_key_device(self):
return skey.get_secure_key()
def get_skey(self):
self.mse()
ct = self.encrypt_chacha(self._get_key_device())
return ct
def enable_device_aut(self):
ct = self.get_skey()
self.vcfg.enable_device_aut(ct)
def disable_device_aut(self):
self.vcfg.disable_device_aut()
def csr(self):
return self._call(
Vendor.CMD.VENDOR_EA,
Vendor.SUBCMD.EA_CSR,
)[Vendor.RESP.PARAM]
def upload_ea(self, der):
self._call(
Vendor.CMD.VENDOR_EA,
Vendor.SUBCMD.EA_UPLOAD,
{
Vendor.PARAM.PARAM: der
}
)
def parse_args():
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command")
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
parser_backup = subparser.add_parser('backup', help='Manages the backup of Pico Fido.')
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
parser_backup.add_argument('filename', help='File to save or load the backup.')
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
parser_attestation.add_argument('subcommand', choices=['csr'])
parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.')
args = parser.parse_args()
return args
def secure(vdr, args):
if (args.subcommand == 'enable'):
vdr.enable_device_aut()
elif (args.subcommand == 'unlock'):
vdr.unlock_device()
elif (args.subcommand == 'disable'):
vdr.disable_device_aut()
def backup(vdr, args):
if (args.subcommand == 'save'):
vdr.backup_save(args.filename)
elif (args.subcommand == 'load'):
vdr.backup_load(args.filename)
def attestation(vdr, args):
if (args.subcommand == 'csr'):
if (args.filename is None):
csr = x509.load_der_x509_csr(vdr.csr())
data = urllib.parse.urlencode({'csr': csr.public_bytes(Encoding.PEM)}).encode()
j = get_pki_data('csr', data=data)
cert = x509.load_pem_x509_certificate(j['x509'].encode())
else:
with open(args.filename, 'rb') as f:
dataf = f.read()
try:
cert = x509.load_der_x509_certificate(dataf)
except ValueError:
cert = x509.load_pem_x509_certificate(dataf)
vdr.upload_ea(cert.public_bytes(Encoding.DER))
def main(args):
print('Pico Fido Tool v1.4')
print('Author: Pol Henarejos')
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
print('')
print('')
dev = next(CtapHidDevice.list_devices(), None)
vdr = Vendor(Ctap2Vendor(dev))
if (args.command == 'secure'):
secure(vdr, args)
elif (args.command == 'backup'):
backup(vdr, args)
elif (args.command == 'attestation'):
attestation(vdr, args)
def run():
args = parse_args()
main(args)
if __name__ == "__main__":
run()

View File

@@ -51,7 +51,9 @@ def get_secure_key():
try:
backend = get_backend(False)
key = backend.get_password(DOMAIN, USERNAME)[0]
except keyring.errors.KeyringError:
if (key is None):
raise TypeError
except (keyring.errors.KeyringError, TypeError):
try:
key = generate_secure_key(False)[0] # It should be True, but secure enclave causes python segfault
except keyring.errors.PasswordSetError:

View File

@@ -0,0 +1,44 @@
import sys
DOMAIN = "PicoKeys.com"
USERNAME = "Pico-Fido"
try:
import keyring
except:
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyring`')
sys.exit(-1)
try:
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
from cryptography.hazmat.primitives.asymmetric import ec
except:
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
sys.exit(-1)
def generate_secure_key():
pkey = ec.generate_private_key(ec.SECP256R1())
set_secure_key(pkey)
return keyring.get_password(DOMAIN, USERNAME)
def get_d(key):
return load_pem_private_key(key, password=None).private_numbers().private_value.to_bytes(32, 'big')
def set_secure_key(pk):
try:
keyring.delete_password(DOMAIN, USERNAME)
except:
pass
keyring.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode())
def get_secure_key():
key = None
try:
key = keyring.get_password(DOMAIN, USERNAME)
if (key is None):
raise TypeError
except (keyring.errors.KeyringError, TypeError):
key = generate_secure_key()
return get_d(key.encode())

View File

@@ -2,12 +2,55 @@
git submodule update --init --recursive
sudo apt update
if [[ $1 == "pico" ]]; then
sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib
git clone https://github.com/raspberrypi/pico-sdk
cd pico-sdk
git checkout tags/2.1.1
git submodule update --init
cd ..
git clone https://github.com/raspberrypi/picotool
cd picotool
git submodule update --init
mkdir build
cd build
cmake -DPICO_SDK_PATH=../../pico-sdk ..
make -j`nproc`
sudo make install
cd ../..
mkdir build_pico
cd build_pico
cmake -DPICO_SDK_PATH=../pico-sdk ..
make
cd ..
elif [[ $1 == "esp32" ]]; then
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
git checkout tags/v5.5
./install.sh esp32s3
. ./export.sh
cd ..
idf.py set-target esp32s3
idf.py all
mkdir -p release
cd build
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin @flash_args
cd ..
cd esp-idf
./install.sh esp32s2
. ./export.sh
cd ..
idf.py set-target esp32s2
idf.py all
mkdir -p release
cd build
esptool.py --chip ESP32-S2 merge_bin -o ../release/pico_fido_esp32-s2.bin @flash_args
cd ..
else
mkdir build
cd build
cmake -DENABLE_EMULATION=1 ..
make
fi