297 Commits

Author SHA1 Message Date
Pol Henarejos
ab6cc09c08 Merge branch 'main' into development 2026-01-26 23:39:33 +01:00
Pol Henarejos
cba1db783f Upgrade to v7.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 23:39:18 +01:00
Pol Henarejos
9788029e8a Upgrade to Pico Keys SDK 8.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 23:24:29 +01:00
Pol Henarejos
31a6315721 Transmit CBOR errors in SW x64 with CCID.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 17:22:00 +01:00
Pol Henarejos
9fb8d475b3 Old files. Not used anymore.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 15:54:59 +01:00
Pol Henarejos
c0298ece7d Remove PHY and MEMORY vendor commands as they are available through rescue applet.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 15:40:08 +01:00
Pol Henarejos
bea7706d63 Fix emulation build
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 01:27:12 +01:00
Pol Henarejos
cfd22c2d2c Fix ccid max packet length & interface naming.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 01:20:22 +01:00
Pol Henarejos
8ea118fe91 Fix OATH in iOS Authenticator. Fixes #248.
For strange reason, iOS app doesn't follow strictly YKOATH spec. When there are remaining bytes after serial, it assumes there's challenge (and thus, access code), but algorithm 7B is there. Apparently algorithm 7B is only returned when challenge is present but I could not see where it is used.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-26 01:18:51 +01:00
Pol Henarejos
8e6f571b48 Move rtc
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-24 01:15:23 +01:00
Pol Henarejos
3c20800839 Add rtc to credential.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-24 01:14:46 +01:00
Pol Henarejos
f2eef5b839 Use new VID:PID allocated to Pico Fido.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 12:33:08 +01:00
Pol Henarejos
bc6ebdd069 Upgrade to new layout
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 12:09:42 +01:00
Pol Henarejos
3f890757ac Not present
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 01:00:18 +01:00
Pol Henarejos
18d68d7e05 Fix needs power cycle logic.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 00:57:31 +01:00
Pol Henarejos
c8d62de621 Add vendor commands via CCID
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 00:26:51 +01:00
Pol Henarejos
60165c21ca Fix vendor keydev loading
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 00:26:27 +01:00
Pol Henarejos
55a60f8875 Fix power_cycle behavior
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-22 00:26:13 +01:00
Pol Henarejos
7ed90007ef Add support for slots 3 & 4 in OTP.
Both slots are activated by clicking three or four times the BOOTSEL button.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-19 16:37:19 +01:00
Pol Henarejos
c23cc9ffe1 Add set/get RTC.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-19 16:36:49 +01:00
Pol Henarejos
dc4565a8fb Fix LED default parameters in Pimoroni boards.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-15 01:17:18 +01:00
Pol Henarejos
dfd7927413 Merge branch 'main' into development 2026-01-15 00:51:05 +01:00
Pol Henarejos
804ee68e86 Remove non-standard MAKE CREDENTIAL step.
It may collide with other userName and the purpose is achieved cleaner via Rescue interface.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-08 10:46:51 +01:00
Pol Henarejos
cfc23a1f0e Merge branch 'main' into development 2026-01-07 23:39:08 +01:00
Pol Henarejos
fe49149d86 Update README with up-to-date info.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-06 21:20:04 +01:00
Pol Henarejos
81d97f1a18 Upgrade to v7.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:56:09 +01:00
Pol Henarejos
d16016cf1e Upgrade Pico Keys SDK to v8.2
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:51:11 +01:00
Pol Henarejos
2e0333677b Fix button logic.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:39:46 +01:00
Pol Henarejos
becdc94339 Disable button press by default since LED may not be properly configured until it is commissioned.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:37:30 +01:00
Pol Henarejos
bd499ae1d4 Remove print
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 19:37:27 +01:00
Pol Henarejos
5fc84d7097 Reset internal state of GA to avoid phantom requests on GNA.
When a previous GA had more than 1 credential, it stored the full list in the internal state. Later, if a GA had only 1 credential, subsequent GNA returned older state of previous non-related GA.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 12:36:44 +01:00
Pol Henarejos
ac7e34522a Fixed resident credential storage when two userId have the same prefix.
Added a specific test for this case.

Fixes #241.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2026-01-05 12:34:04 +01:00
Pol Henarejos
70dec5596a Fix build.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-29 20:36:37 +01:00
Pol Henarejos
2331dcb3ec Blink led three times to acknowledge proper commissioning.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-29 20:16:22 +01:00
Pol Henarejos
42b8b0af5f Fix pimoroni led
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-27 22:04:03 +01:00
Pol Henarejos
ceb54d9a08 Move pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-27 16:03:07 +01:00
Pol Henarejos
527943fbba Releaser is available up to 6.7.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-26 19:53:46 +01:00
Pol Henarejos
a7630dca5c Merge branch 'main' into development 2025-12-11 20:01:15 +01:00
Pol Henarejos
7dddfd971e Build only necessary boards
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-11 20:01:08 +01:00
Pol Henarejos
29f942dab9 Update pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-11 15:42:47 +01:00
Pol Henarejos
aa9df892d3 Revert "Move EDDSA to another branch."
This reverts commit 1867f0330f.
2025-12-11 15:41:47 +01:00
Pol Henarejos
7ac2ce30f0 Revert "Move other curves to another branch."
This reverts commit 46720fb387.
2025-12-11 15:40:16 +01:00
Pol Henarejos
e86862033c Revert "Move enterprise attestation to another branch."
This reverts commit 1d21d93b74.
2025-12-11 15:40:10 +01:00
Pol Henarejos
ae36143498 Revert "Move Secure Boot to another branch."
This reverts commit d90dbb6c5f.
2025-12-11 15:39:57 +01:00
Pol Henarejos
d90dbb6c5f Move Secure Boot to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 21:39:14 +01:00
Pol Henarejos
1d21d93b74 Move enterprise attestation to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 21:39:10 +01:00
Pol Henarejos
8b9be258de Fix applet cmp
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 19:15:35 +01:00
Pol Henarejos
7f31e6a00f Merge branch 'main' into development 2025-12-09 18:52:23 +01:00
Pol Henarejos
46720fb387 Move other curves to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 18:52:13 +01:00
Pol Henarejos
1867f0330f Move EDDSA to another branch.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-09 15:56:31 +01:00
Pol Henarejos
bb542e3b83 Add is_gpg flag for fido2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-07 20:34:42 +01:00
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
a1cb2fa3bf Merge branch 'main' into development 2025-12-02 14:40:25 +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
faceaf8fc6 Merge branch 'main' into development
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-12-02 10:22:07 +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
c33b133c6b Add support for RP2354.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2025-11-15 19:57:43 +01:00
Pol Henarejos
8036a5dda4 Merge branch 'main' into development 2025-11-15 19:57:19 +01:00
Pol Henarejos
f3866c4a93 Merge pull request #189 from sylvainpelissier/autobuild
Bump autobuild bump to esp-idf 5.5.1 and add pico parallel build
2025-11-10 00:58:19 +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
Sylvain
105cf61866 Bump autobuild to esp-idf 5.5.1 and add pico parallel build 2025-09-27 10:42:13 +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
68 changed files with 2996 additions and 1979 deletions

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

@@ -19,6 +19,7 @@ on:
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@v6.7.0
with:
tag: nightly-${{ matrix.refs }}
rm: true
token: ${{ secrets.GITHUB_TOKEN }}
files: release/*.*

View File

@@ -19,6 +19,7 @@ on:
branches: [ "main", "development", "eddsa" ]
schedule:
- cron: '23 5 * * 4'
workflow_dispatch:
jobs:
build:

View File

@@ -17,13 +17,18 @@
cmake_minimum_required(VERSION 3.13)
set(USB_VID 0x2E8A)
set(USB_PID 0x10FE)
if(ESP_PLATFORM)
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
set(DENABLE_POWER_ON_RESET 0)
set(EXTRA_COMPONENT_DIRS pico-keys-sdk/config/esp32/components src/fido)
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()
@@ -32,21 +37,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)
endif()
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)
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
if(ENABLE_POWER_ON_RESET)
@@ -77,13 +69,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
@@ -101,6 +102,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}
@@ -113,8 +115,7 @@ set(SOURCES ${SOURCES}
)
endif()
set(USB_ITF_HID 1)
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 3)
if(ESP_PLATFORM)
project(pico_fido)
endif()
@@ -152,13 +153,20 @@ if(ENABLE_EMULATION)
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)
target_link_libraries(pico_fido PRIVATE pico_keys_sdk mbedtls pthread m)
else()
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/>.

101
README.md
View File

@@ -1,5 +1,7 @@
# Pico FIDO
This project transforms your Raspberry Pi Pico into an integrated FIDO Passkey, functioning like a standard USB Passkey 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 OpenPGP + Fido, see: https://github.com/polhenarejos/pico-fido2. Available through [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App").
## Features
Pico FIDO includes the following features:
@@ -30,51 +32,69 @@ Pico FIDO includes the following features:
- 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 directly typed
- Yubico Authenticator app compatible
- 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 PicoKey App.
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.
## 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.
Pico FIDO is an open platform, so exercise caution. The flash memory contents can be easily dumped, potentially revealing private/master keys. It is not feasible to encrypt the content, meaning at least one key (the master key) must be stored in clear text.
If the Pico is stolen, the private and secret keys can be accessed.
**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 visit the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") to 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 shipped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with OpenSC or similar software, you will need to modify the Info.plist of the CCID driver to add these VID/PID values 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 with the following command:
```sh
./patch_vidpid.sh VID:PID input_hsm_file.uf2 output_hsm_file.uf2
```
You can use any VID/PID (e.g., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
UF2 files are shiped with a VID/PID granted by RaspberryPi (2E8A:10FE). If you plan to use it with OpenSC or similar tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App").
For ease of use, the pure-browser option [Pico Patcher tool](https://www.picokeys.com/pico-patcher/) is highly recommended.
You can use whatever VID/PID for internal purposes, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
## Build
Note that the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App") is the most recommended.
## 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.
```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, the default 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` finishes, the binary file `pico_fido.uf2` will be generated. Put your Pico board into loading mode by holding the BOOTSEL button while plugging it in, then copy the UF2 file to the new USB mass storage Pico device. Once copied, the Pico mass storage will disconnect automatically, and the Pico board will reset with the new firmware. A blinking LED will indicate that the device is ready to work.
- `NitroHSM`
- `NitroFIDO2`
- `NitroStart`
- `NitroPro`
- `Nitro3`
- `Yubikey5`
- `YubikeyNeo`
- `YubiHSM`
- `Gnuk`
- `GnuPG`
**Remark:** Pico FIDO uses the HID interface, so VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary values or the default ones. They are only necessary in case you need to use 3rd-party tools from other vendors.
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:
@@ -118,6 +138,53 @@ To run a subset of tests, use the `-k <test>` flag:
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,103 +1,25 @@
#!/bin/bash
VERSION_MAJOR="5"
VERSION_MINOR="12-eddsa1"
VERSION_MAJOR="7"
VERSION_MINOR="4"
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
#if ! [[ -z "${GITHUB_SHA}" ]]; then
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
#fi
rm -rf release/*
mkdir -p build_release
mkdir -p release
rm -rf -- release/*
cd build_release
for board in 0xcb_helios \
adafruit_feather_rp2040_usb_host \
adafruit_feather_rp2040 \
adafruit_itsybitsy_rp2040 \
adafruit_kb2040 \
adafruit_macropad_rp2040 \
adafruit_qtpy_rp2040 \
adafruit_trinkey_qt2040 \
amethyst_fpga \
archi \
arduino_nano_rp2040_connect \
cytron_maker_pi_rp2040 \
datanoisetv_rp2040_dsp \
eetree_gamekit_rp2040 \
garatronic_pybstick26_rp2040 \
gen4_rp2350_24 \
gen4_rp2350_24ct \
gen4_rp2350_24t \
gen4_rp2350_28 \
gen4_rp2350_28ct \
gen4_rp2350_28t \
gen4_rp2350_32 \
gen4_rp2350_32ct \
gen4_rp2350_32t \
gen4_rp2350_35 \
gen4_rp2350_35ct \
gen4_rp2350_35t \
hellbender_2350A_devboard \
ilabs_challenger_rp2350_bconnect \
ilabs_challenger_rp2350_wifi_ble \
ilabs_opendec02 \
melopero_perpetuo_rp2350_lora \
melopero_shake_rp2040 \
metrotech_xerxes_rp2040 \
net8086_usb_interposer \
nullbits_bit_c_pro \
phyx_rick_tny_rp2350 \
pi-plates_micropi \
pico \
pico_w \
pico2 \
pimoroni_badger2040 \
pimoroni_interstate75 \
pimoroni_keybow2040 \
pimoroni_motor2040 \
pimoroni_pga2040 \
pimoroni_pga2350 \
pimoroni_pico_plus2_rp2350 \
pimoroni_picolipo_4mb \
pimoroni_picolipo_16mb \
pimoroni_picosystem \
pimoroni_plasma2040 \
pimoroni_plasma2350 \
pimoroni_servo2040 \
pimoroni_tiny2040 \
pimoroni_tiny2040_2mb \
pimoroni_tiny2350 \
pololu_3pi_2040_robot \
pololu_zumo_2040_robot \
seeed_xiao_rp2040 \
seeed_xiao_rp2350 \
solderparty_rp2040_stamp \
solderparty_rp2040_stamp_carrier \
solderparty_rp2040_stamp_round_carrier \
solderparty_rp2350_stamp_xl \
solderparty_rp2350_stamp \
sparkfun_micromod \
sparkfun_promicro \
sparkfun_promicro_rp2350 \
sparkfun_thingplus \
switchscience_picossci2_conta_base \
switchscience_picossci2_dev_board \
switchscience_picossci2_micro \
switchscience_picossci2_rp2350_breakout \
switchscience_picossci2_tiny \
tinycircuits_thumby_color_rp2350 \
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 \
weact_studio_rp2040_2mb \
weact_studio_rp2040_4mb \
weact_studio_rp2040_8mb \
weact_studio_rp2040_16mb \
wiznet_w5100s_evb_pico
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
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
SECURE_BOOT_PKEY="${SECURE_BOOT_PKEY:-../../ec_private_key.pem}"
boards=("pico" "pico2")
for board_name in "${boards[@]}"
do
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

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)

View File

@@ -4,13 +4,17 @@
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

View File

@@ -1,6 +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 bootloader_support esp_partition esp_tinyusb zorxx__neopixel mbedtls efuse
INCLUDE_DIRS .
REQUIRES esp_tinyusb mbedtls efuse pico-keys-sdk
)
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/>.
*/
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#include "pico_keys.h"
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "hid/ctap_hid.h"
@@ -40,6 +41,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len);
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);
extern void reset_gna_state();
extern int cmd_read_config();
@@ -58,6 +60,9 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
}
if (cap_supported(CAP_FIDO2)) {
if (cmd == CTAPHID_CBOR) {
if (data[0] != CTAP_GET_NEXT_ASSERTION) {
reset_gna_state();
}
if (data[0] == CTAP_MAKE_CREDENTIAL) {
return cbor_make_credential(data + 1, len - 1);
}
@@ -103,7 +108,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
return CTAP1_ERR_INVALID_CMD;
}
void cbor_thread(void) {
void *cbor_thread(void *arg) {
(void)arg;
card_init_core1();
while (1) {
uint32_t m;
@@ -114,13 +120,18 @@ void cbor_thread(void) {
if (m == EV_EXIT) {
break;
}
apdu.sw = cbor_parse(cbor_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 {
res_APDU[0] = apdu.sw;
//apdu.sw = 0;
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;
@@ -128,9 +139,7 @@ void cbor_thread(void) {
flag = EV_EXEC_FINISHED;
queue_add_blocking(&card_to_usb_q, &flag);
}
#ifdef ESP_PLATFORM
vTaskDelete(NULL);
#endif
return NULL;
}
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
@@ -204,9 +213,14 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
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,

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"
#ifndef ESP_PLATFORM
#include "common.h"
#else
@@ -27,7 +28,7 @@
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -35,14 +36,15 @@
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
#include "pico_keys.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;
@@ -104,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;
@@ -124,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;
}
@@ -159,26 +138,38 @@ 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));
file_put_data(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);
return 0;
}
low_flash_available();
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;
}
@@ -209,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
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);
@@ -241,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
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;
}
@@ -279,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) {
@@ -289,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();
}
@@ -415,12 +414,25 @@ 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);
file_put_data(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);
mbedtls_platform_zeroize(paddedNewPin, sizeof(paddedNewPin));
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));
needs_power_cycle = false;
goto err; //No return
}
else if (subcommand == 0x4) { //changePIN
@@ -449,6 +461,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (needs_power_cycle) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
}
uint8_t sharedSecret[64];
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
@@ -462,10 +477,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
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;
file_put_data(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];
@@ -474,7 +489,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
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) {
@@ -488,9 +512,28 @@ 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;
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((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
@@ -512,15 +555,34 @@ 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];
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);
}
file_put_data(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 *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
memcpy(tmpf, file_get_data(ef_minpin), file_get_size(ef_minpin));
@@ -530,6 +592,8 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
low_flash_available();
resetPinUvAuthToken();
resetPersistentPinUvAuthToken();
needs_power_cycle = false;
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
@@ -550,7 +614,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);
@@ -564,16 +630,19 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (needs_power_cycle) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
}
uint8_t sharedSecret[64];
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;
file_put_data(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 = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
@@ -582,9 +651,18 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
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);
}
@@ -596,32 +674,63 @@ 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;
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 = (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;
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((uint8_t)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));
needs_power_cycle = false;
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
@@ -635,6 +744,7 @@ 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;

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,22 +23,24 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "pico_keys.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;
//CborEncoder mapEncoder;
@@ -65,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 == 0x7f) {
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);
@@ -112,9 +121,12 @@ int cbor_config(const uint8_t *data, size_t len) {
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);
@@ -131,8 +143,8 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (subcommand == 0x7f) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
if (subcommand == 0xFF) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
if (!file_has_data(ef_keydev_enc)) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -153,7 +165,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);
}
@@ -161,7 +173,7 @@ 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);
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) {
@@ -174,6 +186,31 @@ int cbor_config(const uint8_t *data, size_t len) {
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
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);
}
@@ -197,6 +234,10 @@ 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;
}
if (forceChangePin) {
resetPersistentPinUvAuthToken();
resetPinUvAuthToken();
}
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0;
@@ -220,7 +261,8 @@ int cbor_config(const uint8_t *data, size_t len) {
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(vendorAutCt);
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]);
}

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 "pico_keys.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);
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) {
if (verify((uint8_t)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++) {
@@ -144,11 +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((uint8_t)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;
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04;
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 (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;
@@ -239,15 +259,15 @@ 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);
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);
}
@@ -296,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
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));
@@ -335,7 +357,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
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) {
@@ -352,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
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, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
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);
@@ -394,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
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, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
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) {
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
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, (int)cred.alg,
(int)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);
}

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"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#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 "pico_keys.h"
#include "apdu.h"
#include "cbor_make_credential.h"
#include "credential.h"
@@ -42,6 +42,22 @@ uint32_t timerx = 0;
uint8_t *datax = NULL;
size_t lenx = 0;
void reset_gna_state() {
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
credential_free(&credsx[i]);
}
if (datax) {
free(datax);
datax = NULL;
}
lenx = 0;
residentx = false;
timerx = 0;
flagsx = 0;
credentialCounter = 0;
numberOfCredentialsx = 0;
}
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
(void) data;
(void) len;
@@ -57,19 +73,7 @@ int cbor_get_next_assertion(const uint8_t *data, size_t len) {
credentialCounter++;
err:
if (error != CborNoError || credentialCounter == numberOfCredentialsx) {
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
credential_free(&credsx[i]);
}
if (datax) {
free(datax);
datax = NULL;
}
lenx = 0;
residentx = false;
timerx = 0;
flagsx = 0;
credentialCounter = 0;
numberOfCredentialsx = 0;
reset_gna_state();
if (error == CborErrorImproperValue) {
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
}
@@ -93,7 +97,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;
@@ -235,6 +239,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);
//}
@@ -243,12 +253,12 @@ 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
@@ -279,6 +289,8 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
}
bool silent = (up == false && uv == false);
if (allowList_len > 0) {
for (size_t e = 0; e < allowList_len; e++) {
if (allowList[e].type.present == false || allowList[e].id.present == false) {
@@ -287,12 +299,49 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
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;
}
}
}
}
}
}
@@ -302,12 +351,13 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
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;
@@ -318,8 +368,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
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 {
@@ -342,15 +391,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
}
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].board_creation > creds[i].board_creation) {
Credential tmp = creds[j];
creds[j] = creds[i];
creds[i] = tmp;
}
}
}
}
@@ -380,15 +462,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;
@@ -410,106 +498,110 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
int ret = 0;
uint8_t largeBlobKey[32] = {0};
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);
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] = {0};
if (extensions.present == true) {
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++;
}
if (extensions.thirdPartyPayment != NULL) {
if (extensions.thirdPartyPayment == 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));
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));
}
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
}
}
if (extensions.hmac_secret != NULL) {
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] = {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) {
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));
}
}
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(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 (extensions.thirdPartyPayment != NULL) {
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));
}
}
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();
@@ -519,10 +611,7 @@ 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) & 0xFF;
*pa++ = (ctr >> 16) & 0xFF;
*pa++ = (ctr >> 8) & 0xFF;
*pa++ = ctr & 0xFF;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, ext, ext_len); pa += ext_len;
if ((size_t)(pa - aut_data) != aut_data_len) {
CBOR_ERROR(CTAP1_ERR_OTHER);
@@ -531,31 +620,42 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
memcpy(pa, clientDataHash.data, clientDataHash.len);
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_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
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_ecdsa_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);
}
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
md = NULL;
}
mbedtls_ecp_keypair ekey;
mbedtls_ecp_keypair_init(&ekey);
size_t olen = 0;
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);
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 {
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);
// Bogus signature
olen = 64;
memset(sig, 0x0B, olen);
}
mbedtls_ecp_keypair_free(&ekey);
if (ret != 0) {
@@ -563,13 +663,13 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
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_CBOR_PAYLOAD, 0);
@@ -578,7 +678,19 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
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));
@@ -588,7 +700,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) {
@@ -601,8 +713,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"));
@@ -615,11 +726,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)));
}

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"
@@ -27,22 +28,36 @@ int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
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, 6));
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));
@@ -50,11 +65,18 @@ int cbor_get_info() {
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"));
@@ -91,12 +113,31 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5));
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));
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256K, &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));
@@ -124,13 +165,36 @@ 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) {

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 "pico_keys.h"
#include "mbedtls/sha256.h"
static uint64_t expectedLength = 0, expectedNextOffset = 0;
@@ -129,10 +129,7 @@ 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) & 0xFF;
verify_data[36] = (offset >> 16) & 0xFF;
verify_data[37] = (offset >> 24) & 0xFF;
put_uint32_t_le((uint32_t)offset, verify_data + 34);
mbedtls_sha256(set.data, set.len, verify_data + 38, 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);

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 "pico_keys.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));
@@ -127,12 +132,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
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);
@@ -202,31 +234,57 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
}
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
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) {
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) {
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_ES256K) {
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;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
}
#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
}
@@ -251,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) {
@@ -291,13 +349,27 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
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))) {
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);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
}
credential_free(&ecred);
}
@@ -307,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) {
@@ -326,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
size_t cred_id_len = 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;
@@ -342,13 +416,13 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
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);
@@ -366,30 +440,91 @@ 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;
}
mbedtls_ecp_keypair ekey;
mbedtls_ecp_keypair_init(&ekey);
@@ -410,19 +545,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
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) & 0xFF;
*pa++ = (ctr >> 16) & 0xFF;
*pa++ = (ctr >> 8) & 0xFF;
*pa++ = ctr & 0xFF;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, aaguid, 16); pa += 16;
*pa++ = ((uint16_t)cred_id_len >> 8) & 0xFF;
*pa++ = (uint16_t)cred_id_len & 0xFF;
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
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) {
@@ -433,15 +572,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
memcpy(pa, clientDataHash.data, clientDataHash.len);
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) {
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) {
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);
}
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
#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);
}
@@ -450,16 +591,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) {
mbedtls_ecp_keypair_free(&ekey);
mbedtls_ecp_keypair_init(&ekey);
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
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;
}
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);
@@ -474,7 +622,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, extensions.largeBlobKey == ptrue && options.rk == ptrue ? 5 : 4));
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"));
@@ -482,12 +637,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 || is_nitrokey ? 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 || is_nitrokey ? -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 || is_nitrokey) {
if (self_attestation == false || is_nk) {
CborEncoder arrEncoder;
file_t *ef_cert = NULL;
if (enterpriseAttestation == 2) {
@@ -503,8 +658,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
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));
@@ -530,6 +687,10 @@ err:
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
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);
}

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,34 +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"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#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 "pico_keys.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;
}
@@ -112,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) {
@@ -140,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);
@@ -160,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);
@@ -236,7 +206,12 @@ 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));
uint8_t keydev[32] = {0};
if (load_keydev(keydev) != 0) {
CBOR_ERROR(CTAP1_ERR_OTHER);
}
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, keydev, 32);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP2_ERR_PROCESSING);
@@ -248,9 +223,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
}
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 %s", pico_serial_str);
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);
@@ -258,12 +231,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) {
@@ -274,17 +242,6 @@ 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);
}
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, vendorParam.data, (uint16_t)vendorParam.len);
}
low_flash_available();
goto err;
}
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);

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 "fido.h"
#include "pico_keys.h"
#include "fido.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,38 +38,35 @@ 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) & 0xFF;
resp->ctr[1] = (ctr >> 16) & 0xFF;
resp->ctr[2] = (ctr >> 8) & 0xFF;
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));
@@ -77,12 +74,12 @@ int cmd_authenticate() {
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);
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);
mbedtls_ecp_keypair_free(&key);
if (ret != 0) {
return SW_EXEC_ERROR();
}

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 "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
@@ -37,9 +37,9 @@ int u2f_select(app_t *a, uint8_t force) {
if (cap_supported(CAP_U2F)) {
a->process_apdu = u2f_process_apdu;
a->unload = u2f_unload;
return CCID_OK;
return PICOKEY_OK;
}
return CCID_ERR_FILE_NOT_FOUND;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
INITIALIZER ( u2f_ctor ) {
@@ -47,7 +47,7 @@ INITIALIZER ( u2f_ctor ) {
}
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";
@@ -59,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();
@@ -69,15 +69,11 @@ 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();
}
@@ -100,8 +96,14 @@ int cmd_register() {
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();
}

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/>.
*/
#include "apdu.h"

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"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -26,23 +27,60 @@
#include "ctap.h"
#include "random.h"
#include "files.h"
#include "pico_keys.h"
#include "otp.h"
int credential_derive_chacha_key(uint8_t *outk);
extern bool has_set_rtc();
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;
}
@@ -56,7 +94,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];
@@ -111,27 +149,31 @@ int credential_create(CborCharString *rpId,
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
if (has_set_rtc()) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t) get_rtc_time()));
}
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);
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) {
@@ -147,8 +189,12 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
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);
@@ -179,7 +225,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1);
}
else if (val_u == 0x06) {
CBOR_FIELD_GET_UINT(cred->creation, 1);
CBOR_FIELD_GET_UINT(cred->board_creation, 1);
}
else if (val_u == 0x07) {
cred->extensions.present = true;
@@ -214,6 +260,9 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x0C) {
CBOR_FIELD_GET_UINT(cred->rtc_creation, 1);
}
else {
CBOR_ADVANCE(1);
}
@@ -236,17 +285,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);
if (cred->extensions.present) {
CBOR_FREE_BYTE_STRING(cred->extensions.credBlob);
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;
}
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) {
@@ -271,12 +322,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 (rcred.userId.len == cred.userId.len && memcmp(rcred.userId.data, cred.userId.data, rcred.userId.len) == 0) {
sloti = i;
credential_free(&rcred);
new_record = false;
@@ -287,11 +338,14 @@ 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);
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);
file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
free(data);
if (new_record == true) { //increase rps
@@ -344,13 +398,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) {
@@ -359,7 +413,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;
}
@@ -373,8 +427,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;
@@ -42,7 +43,7 @@ typedef struct Credential {
CborByteString userId;
CborCharString userName;
CborCharString userDisplayName;
uint64_t creation;
uint64_t board_creation;
CredExtensions extensions;
const bool *use_sign_count;
int64_t alg;
@@ -50,15 +51,36 @@ typedef struct Credential {
CborByteString id;
CredOptions opts;
bool present;
uint64_t rtc_creation;
} Credential;
#define CRED_PROT_UV_OPTIONAL 0x01
#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,
@@ -69,7 +91,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,
@@ -80,5 +102,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,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 _CTAP_H_
@@ -114,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)
@@ -121,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
@@ -128,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,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 _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 "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "kek.h"
#include "apdu.h"
#include "ctap.h"
#include "files.h"
@@ -24,30 +25,39 @@
#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
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#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,
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
};
const uint8_t fido_aid_backup[] = {
8,
0xB0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
};
const uint8_t atr_fido[] = {
23,
0x3b, 0xfd, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe, 0x15, 0x80, 0x73, 0xc0, 0x21, 0xc0, 0x57, 0x59,
@@ -66,9 +76,9 @@ int fido_select(app_t *a, uint8_t force) {
if (cap_supported(CAP_FIDO2)) {
a->process_apdu = fido_process_apdu;
a->unload = fido_unload;
return CCID_OK;
return PICOKEY_OK;
}
return CCID_ERR_FILE_NOT_FOUND;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
extern uint8_t (*get_version_major)();
@@ -81,10 +91,11 @@ INITIALIZER ( fido_ctor ) {
get_version_major = fido_get_version_major;
get_version_minor = fido_get_version_minor;
register_app(fido_select, fido_aid);
register_app(fido_select, fido_aid_backup);
}
int fido_unload() {
return CCID_OK;
return PICOKEY_OK;
}
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
@@ -106,12 +117,23 @@ 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) {
@@ -133,12 +155,14 @@ int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
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;
}
@@ -150,7 +174,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *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);
@@ -186,22 +210,56 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
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_ecp_keypair *key) {
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
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;
@@ -237,11 +295,12 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
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));
@@ -277,18 +336,38 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
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...");
@@ -300,16 +379,18 @@ int scan_files() {
mbedtls_ecdsa_free(&ecdsa);
return ret;
}
uint8_t kdata[64];
uint8_t keydev[32] = {0};
size_t key_size = 0;
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
if (ret != CCID_OK) {
return ret;
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;
}
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
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");
@@ -321,10 +402,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[2048];
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;
@@ -369,34 +455,50 @@ 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)) {
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();
extern bool needs_power_cycle;
void init_fido() {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
needs_power_cycle = false;
}
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;
}
@@ -404,21 +506,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() {
@@ -439,16 +538,32 @@ extern int cmd_register();
extern int cmd_authenticate();
extern int cmd_version();
extern int cbor_parse(int, uint8_t *, size_t);
extern int cbor_vendor(const uint8_t *data, size_t len);
extern void driver_init_hid();
#define CTAP_CBOR 0x10
int cmd_vendor() {
uint8_t *old_buf = res_APDU;
driver_init_hid();
int ret = cbor_vendor(apdu.data, apdu.nc);
res_APDU = old_buf;
if (ret != 0) {
return set_res_sw(0x64, ret);
}
res_APDU_size += 1;
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
return SW_OK();
}
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;
if (ret != 0) {
return set_res_sw(0x64, ret);
}
res_APDU_size += 1;
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
return SW_OK();
@@ -459,6 +574,7 @@ static const cmd_t cmds[] = {
{ CTAP_AUTHENTICATE, cmd_authenticate },
{ CTAP_VERSION, cmd_version },
{ CTAP_CBOR, cmd_cbor },
{ 0x41, cmd_vendor },
{ 0x00, 0x0 }
};

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 _FIDO_H_
#define _FIDO_H_
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#ifndef ESP_PLATFORM
@@ -28,12 +28,10 @@
#endif
#include "mbedtls/ecdsa.h"
#ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h"
#ifndef ENABLE_EMULATION
#include "hid/ctap_hid.h"
#else
#include <stdbool.h>
#endif
#include "hid/ctap_hid.h"
#define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32)
@@ -41,7 +39,7 @@
#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,
@@ -53,20 +51,28 @@ extern void init_fido();
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
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);
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
@@ -76,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
@@ -83,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();
@@ -126,9 +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 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,54 +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 = 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
{ .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];
@@ -62,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,16 @@
#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
@@ -37,6 +40,8 @@
#define EF_OATH_CODE 0xBAFF
#define EF_OTP_SLOT1 0xBB00
#define EF_OTP_SLOT2 0xBB01
#define EF_OTP_SLOT3 0xBB02
#define EF_OTP_SLOT4 0xBB03
#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN
extern file_t *ef_keydev;
@@ -46,5 +51,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,26 +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 "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "version.h"
#include "files.h"
#include "asn1.h"
#include "management.h"
bool is_gpg = true;
int man_process_apdu();
int man_unload();
@@ -40,9 +42,12 @@ int man_select(app_t *a, uint8_t force) {
apdu.ne = res_APDU_size;
if (force) {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
}
return CCID_OK;
is_gpg = false;
return PICOKEY_OK;
}
INITIALIZER ( man_ctor ) {
@@ -50,7 +55,7 @@ INITIALIZER ( man_ctor ) {
}
int man_unload() {
return CCID_OK;
return PICOKEY_OK;
}
bool cap_supported(uint16_t cap) {
@@ -65,7 +70,7 @@ bool cap_supported(uint16_t cap) {
if (tag == TAG_USB_ENABLED) {
uint16_t ecaps = tag_data[0];
if (tag_len == 2) {
ecaps = (tag_data[0] << 8) | tag_data[1];
ecaps = get_uint16_t_be(tag_data);
}
return ecaps & cap;
}
@@ -74,17 +79,34 @@ bool cap_supported(uint16_t 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++] = TAG_USB_SUPPORTED;
res_APDU[res_APDU_size++] = 2;
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
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;
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++] = TAG_FORM_FACTOR;
res_APDU[res_APDU_size++] = 1;
@@ -94,23 +116,36 @@ int man_get_config() {
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_NFC_SUPPORTED;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = 0x00;
if (!file_has_data(ef)) {
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
res_APDU[res_APDU_size++] = 2;
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
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;
res_APDU[res_APDU_size++] = TAG_NFC_ENABLED;
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));
@@ -132,15 +167,32 @@ int cmd_write_config() {
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 }
};

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/>.
*/
#ifndef _MANAGEMENT_H_
#define _MANAGEMENT_H_
#include <stdlib.h>
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif

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 "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,6 +24,7 @@
#include "asn1.h"
#include "crypto_utils.h"
#include "management.h"
extern bool is_nk;
#define MAX_OATH_CRED 255
#define CHALLENGE_LEN 8
@@ -44,6 +45,10 @@
#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
@@ -56,6 +61,7 @@
#define PROP_INC 0x01
#define PROP_TOUCH 0x02
#define PROP_PIN 0x03
int oath_process_apdu();
int oath_unload();
@@ -88,21 +94,27 @@ int oath_select(app_t *a, uint8_t force) {
res_APDU[res_APDU_size++] = sizeof(challenge);
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++] = TAG_ALGO;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = *pin_data;
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;
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;
apdu.ne = res_APDU_size;
return CCID_OK;
return PICOKEY_OK;
}
return CCID_ERR_FILE_NOT_FOUND;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
INITIALIZER ( oath_ctor ) {
@@ -110,7 +122,7 @@ INITIALIZER ( oath_ctor ) {
}
int oath_unload() {
return CCID_OK;
return PICOKEY_OK;
}
file_t *find_oath_cred(const uint8_t *name, size_t name_len) {
@@ -270,16 +282,27 @@ int cmd_list() {
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((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
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++] = (uint8_t)(name.len + 1);
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;
}
}
}
}
@@ -337,7 +360,7 @@ int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const u
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;
@@ -354,7 +377,7 @@ int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const u
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() {
@@ -391,19 +414,11 @@ int cmd_calculate() {
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
if (ret != CCID_OK) {
if (ret != PICOKEY_OK) {
return SW_EXEC_ERROR();
}
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
uint64_t v =
((uint64_t) chal.data[0] << 56) |
((uint64_t) chal.data[1] << 48) |
((uint64_t) chal.data[2] << 40) |
((uint64_t) chal.data[3] << 32) |
((uint64_t) chal.data[4] << 24) |
((uint64_t) chal.data[5] << 16) |
((uint64_t) chal.data[6] << 8) |
(uint64_t) chal.data[7];
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);
@@ -411,14 +426,7 @@ int cmd_calculate() {
asn1_ctx_t ctxt;
asn1_ctx_init(tmp, (uint16_t)ef_size, &ctxt);
asn1_find_tag(&ctxt, TAG_IMF, &chal);
chal.data[0] = (v >> 56) & 0xFF;
chal.data[1] = (v >> 48) & 0xFF;
chal.data[2] = (v >> 40) & 0xFF;
chal.data[3] = (v >> 32) & 0xFF;
chal.data[4] = (v >> 24) & 0xFF;
chal.data[5] = (v >> 16) & 0xFF;
chal.data[6] = (v >> 8) & 0xFF;
chal.data[7] = v & 0xff;
put_uint64_t_be(v, chal.data);
file_put_data(ef, tmp, (uint16_t)ef_size);
low_flash_available();
free(tmp);
@@ -466,7 +474,7 @@ int cmd_calculate_all() {
else {
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
if (ret != CCID_OK) {
if (ret != PICOKEY_OK) {
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = key.data[1];
}
@@ -577,14 +585,14 @@ int cmd_verify_hotp() {
return SW_INCORRECT_PARAMS();
}
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &code) == true) {
code_int = (code.data[0] << 24) | (code.data[1] << 16) | (code.data[2] << 8) | code.data[3];
code_int = get_uint32_t_be(code.data);
}
int ret = calculate_oath(0x01, key.data, key.len, chal.data, chal.len);
if (ret != CCID_OK) {
if (ret != PICOKEY_OK) {
return SW_EXEC_ERROR();
}
uint32_t res_int = (res_APDU[2] << 24) | (res_APDU[3] << 16) | (res_APDU[4] << 8) | res_APDU[5];
uint32_t res_int = get_uint32_t_be(res_APDU + 2);
if (res_APDU[1] == 6) {
res_int %= (uint32_t) 1e6;
}
@@ -599,10 +607,100 @@ int cmd_verify_hotp() {
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
@@ -612,12 +710,14 @@ int cmd_verify_hotp() {
#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 },
@@ -627,6 +727,7 @@ static const cmd_t cmds[] = {
{ 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 }
};

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 "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,14 +24,17 @@
#include "asn1.h"
#include "hid/ctap_hid.h"
#include "usb.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#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"
#ifndef ENABLE_EMULATION
#include "tusb.h"
#endif
#define FIXED_SIZE 16
#define KEY_SIZE 16
@@ -46,6 +49,11 @@
#define CONFIG_LED_INV 0x10
#define CONFIG_STATUS_MASK 0x1f
#define CONFIG3_VALID 0x01
#define CONFIG4_VALID 0x02
#define CONFIG3_TOUCH 0x04
#define CONFIG4_TOUCH 0x08
/* EXT Flags */
#define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press)
#define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field
@@ -111,17 +119,15 @@ typedef struct otp_config {
}) otp_config_t;
#define otp_config_size sizeof(otp_config_t)
uint16_t otp_status();
uint16_t otp_status(bool is_otp);
int otp_process_apdu();
int otp_unload();
#ifndef ENABLE_EMULATION
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);
#endif
const uint8_t otp_aid[] = {
7,
@@ -140,13 +146,10 @@ int otp_select(app_t *a, uint8_t force) {
else {
config_seq = 0;
}
otp_status();
memmove(res_APDU, res_APDU + 1, 6);
res_APDU_size = 6;
apdu.ne = res_APDU_size;
return CCID_OK;
otp_status(false);
return PICOKEY_OK;
}
return CCID_ERR_FILE_NOT_FOUND;
return PICOKEY_ERR_FILE_NOT_FOUND;
}
uint8_t modhex_tab[] =
@@ -163,18 +166,17 @@ extern void scan_all();
void init_otp() {
if (scanned == false) {
scan_all();
for (uint8_t i = 0; i < 2; i++) {
for (uint8_t i = 0; i < 4; 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];
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;
put_uint16_t_be(counter, new_data + otp_config_size);
file_put_data(ef, new_data, sizeof(new_data));
}
}
@@ -204,16 +206,14 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
return crc & 0xFFFF;
}
#ifndef ENABLE_EMULATION
static uint8_t session_counter[2] = { 0 };
#endif
int otp_button_pressed(uint8_t slot) {
init_otp();
if (!cap_supported(CAP_OTP)) {
return 3;
}
#ifndef ENABLE_EMULATION
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
uint16_t slot_ef = EF_OTP_SLOT1 + slot - 1;
file_t *ef = search_dynamic_file(slot_ef);
const uint8_t *data = file_get_data(ef);
otp_config_t *otp_config = (otp_config_t *) data;
if (file_has_data(ef) == false) {
@@ -222,31 +222,25 @@ int otp_button_pressed(uint8_t slot) {
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) {
@@ -258,9 +252,8 @@ int otp_button_pressed(uint8_t slot) {
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));
@@ -271,6 +264,7 @@ int otp_button_pressed(uint8_t slot) {
append_keyboard_buffer((const uint8_t *) "\r", 1);
}
}
#endif
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
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
@@ -284,7 +278,7 @@ int otp_button_pressed(uint8_t slot) {
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;
@@ -294,9 +288,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;
@@ -304,8 +297,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);
@@ -326,47 +318,100 @@ 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;
put_uint16_t_be(counter, new_data + otp_config_size);
file_put_data(ef, new_data, sizeof(new_data));
low_flash_available();
}
}
#else
(void) slot;
#endif
return 0;
}
INITIALIZER( otp_ctor ) {
register_app(otp_select, otp_aid);
button_pressed_cb = otp_button_pressed;
#ifndef ENABLE_EMULATION
hid_set_report_cb = otp_hid_set_report_cb;
hid_get_report_cb = otp_hid_get_report_cb;
#endif
}
int otp_unload() {
return CCID_OK;
return PICOKEY_OK;
}
uint16_t otp_status() {
uint8_t status_byte = 0x0;
uint16_t otp_status_ext() {
for (int i = 0; i < 4; i++) {
file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
if (file_has_data(ef)) {
res_APDU[res_APDU_size++] = 0xB0 + i;
res_APDU[res_APDU_size++] = 0; // Filled later
uint8_t *p = res_APDU + res_APDU_size;
otp_config_t *otp_config = (otp_config_t *)file_get_data(ef);
*p++ = 0xA0;
*p++ = 2;
*p++ = otp_config->tkt_flags;
*p++ = otp_config->cfg_flags;
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
}
else if (otp_config->tkt_flags & OATH_HOTP) {
}
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
}
else {
*p++ = 0xC0;
*p++ = 6;
memcpy(p, otp_config->fixed_data, 6);
p += 6;
}
uint8_t len = p - (res_APDU + res_APDU_size);
res_APDU[res_APDU_size - 1] = len;
res_APDU_size += len;
}
}
return SW_OK();
}
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();
}
@@ -375,14 +420,16 @@ bool check_crc(const otp_config_t *data) {
return crc == 0xF0B8;
}
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;
file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
if (p1 == 0x03 && p2 != 0x0) {
return SW_INCORRECT_P1P2();
}
uint16_t slot = (p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
file_t *ef = file_new(slot);
if (file_has_data(ef)) {
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
@@ -398,23 +445,24 @@ int cmd_otp() {
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) {
else if (p1 == 0x04 || p1 == 0x05) { // Update slot
otp_config_t *odata = (otp_config_t *) apdu.data;
if (p1 == 0x05 && p2 != 0x0) {
return SW_INCORRECT_P1P2();
}
uint16_t slot = (p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
return SW_WRONG_DATA();
}
file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
file_t *ef = search_dynamic_file(slot);
if (file_has_data(ef)) {
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
@@ -426,17 +474,32 @@ int cmd_otp() {
(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);
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);
file_t *ef2 = file_new(EF_OTP_SLOT2);
uint16_t slot1 = EF_OTP_SLOT1, slot2 = EF_OTP_SLOT2;
if (apdu.ne > 0) {
if (apdu.ne != 2) {
return SW_WRONG_LENGTH();
}
slot1 += apdu.data[0];
slot2 += apdu.data[1];
}
file_t *ef1 = file_new(slot1);
file_t *ef2 = file_new(slot2);
if (file_has_data(ef1)) {
memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
ef1_data = true;
@@ -446,6 +509,8 @@ int cmd_otp() {
}
else {
delete_file(ef1);
// When a dynamic file is deleted, existing referenes are invalidated
ef2 = file_new(slot2);
}
if (ef1_data) {
file_put_data(ef2, tmp, sizeof(tmp));
@@ -454,34 +519,68 @@ int cmd_otp() {
delete_file(ef2);
}
low_flash_available();
config_seq++;
return otp_status(_is_otp);
}
else if (p1 == 0x10) {
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) {
file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
else if (p1 == 0x14) {
otp_status_ext();
}
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP
if ((p1 == 0x38 || p1 == 0x28) && p2 != 0x0) {
return SW_INCORRECT_P1P2();
}
uint16_t slot = (p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
file_t *ef = search_dynamic_file(slot);
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)) {
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);
memcpy(challenge + 6, pico_serial_str, 10);
@@ -494,6 +593,9 @@ int cmd_otp() {
res_APDU_size = 16;
}
}
if (ret == 0) {
status_byte = 0x00;
}
}
}
return SW_OK();
@@ -521,8 +623,6 @@ int otp_process_apdu() {
return SW_INS_NOT_SUPPORTED();
}
#ifndef ENABLE_EMULATION
uint8_t otp_frame_rx[70] = {0};
uint8_t otp_frame_tx[70] = {0};
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
@@ -532,9 +632,7 @@ 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[frame_len] = ~crc & 0xff;
frame[frame_len + 1] = ~crc >> 8;
frame_len += 2;
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) {
@@ -544,49 +642,45 @@ int otp_send_frame(uint8_t *frame, size_t frame_len) {
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 (itf == ITF_KEYBOARD) {
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));
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;
}
memcpy(otp_frame_rx + rseq * 7, buffer, 7);
if (rseq == 9) {
DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = (otp_frame_rx[66] << 8 | 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;
int ret = otp_process_apdu();
if (ret == 0x9000 && res_APDU_size > 0) {
otp_send_frame(apdu.rdata, apdu.rlen);
}
}
else {
printf("[OTP] Bad CRC!\n");
}
else {
printf("[OTP] Bad CRC!\n");
}
}
}
@@ -624,10 +718,9 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
}
else {
res_APDU = buffer;
otp_status();
otp_status(true);
DEBUG_DATA(buffer, 8);
}
return reqlen;
}
#endif

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 0x050C
#define PICO_FIDO_VERSION 0x0704
#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, PublicKeyCredentialParameters, PublicKeyCredentialType
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
@@ -117,7 +129,7 @@ class Device():
self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
self.__server.allowed_algorithms = [
PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
for p in self.__client._backend.info.algorithms
]
@@ -216,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
@@ -226,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):
@@ -267,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)
@@ -294,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']
@@ -325,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:
@@ -347,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:
@@ -416,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")
@@ -442,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,11 +22,7 @@ RUN apt install -y libccid \
cmake \
libfuse-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
RUN git clone https://github.com/polhenarejos/python-fido2.git
WORKDIR /python-fido2
RUN git checkout development
RUN pip3 install .
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

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__)
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
from . import openbsd as backend
else:
raise Exception("Unsupported platform")
from . import emulation as backend
list_descriptors = backend.list_descriptors
@@ -62,6 +64,10 @@ 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
@@ -109,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,
@@ -129,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
@@ -139,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
@@ -159,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
@@ -194,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)
@@ -202,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
@@ -220,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

@@ -51,7 +51,7 @@ def test_lockout(device, resetdevice, client_pin):
res = client_pin.get_pin_retries()
assert res[0] == attempts
if err == CtapError.ERR.PIN_AUTH_BLOCKED:
if e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED:
device.reboot()
client_pin = ClientPin(resetdevice.client()._backend.ctap2)

View File

@@ -20,8 +20,6 @@
from fido2.client import CtapError
from fido2.cose import ES256, ES384, ES512, EdDSA
import fido2.features
fido2.features.webauthn_json_mapping.enabled = False
from utils import ES256K
import pytest
@@ -51,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:
@@ -71,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
@@ -93,35 +91,23 @@ 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})
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"])
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
@pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
@@ -132,13 +118,13 @@ def test_algorithms(device, info, alg):
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.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.INVALID_CBOR,
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
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
@@ -158,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

@@ -31,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=[
@@ -63,8 +63,8 @@ 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 = []
@@ -127,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):
@@ -137,7 +137,7 @@ def test_missing_rp(device):
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:
@@ -150,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"}
]
)
@@ -177,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"}
],
@@ -185,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"}
]
)
@@ -200,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"}
]
@@ -208,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,21 +198,40 @@ 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
def test_same_prefix_userId(device):
"""
A make credential request with two different UserIds that share the same prefix should NOT overwrite.
"""
rp = {"id": "sameprefix.org", "name": "Example"}
user1 = {"id": b"user_12", "name": "A fixed name", "displayName": "A fixed display name"}
user2 = {"id": b"user_123", "name": "A fixed name", "displayName": "A fixed display name"}
mc_res1 = device.MC(rp = rp, options={"rk":True}, user = user1)
# Should not overwrite the first credential.
mc_res2 = device.MC(rp = rp, options={"rk":True}, user = user2)
ga_res = device.GA(rp_id=rp['id'])['res']
assert ga_res.number_of_credentials == 2
def test_same_userId_overwrites_rk(resetdevice):
"""
A make credential request with a UserId & Rp that is the same as an existing one should overwrite.
"""
resetdevice.reset()
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 +247,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 +274,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.5
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
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.5
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
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

@@ -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

@@ -1,469 +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 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, ClientPin, PinProtocolV2
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
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
CONFIG_VENDOR_PROTOTYPE = 0x7f
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(
VendorConfig.CMD.CONFIG_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(
VendorConfig.CMD.CONFIG_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, pin_uv_protocol=pin_uv_protocol, pin_uv_token=pin_uv_token)
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):
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)
from words import words
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):
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)
from words import words
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):
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)
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.add_argument('-p','--pin', help='Specify the PIN of the device.', required=True)
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.6')
print('Author: Pol Henarejos')
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
print('')
print('')
dev = next(CtapHidDevice.list_devices(), None)
ctap = Ctap2Vendor(dev)
client_pin = ClientPin(ctap)
token = client_pin.get_pin_token(args.pin, permissions=ClientPin.PERMISSION.AUTHENTICATOR_CFG)
vdr = Vendor(ctap, pin_uv_protocol=PinProtocolV2(), pin_uv_token=token)
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

@@ -1,61 +0,0 @@
import sys
import keyring
DOMAIN = "PicoKeys.com"
USERNAME = "Pico-Fido"
try:
import keyring
from keyrings.osx_keychain_keys.backend import OSXKeychainKeysBackend, OSXKeychainKeyType, OSXKeyChainKeyClassType
except:
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
sys.exit(-1)
try:
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
except:
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
sys.exit(-1)
def get_backend(use_secure_enclave=False):
backend = OSXKeychainKeysBackend(
key_type=OSXKeychainKeyType.EC, # Key type, e.g. RSA, RC, DSA, ...
key_class_type=OSXKeyChainKeyClassType.Private, # Private key, Public key, Symmetric-key
key_size_in_bits=256,
is_permanent=True, # If set, saves the key in keychain; else, returns a transient key
use_secure_enclave=use_secure_enclave, # Saves the key in the T2 (TPM) chip, requires a code-signed interpreter
access_group=None, # Limits key management and retrieval to set group, requires a code-signed interpreter
is_extractable=True # If set, private key is extractable; else, it can't be retrieved, but only operated against
)
return backend
def generate_secure_key(use_secure_enclave=False):
backend = get_backend(use_secure_enclave)
backend.set_password(DOMAIN, USERNAME, password=None)
return backend.get_password(DOMAIN, USERNAME)
def get_d(key):
return key.private_numbers().private_value.to_bytes(32, 'big')
def set_secure_key(pk):
backend = get_backend(False)
try:
backend.delete_password(DOMAIN, USERNAME)
except:
pass
backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
def get_secure_key():
key = None
try:
backend = get_backend(False)
key = backend.get_password(DOMAIN, USERNAME)[0]
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:
key = generate_secure_key(False)[0]
return get_d(key)

View File

@@ -1,44 +0,0 @@
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())

File diff suppressed because one or more lines are too long

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 -j`nproc`
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.1
./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