138 Commits
v1.2 ... v2.2

Author SHA1 Message Date
Pol Henarejos
7f97ea4f24 Updating readme
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-05 12:56:50 +02:00
Pol Henarejos
467523769e Upgrading version to v2.2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-05 11:09:36 +02:00
Pol Henarejos
2d295d0d98 Fix severe bug zeroing outside memory.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-05 11:09:03 +02:00
Pol Henarejos
0758644583 Fix generic build
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-05 11:01:54 +02:00
Pol Henarejos
c3a5b8e708 Adding building script
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-05 06:55:32 +02:00
Pol Henarejos
b134d261ae Adding hid tests. They worked... meh
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 20:02:36 +02:00
Pol Henarejos
4f93b984cd Adding U2F tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 19:38:07 +02:00
Pol Henarejos
ea0547ef49 Adding tests for credProtect.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 17:29:47 +02:00
Pol Henarejos
e5b7dff8cc Adding credential management tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:58:59 +02:00
Pol Henarejos
6a077d0d8f Enabling credential management.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:58:49 +02:00
Pol Henarejos
7c271fc4f3 Fix counting mismatches.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:58:33 +02:00
Pol Henarejos
2734259c02 Cosmetic changes
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:58:19 +02:00
Pol Henarejos
ba4faa9840 No more icon
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:58:03 +02:00
Pol Henarejos
746c324113 Adding client_pin fixture.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:57:54 +02:00
Pol Henarejos
ac224063fc Fix freeing memory.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:57:45 +02:00
Pol Henarejos
cf4778b9ad Fixes in enumerations.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 16:57:34 +02:00
Pol Henarejos
3f80acc81b Added ctap1 interoperability test.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 11:42:54 +02:00
Pol Henarejos
4cea6ebe87 U2F keys shall be verified only when the credID is u2f.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 11:42:41 +02:00
Pol Henarejos
02e5eb8dba Updating pointer
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 00:37:24 +02:00
Pol Henarejos
037019b348 Update codeql.yml 2022-10-04 00:33:54 +02:00
Pol Henarejos
ae237db9ca Added tests for PIN.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 00:00:13 +02:00
Pol Henarejos
c2e16fda41 Fix change pin for protocol v2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-04 00:00:03 +02:00
Pol Henarejos
f84d36b1da Add return error when no pin is set on getUVToken.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 17:47:27 +02:00
Pol Henarejos
04aaf0f572 Fix test.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 16:10:45 +02:00
Pol Henarejos
577edbb62f Adding hmac-secret tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 16:10:36 +02:00
Pol Henarejos
40b5f70761 Fixes with hmac-secret and ProtocolV2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 16:10:12 +02:00
Pol Henarejos
a294840425 Make more easy encryption/decryption with ProtocolV2.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 16:09:59 +02:00
Pol Henarejos
d786a9c6e5 User data is returned when there are more than 1 credential.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 10:44:57 +02:00
Pol Henarejos
b87eb3f278 Credentials are sequentially returned only if allowList is empty. Also, user data is returned only when more than 1 credential is returned (and thus, are discoverable).
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 10:43:24 +02:00
Pol Henarejos
6f226001df More test fixes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 01:24:17 +02:00
Pol Henarejos
6e91694253 Only return user fields on discoverable request.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 01:24:05 +02:00
Pol Henarejos
0c5b308aef Only return numberOfCredentials if allowList is empty.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 01:11:51 +02:00
Pol Henarejos
3fc41a12a7 Only return other user fields if credentials > 1.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-03 00:53:31 +02:00
Pol Henarejos
8ad8c82baf Adding test discoverable.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 20:31:59 +02:00
Pol Henarejos
85818d009c Fix adding icon field to user.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 20:31:43 +02:00
Pol Henarejos
bb069c5651 Get assertion also returns userName and userDisplayName.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 20:24:29 +02:00
Pol Henarejos
e3b036456f One more test
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 00:54:38 +02:00
Pol Henarejos
47ea749454 Adding authentication tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 00:28:13 +02:00
Pol Henarejos
cb4827688b Fix missing parameters.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-02 00:27:15 +02:00
Pol Henarejos
d43b6caf16 Finalizing register tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-30 20:48:17 +02:00
Pol Henarejos
7534d7bb76 Update workflow to include python
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-30 17:30:26 +02:00
Pol Henarejos
cc8d9e0741 Adding first tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-30 12:06:43 +02:00
Pol Henarejos
4e94cbe40e Finalizing credmgmt.
Needs test.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 22:57:27 +02:00
Pol Henarejos
b1b9dad9f5 Only increase rps if it is not an update.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 22:57:02 +02:00
Pol Henarejos
0c51160d23 Adding more subpara.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 20:46:57 +02:00
Pol Henarejos
958a20ce11 Fix public key size.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 20:46:45 +02:00
Pol Henarejos
1e7d711c03 Adding cred_mgmt. Not finished.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 17:48:47 +02:00
Pol Henarejos
cc0a181f75 Renaming authenticator selection.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 12:02:52 +02:00
Pol Henarejos
b8568d834a Adding CRED_PROTO.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 11:54:39 +02:00
Pol Henarejos
174241c0a0 Fix generating random IV on credential creation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-28 11:52:48 +02:00
Pol Henarejos
fa17d5c906 Fix increasing sign counter.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 22:36:19 +02:00
Pol Henarejos
7a4be766bc Comparing appId with bogus apps.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 22:10:31 +02:00
Pol Henarejos
1835afe54a Fix making new credential when up is absent.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 22:10:11 +02:00
Pol Henarejos
ad07052e6a PIN protocol 2 fixes.
Tested with Webauthn.io

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 22:09:46 +02:00
Pol Henarejos
da577b8e8d Debug all CBOR.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 22:09:25 +02:00
Pol Henarejos
0ec563c8de Adding authenticatorSelection 0x0B support.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 12:24:22 +02:00
Pol Henarejos
d4b7bfd6cc Fix generating cert dev.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 12:24:02 +02:00
Pol Henarejos
995870d77e Moving some defines.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-27 12:23:43 +02:00
Pol Henarejos
8338762bcd Adding autobuild.sh
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:57:35 +02:00
Pol Henarejos
11a0b2cb43 Update codeql.yml 2022-09-25 18:56:44 +02:00
Pol Henarejos
957bcae183 Create codeql.yml 2022-09-25 18:33:03 +02:00
Pol Henarejos
109cd4e4ea Upgrading to Version 2.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:15:59 +02:00
Pol Henarejos
8c61cf180d Upgrading Pico HSM SDK pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:15:48 +02:00
Pol Henarejos
4fd9e80e92 Adding credProtect on getinfo.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:13:03 +02:00
Pol Henarejos
01a3c0c60e Adding firmware field on getinfo.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:12:19 +02:00
Pol Henarejos
7a3996da02 Updating readme.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:10:07 +02:00
Pol Henarejos
48f358cb19 Adding default options on make cred.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-25 18:09:46 +02:00
Pol Henarejos
074dd80afe Adding support of credProtect on excludeList when make cred.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:49:12 +02:00
Pol Henarejos
9cbb53716b Fix return errors.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:29:23 +02:00
Pol Henarejos
7a6b8a6af4 Added size check.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:20:39 +02:00
Pol Henarejos
eb318bc381 Return error on bad CLA.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:13:01 +02:00
Pol Henarejos
0e4532a22c Adding check UP if not provided.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:07:16 +02:00
Pol Henarejos
bfc82d5de4 Reset must be confirmed always.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 18:07:05 +02:00
Pol Henarejos
d558941311 Credentials now include a flag to mark whether they are resident or not.
It is used by get assertion to attach userId, regardless allowList is present.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 17:30:18 +02:00
Pol Henarejos
9fa2c5d39c Display more debug info on error.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 17:29:22 +02:00
Pol Henarejos
cd66e65b9c Adding ENABLE_POWER_RESET to enable power cycle for reset command. Enabled by default.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 16:46:16 +02:00
Pol Henarejos
a165d286af Fix returning errors on hmac-secret.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 15:17:46 +02:00
Pol Henarejos
9bf40e69af Fix parsing hmac_secret on assertion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 12:03:18 +02:00
Pol Henarejos
71564e0c79 Fix concurrency when loading FIDO.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 11:41:28 +02:00
Pol Henarejos
be68d5516f Making assertion for U2F.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 11:41:07 +02:00
Pol Henarejos
2c4c618e3b Loading credential if it belongs to U2F.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 11:40:39 +02:00
Pol Henarejos
dd4b52faf3 Fix authenticating MSG from CTAP2 and U2F.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 11:03:13 +02:00
Pol Henarejos
e94f6843e5 Adding cmake option ENABLE_UP_BUTTON to enable/disable user presence confirmation via button. Enabled by default.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-23 09:21:32 +02:00
Pol Henarejos
cbfe66e89b Not necessary scan on every call.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 23:56:48 +02:00
Pol Henarejos
883c5fef35 Removing debugs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 23:54:11 +02:00
Pol Henarejos
40110ad602 Fix generating dev cert.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 22:52:23 +02:00
Pol Henarejos
61b10b7971 Fix get sign counter.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 22:52:10 +02:00
Pol Henarejos
2d496fd8fc Random functions shall be called for each core, otherwise it will hung.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 20:18:05 +02:00
Pol Henarejos
cc373e3e7e Adding send_keepalive().
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:25:52 +02:00
Pol Henarejos
4360ab0375 Device key must not be persistent across resets.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:25:44 +02:00
Pol Henarejos
73c846e985 Credentials are reset on reset, as the device key also changes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:25:26 +02:00
Pol Henarejos
d95bc1aba6 Reset shall call for user presence and can only be called within the 10 seconds from boot.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:25:04 +02:00
Pol Henarejos
2d5fffedb9 Fix resetting pin mismatches.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:24:28 +02:00
Pol Henarejos
f045ec8d03 card_init_core1() shall be called from every thread launched on core1.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 19:24:07 +02:00
Pol Henarejos
e0d8ce7637 Fix encoding credential.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 10:24:38 +02:00
Pol Henarejos
86e3c960a4 Fix when no pin is provided.
6.1.3.7.1 is too ambiguous on uv == false. We also accept that is not provided.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 10:00:06 +02:00
Pol Henarejos
864965c1fe Fix verying when no paut is in use.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 09:59:17 +02:00
Pol Henarejos
3b25eb295c Fix get assertion.
Credentials must be sorted in descending order.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-22 09:30:13 +02:00
Pol Henarejos
226fcc5405 Fixing next get assertion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 23:44:47 +02:00
Pol Henarejos
5625e0dacd Adding preliminary support for get next assertion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 20:02:19 +02:00
Pol Henarejos
cf206bf158 Credentials CANNOT be regenerated, as they depend on random IV.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 19:53:36 +02:00
Pol Henarejos
a44227db52 Fix encoding extensions.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 19:53:11 +02:00
Pol Henarejos
4ab898378a More fixes
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 16:30:49 +02:00
Pol Henarejos
99fc76a385 Finalizing get assertion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 14:29:28 +02:00
Pol Henarejos
f71624f489 More steps.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 00:00:51 +02:00
Pol Henarejos
08c3c3344c Moving up and uv flags to paut.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 00:00:25 +02:00
Pol Henarejos
804970e77a Using extensions and fixing up and uv flags.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-21 00:00:10 +02:00
Pol Henarejos
c938d47bf7 Adding extensions struct.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 23:59:52 +02:00
Pol Henarejos
22a2ea109e Adding unfinished get_assertion.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 20:04:54 +02:00
Pol Henarejos
3a3ec97c90 Fix saving creds.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 19:56:20 +02:00
Pol Henarejos
8a379d9702 Adding Credential manager.
Also adding resident credentials.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 19:50:33 +02:00
Pol Henarejos
72ebb2b596 Adding Credential management.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 17:31:09 +02:00
Pol Henarejos
3dc7af05c1 More fixes. 2022-09-20 15:07:48 +02:00
Pol Henarejos
a3c60f762d Reorganizing core0/core1 split.
Now CBOR and APDU (i.e., intensive processing) areas are executed on core1, while core0 is dedicated for hardware tasks (usb, button, led, etc.).
2022-09-20 14:39:59 +02:00
Pol Henarejos
8feac76a73 If user has introduced PIN, it is verified.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 11:50:45 +02:00
Pol Henarejos
f439b85de7 clientPIN passes the first test.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-20 11:38:59 +02:00
Pol Henarejos
ee8f3a0965 Adding support for clientPIN.
It does not pass the tests yet.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-19 17:20:52 +02:00
Pol Henarejos
199091e2b9 Adding file debug.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-19 17:20:10 +02:00
Pol Henarejos
24f48e33bb Reset nows flushes the memory storage.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-19 17:20:00 +02:00
Pol Henarejos
479aae2ef9 Adding support for CTAP_2_0 and minPINlength.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-19 17:19:45 +02:00
Pol Henarejos
fd7da11931 More fixes. Finally it passes all tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 17:13:26 +02:00
Pol Henarejos
a80247ffa2 Check required parameters in excludeList.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 16:27:56 +02:00
Pol Henarejos
c9c10eca36 Fix excludeList parsing.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 16:23:27 +02:00
Pol Henarejos
11642fe0a3 Fixes with missing parameters.
Up should not be present.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 15:59:20 +02:00
Pol Henarejos
a9cb5ee87c More fixes.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 12:06:54 +02:00
Pol Henarejos
2c6b14822e Fixed many bugs. It works.
This is the first commit that passes *some* tests.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 11:19:54 +02:00
Pol Henarejos
1b70c21588 Added macro for windows compilation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 11:19:20 +02:00
Pol Henarejos
aa15ad471b Fix order of items.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 11:19:08 +02:00
Pol Henarejos
20038b1586 Lots of efforts for make_Credential.
It DOES NOT pass tests yet.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 00:50:19 +02:00
Pol Henarejos
5da2af2c34 Accepting curves for key_derivation as a parameter.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 00:49:59 +02:00
Pol Henarejos
9b49d39ccc Scan files at the beginning.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 00:49:25 +02:00
Pol Henarejos
84a91fcbda Adding known apps.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-16 00:48:58 +02:00
Pol Henarejos
3873303309 Refactor CTAP2 file structure.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-15 14:16:12 +02:00
Pol Henarejos
82b5b1cb96 Moving pointer.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-13 20:37:38 +02:00
Pol Henarejos
1fc8b599ec Fix returned version.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-12 00:47:46 +02:00
Pol Henarejos
4c8242f4c8 Moving CBOR to HID.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-08 20:37:33 +02:00
Pol Henarejos
e9ab270dc3 Using new CTAP header.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-08 17:50:16 +02:00
Pol Henarejos
73f88b6882 Moving from U2F to CTAP1.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-09-08 17:35:56 +02:00
42 changed files with 6235 additions and 210 deletions

72
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,72 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
schedule:
- cron: '23 5 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
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
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- run: |
echo "Run, Build Application using script"
./workflows/autobuild.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -28,12 +28,40 @@ pico_sdk_init()
add_executable(pico_fido)
option(ENABLE_UP_BUTTON "Enable/disable user presence button" ON)
if(ENABLE_UP_BUTTON)
add_definitions(-DENABLE_UP_BUTTON=1)
message("Enabling user presence with button")
else()
add_definitions(-DENABLE_UP_BUTTON=0)
message("Disabling user presence with button")
endif(ENABLE_UP_BUTTON)
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
if(ENABLE_POWER_ON_RESET)
add_definitions(-DENABLE_POWER_ON_RESET=1)
message("Enabling power cycle on reset")
else()
add_definitions(-DENABLE_POWER_ON_RESET=0)
message("Disabling power cycle on reset")
endif(ENABLE_POWER_ON_RESET)
target_sources(pico_fido PUBLIC
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.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
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_reset.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_info.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_make_credential.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/known_apps.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_client_pin.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/credential.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_assertion.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c
)
set(HSM_DRIVER "hid")
include(pico-hsm-sdk/pico_hsm_sdk_import.cmake)
@@ -45,7 +73,13 @@ target_include_directories(pico_fido PUBLIC
target_compile_options(pico_fido PUBLIC
-Wall
-Werror
)
)
string(FIND ${CMAKE_C_COMPILER} ":" COMPILER_COLON)
if (${COMPILER_COLON} GREATER_EQUAL 0)
target_compile_options(pico_fido PUBLIC
-Wno-error=use-after-free
)
endif()
pico_add_extra_outputs(pico_fido)

View File

@@ -4,19 +4,28 @@ This project aims at transforming your Raspberry Pico into a FIDO key integrated
## Features
Pico FIDO has implemented the following features:
- ECDSA authentication.
- App registration and login.
- User presence enforcement through physical button.
- CTAP 2.1 / CTAP 1
- WebAuthn
- U2F
- HMAC-Secret extension
- CredProtect extension
- User presence enforcement through physical button
- User Verification with PIN
- Discoverable credentials
- Credential management
- ECDSA authentication
- App registration and login
- Device selection
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
## Security considerations
Pico FIDO is an open platform so be careful. The contents in the flash memory may be easily dumpled and obtain the private/master keys. There is no way to ensure the master key is stored securely, as the specifications do not support external passphrases or PIN numbers. Therefore, it is not possible to encrypt the content. At least, one key (the master, the supreme key) must be stored in clear text.
Pico FIDO is an open platform so be careful. The contents in the flash memory may be easily dumpled and obtain the private/master keys. Therefore, it is not possible to encrypt the content. At least, one key (the master, the supreme key) must be stored in clear text.
If the Pico is stolen the contents of private and secret keys can be read.
## Download
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page")) and download the UF2 file for your board.
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board.
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the VID/PID patcher as follows: `./pico-fido-patch-vidpid.sh VID:PID input_fido_file.uf2 output_fido_file.uf2`
@@ -62,7 +71,23 @@ While processing, the Pico FIDO is busy and cannot receive additional commands u
Pico FIDO uses the `HID` driver, present in all OS. It should be detected by all OS and browser/applications, like normal USB FIDO keys.
## Tests
Tests can be found at `tests` folder. It is based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys, but adapted to [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from previous 0.8 version and includes latests improvements from CTAP 2.1.
All tests can be run by
```
pytest
```
or by selecting a subset with `-k <test>` flag:
```
pytest -k test_credprotect
```
## Credits
Pico FIDO uses the following libraries or portion of code:
- MbedTLS for cryptographic operations.
- TinyUSB for low level USB procedures.
- TinyCBOR for CBOR parsing and formatting.

53
build_pico_fido.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
VERSION_MAJOR="2"
VERSION_MINOR="2"
rm -rf release/*
cd build_release
for board in adafruit_feather_rp2040 \
adafruit_itsybitsy_rp2040 \
adafruit_kb2040 \
adafruit_macropad_rp2040 \
adafruit_qtpy_rp2040 \
adafruit_trinkey_qt2040 \
arduino_nano_rp2040_connect \
datanoisetv_rp2040_dsp \
eetree_gamekit_rp2040 \
garatronic_pybstick26_rp2040 \
melopero_shake_rp2040 \
pico \
pico_w \
pimoroni_badger2040 \
pimoroni_interstate75 \
pimoroni_keybow2040 \
pimoroni_motor2040 \
pimoroni_pga2040 \
pimoroni_picolipo_4mb \
pimoroni_picolipo_16mb \
pimoroni_picosystem \
pimoroni_plasma2040 \
pimoroni_servo2040 \
pimoroni_tiny2040 \
pimoroni_tiny2040_2mb \
seeed_xiao_rp2040 \
solderparty_rp2040_stamp \
solderparty_rp2040_stamp_carrier \
solderparty_rp2040_stamp_round_carrier \
sparkfun_micromod \
sparkfun_promicro \
sparkfun_thingplus \
vgaboard \
waveshare_rp2040_lcd_0.96 \
waveshare_rp2040_plus_4mb \
waveshare_rp2040_plus_16mb \
waveshare_rp2040_zero \
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
done

View File

@@ -18,7 +18,7 @@
#
VERSION_MAJOR="3" #Version of Pico CCID Core
VERSION_MINOR="2"
VERSION_MINOR="4"
echo "----------------------------"
echo "VID/PID patcher for Pico FIDO"

97
src/fido/cbor.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 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/>.
*/
#include <stdlib.h>
#include "pico/stdlib.h"
#include "ctap2_cbor.h"
#include "ctap.h"
#include "ctap_hid.h"
#include "fido.h"
#include "hsm.h"
#include "usb.h"
#include "apdu.h"
const bool _btrue = true, _bfalse = false;
extern int cbor_process(const uint8_t *data, size_t len);
int cbor_reset();
int cbor_get_info();
int cbor_make_credential(const uint8_t *data, size_t len);
int cbor_client_pin(const uint8_t *data, size_t len);
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
int cbor_get_next_assertion(const uint8_t *data, size_t len);
int cbor_selection();
int cbor_cred_mgmt(const uint8_t *data, size_t len);
const uint8_t aaguid[16] = {0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45}; // First 16 bytes of SHA256("Pico FIDO2")
const uint8_t *cbor_data = NULL;
size_t cbor_len = 0;
int cbor_parse(const uint8_t *data, size_t len) {
if (len == 0)
return CTAP1_ERR_INVALID_LEN;
DEBUG_DATA(data+1,len-1);
driver_prepare_response();
if (data[0] == CTAP_MAKE_CREDENTIAL)
return cbor_make_credential(data + 1, len - 1);
if (data[0] == CTAP_GET_INFO)
return cbor_get_info();
else if (data[0] == CTAP_RESET)
return cbor_reset();
else if (data[0] == CTAP_CLIENT_PIN)
return cbor_client_pin(data + 1, len - 1);
else if (data[0] == CTAP_GET_ASSERTION)
return cbor_get_assertion(data + 1, len - 1, false);
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
return cbor_get_next_assertion(data + 1, len - 1);
else if (data[0] == CTAP_SELECTION)
return cbor_selection();
else if (data[0] == CTAP_CREDENTIAL_MGMT)
return cbor_cred_mgmt(data + 1, len - 1);
return CTAP2_ERR_INVALID_CBOR;
}
void cbor_thread() {
card_init_core1();
while (1) {
uint32_t m;
queue_remove_blocking(&usb_to_card_q, &m);
if (m == EV_EXIT) {
break;
}
apdu.sw = cbor_parse(cbor_data, cbor_len);
if (apdu.sw == 0)
DEBUG_DATA(res_APDU + 1, res_APDU_size);
finished_data_size = res_APDU_size+1;
uint32_t flag = EV_EXEC_FINISHED;
queue_add_blocking(&card_to_usb_q, &flag);
}
}
int cbor_process(const uint8_t *data, size_t len) {
cbor_data = data;
cbor_len = len;
res_APDU = ctap_resp->init.data + 1;
res_APDU_size = 0;
return 1;
}

535
src/fido/cbor_client_pin.c Normal file
View File

@@ -0,0 +1,535 @@
/*
* 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/>.
*/
#include "common.h"
#include "mbedtls/ecp.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/sha256.h"
#include "mbedtls/hkdf.h"
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#include "bsp/board.h"
#include "fido.h"
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
#include "hsm.h"
#include "apdu.h"
uint8_t permissions_rp_id = 0, permission_set = 0;
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
uint32_t max_usage_time_period = 600*1000;
bool needs_power_cycle = false;
mbedtls_ecdh_context hkey;
bool hkey_init = false;
int beginUsingPinUvAuthToken(bool userIsPresent) {
paut.user_present = userIsPresent;
paut.user_verified = true;
initial_usage_time_limit = board_millis();
usage_timer = board_millis();
paut.in_use = true;
return 0;
}
void clearUserPresentFlag() {
if (paut.in_use == true)
paut.user_present = false;
}
void clearUserVerifiedFlag() {
if (paut.in_use == true)
paut.user_verified = false;
}
void clearPinUvAuthTokenPermissionsExceptLbw() {
if (paut.in_use == true)
paut.permissions = FIDO2_PERMISSION_LBW;
}
void stopUsingPinUvAuthToken() {
permissions_rp_id = 0;
paut.permissions = 0;
usage_timer = 0;
paut.in_use = false;
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
initial_usage_time_limit = 0;
paut.user_present = paut.user_verified = false;
user_present_time_limit = 0;
}
bool getUserPresentFlagValue() {
if (paut.in_use != true)
paut.user_present = false;
return paut.user_present;
}
bool getUserVerifiedFlagValue() {
if (paut.in_use != true)
paut.user_verified = false;
return paut.user_verified;
}
int regenerate() {
if (hkey_init == true)
mbedtls_ecdh_free(&hkey);
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);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0)
return ret;
return 0;
}
int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
int ret = 0;
uint8_t buf[32];
ret = mbedtls_mpi_write_binary(z, buf, sizeof(buf));
if (ret != 0)
return ret;
if (protocol == 1) {
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);
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 -1;
}
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);
ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z);
return ret;
}
int resetPinUvAuthToken() {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
paut.permissions = 0;
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
low_flash_available();
return 0;
}
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out) {
if (protocol == 1) {
memcpy(out, in, in_len);
return aes_encrypt(key, NULL, 32*8, HSM_AES_MODE_CBC, out, in_len);
}
else if (protocol == 2) {
random_gen(NULL, out, IV_SIZE);
memcpy(out + IV_SIZE, in, in_len);
return aes_encrypt(key+32, out, 32*8, HSM_AES_MODE_CBC, out+IV_SIZE, in_len);
}
return -1;
}
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out) {
if (protocol == 1) {
memcpy(out, in, in_len);
return aes_decrypt(key, NULL, 32*8, HSM_AES_MODE_CBC, out, in_len);
}
else if (protocol == 2) {
memcpy(out, in+IV_SIZE, in_len);
return aes_decrypt(key+32, in, 32*8, HSM_AES_MODE_CBC, out, in_len-IV_SIZE);
}
return -1;
}
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);
if (ret != 0)
return ret;
if (protocol == 1) {
memcpy(sign, hmac, 16);
}
else if (protocol == 2) {
memcpy(sign, hmac, 32);
}
else
return -1;
return 0;
}
int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
uint8_t hmac[32];
//if (paut.in_use == false)
// return -2;
int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
if (ret != 0)
return ret;
if (protocol == 1)
return memcmp(sign, hmac, 16);
else if (protocol == 2)
return memcmp(sign, hmac, 32);
return -1;
}
int initialize() {
regenerate();
return resetPinUvAuthToken();
}
int getPublicKey() {
return 0;
}
int pinUvAuthTokenUsageTimerObserver() {
if (usage_timer == 0)
return -1;
if (usage_timer+max_usage_time_period > board_millis()) {
if (user_present_time_limit == 0 || user_present_time_limit+TRANSPORT_TIME_LIMIT < board_millis())
clearUserPresentFlag();
if (paut.in_use == true) {
if (initial_usage_time_limit == 0 || initial_usage_time_limit+TRANSPORT_TIME_LIMIT < board_millis()) {
stopUsingPinUvAuthToken();
return 1;
}
}
// TO DO: implement a rolling timer
}
return 0;
}
uint8_t new_pin_mismatches = 0;
int cbor_client_pin(const uint8_t *data, size_t len) {
size_t resp_size = 0;
uint64_t subcommand = 0x0, pinUvAuthProtocol = 0, permissions = 0;
int64_t kty = 0, alg = 0, crv = 0;
CborParser parser;
CborEncoder encoder, mapEncoder;
CborValue map;
CborError error = CborNoError;
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;
if (hkey_init == false)
initialize();
CBOR_PARSE_MAP_START(map, 1)
{
uint64_t val_u = 0;
CBOR_FIELD_GET_UINT(val_u, 1);
if (val_c <= 2 && val_c != val_u)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (val_u < val_c)
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
val_c = val_u + 1;
if (val_u == 0x01) {
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
}
else if (val_u == 0x02) {
CBOR_FIELD_GET_UINT(subcommand, 1);
}
else if (val_u == 0x03) {
int64_t key = 0;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_INT(key, 2);
if (key == 1) {
CBOR_FIELD_GET_INT(kty, 2);
}
else if (key == 3) {
CBOR_FIELD_GET_INT(alg, 2);
}
else if (key == -1) {
CBOR_FIELD_GET_INT(crv, 2);
}
else if (key == -2) {
CBOR_FIELD_GET_BYTES(kax, 2);
}
else if (key == -3) {
CBOR_FIELD_GET_BYTES(kay, 2);
}
else
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x04) {
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
}
else if (val_u == 0x05) {
CBOR_FIELD_GET_BYTES(newPinEnc, 1);
}
else if (val_u == 0x06) {
CBOR_FIELD_GET_BYTES(pinHashEnc, 1);
}
else if (val_u == 0x09) {
CBOR_FIELD_GET_UINT(permissions, 1);
}
else if (val_u == 0x0A) {
CBOR_FIELD_GET_TEXT(rpId, 1);
}
}
CBOR_PARSE_MAP_END(map, 1);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
if (subcommand == 0x0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
else if (subcommand == 0x1) { //getPINRetries
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, needs_power_cycle ? 2 : 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t)*file_get_data(ef_pin)));
if (needs_power_cycle) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
}
else if (subcommand == 0x2) { //getKeyAgreement
if (pinUvAuthProtocol == 1 || pinUvAuthProtocol == 2) {
CborEncoder mapEncoder2;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
uint8_t pkey[32];
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
else if (pinUvAuthProtocol == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
else
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
else if (subcommand == 0x3) { //setPIN
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
if ((pinUvAuthProtocol == 1 && newPinEnc.len != 64) || (pinUvAuthProtocol == 2 && newPinEnc.len != 64+IV_SIZE))
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, pinUvAuthParam.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
uint8_t paddedNewPin[64];
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (ret != 0)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (paddedNewPin[63] != 0)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
uint8_t pin_len = 0;
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
pin_len++;
if (pin_len < 4)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
uint8_t hsh[33];
hsh[0] = MAX_PIN_RETRIES;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
flash_write_data_to_file(ef_pin, hsh, 1+16);
low_flash_available();
goto err; //No return
}
else if (subcommand == 0x4) { //changePIN
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0 || pinHashEnc.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (!file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
if (*file_get_data(ef_pin) == 0)
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
if ((pinUvAuthProtocol == 1 && (newPinEnc.len != 64 || pinHashEnc.len != 16)) || (pinUvAuthProtocol == 2 && (newPinEnc.len != 64+IV_SIZE || pinHashEnc.len != 16+IV_SIZE)))
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t tmp[80 + 32];
memcpy(tmp, newPinEnc.data, newPinEnc.len);
memcpy(tmp + newPinEnc.len, pinHashEnc.data, pinHashEnc.len);
if (verify(pinUvAuthProtocol, sharedSecret, tmp, newPinEnc.len+pinHashEnc.len, pinUvAuthParam.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
uint8_t retries = *file_get_data(ef_pin) - 1;
flash_write_data_to_file(ef_pin, &retries, 1);
uint8_t paddedNewPin[64];
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
low_flash_available();
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
}
if (++new_pin_mismatches >= 3) {
needs_power_cycle = true;
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
}
else
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
retries = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, &retries, 1);
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (ret != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (paddedNewPin[63] != 0)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
uint8_t pin_len = 0;
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
pin_len++;
if (pin_len < 4)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
uint8_t hsh[33];
hsh[0] = MAX_PIN_RETRIES;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
flash_write_data_to_file(ef_pin, hsh, 1+16);
low_flash_available();
resetPinUvAuthToken();
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getUVRgetPinUvAuthTokenUsingPinWithPermissionsetries
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || alg == 0 || pinHashEnc.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if ((subcommand == 0x9 && permissions == 0) || (subcommand == 0x5 && (permissions != 0 || rpId.present == true)))
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (!file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
if (*file_get_data(ef_pin) == 0)
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t sharedSecret[64];
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t retries = *file_get_data(ef_pin) - 1;
flash_write_data_to_file(ef_pin, &retries, 1);
uint8_t paddedNewPin[64], poff = (pinUvAuthProtocol-1)*IV_SIZE;
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
low_flash_available();
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
}
if (++new_pin_mismatches >= 3) {
needs_power_cycle = true;
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
}
else
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
retries = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, &retries, 1);
low_flash_available();
beginUsingPinUvAuthToken(false);
paut.permissions = permissions;
if (rpId.present == true)
memcpy(paut.rp_id_hash, rpId.data, 32);
else
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
uint8_t pinUvAuthToken_enc[32+IV_SIZE];
encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 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));
}
else
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(newPinEnc);
CBOR_FREE_BYTE_STRING(pinHashEnc);
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(rpId);
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}

369
src/fido/cbor_cred_mgmt.c Normal file
View File

@@ -0,0 +1,369 @@
/*
* 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/>.
*/
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "bsp/board.h"
#include "cbor_make_credential.h"
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "hsm.h"
uint8_t rp_counter = 1;
uint8_t rp_total = 0;
uint8_t cred_counter = 1;
uint8_t cred_total = 0;
CborByteString rpIdHashx = {0};
int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CborParser parser;
CborValue map;
CborError error = CborNoError;
uint64_t subcommand = 0, pinUvAuthProtocol = 0;
CborByteString pinUvAuthParam = {0}, rpIdHash = {0};
PublicKeyCredentialDescriptor credentialId = {0};
PublicKeyCredentialUserEntity user = {0};
size_t resp_size = 0;
CborEncoder encoder, mapEncoder, mapEncoder2;
uint8_t *raw_subpara = NULL;
size_t raw_subpara_len = 0;
bool asserted = false;
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
CBOR_PARSE_MAP_START(map, 1) {
uint64_t val_u = 0;
CBOR_FIELD_GET_UINT(val_u, 1);
if (val_c <= 1 && val_c != val_u)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (val_u < val_c)
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
val_c = val_u + 1;
if (val_u == 0x01) {
CBOR_FIELD_GET_UINT(subcommand, 1);
}
else if (val_u == 0x02) {
uint64_t subpara = 0;
raw_subpara = (uint8_t *)cbor_value_get_next_byte(&_f1);
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_BYTES(rpIdHash, 2);
}
else if (subpara == 0x02) {
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", credentialId.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", credentialId.type);
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) {
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
credentialId.transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
}
else if (subpara == 0x03) {
CBOR_PARSE_MAP_START(_f1, 3) {
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", user.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "name", user.parent.name);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "displayName", user.displayName);
}
CBOR_PARSE_MAP_END(_f1, 3);
}
}
CBOR_PARSE_MAP_END(_f1, 2);
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
}
else if (val_u == 0x03) {
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
}
else if (val_u == 0x04) { // pubKeyCredParams
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
}
}
CBOR_PARSE_MAP_END(map, 1);
if (subcommand != 0x03 && subcommand != 0x05) {
if (pinUvAuthParam.present == false)
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
if(subcommand == 0x01) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x01", 1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
if (file_has_data(search_dynamic_file(EF_CRED + i)))
existing++;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, existing));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_RESIDENT_CREDENTIALS-existing));
}
else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL;
if (subcommand == 0x02) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x02", 1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
rp_counter = 1;
rp_total = 0;
}
else {
if (rp_counter > rp_total)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
uint8_t skip = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *tef = search_dynamic_file(EF_RP + i);
if (file_has_data(tef) && *file_get_data(tef) > 0) {
if (++skip == rp_counter) {
if (rp_ef == NULL)
rp_ef = tef;
if (subcommand == 0x03)
break;
}
if (subcommand == 0x02)
rp_total++;
}
}
if (rp_ef == NULL) // should not happen
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
rp_counter++;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x02 ? 3 : 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 1));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, file_get_data(rp_ef)+33, file_get_size(rp_ef)-33));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(rp_ef)+1, 32));
if (subcommand == 0x02) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, rp_total));
}
}
else if (subcommand == 0x04 || subcommand == 0x05) {
if (subcommand == 0x04 && rpIdHash.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (subcommand == 0x04) {
*(raw_subpara-1) = 0x04;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
cred_counter = 1;
cred_total = 0;
}
else {
if (cred_counter > cred_total) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
rpIdHash = rpIdHashx;
}
file_t *cred_ef = NULL;
uint8_t skip = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *tef = search_dynamic_file(EF_CRED + i);
if (file_has_data(tef) && memcmp(file_get_data(tef), rpIdHash.data, 32) == 0) {
if (++skip == cred_counter) {
if (cred_ef == NULL)
cred_ef = tef;
if (subcommand == 0x05)
break;
}
if (subcommand == 0x04)
cred_total++;
}
}
if (!file_has_data(cred_ef))
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
Credential cred = {0};
if (credential_load(file_get_data(cred_ef)+32, file_get_size(cred_ef)-32, rpIdHash.data, &cred) != 0)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
if (fido_load_key(cred.curve, cred.id.data, &key) != 0) {
credential_free(&cred);
mbedtls_ecdsa_free(&key);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
cred_counter++;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x04 ? 5 : 4));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
uint8_t l = 0;
if (cred.userId.present == true)
l++;
if (cred.userName.present == true)
l++;
if (cred.userDisplayName.present == true)
l++;
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, l));
if (cred.userId.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.userId.data, cred.userId.len));
}
if (cred.userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userName.data, cred.userName.len));
}
if (cred.userDisplayName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "displayName"));
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userDisplayName.data, cred.userDisplayName.len));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, cred.alg));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, cred.curve));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
uint8_t pkey[66];
mbedtls_mpi_write_binary(&key.Q.X, pkey, mbedtls_mpi_size(&key.Q.X));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, mbedtls_mpi_size(&key.Q.X)));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
mbedtls_mpi_write_binary(&key.Q.Y, pkey, mbedtls_mpi_size(&key.Q.Y));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, mbedtls_mpi_size(&key.Q.Y)));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
if (subcommand == 0x04) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x09));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred_total));
}
if (cred_counter <= cred_total) {
asserted = true;
rpIdHashx = rpIdHash;
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect));
credential_free(&cred);
mbedtls_ecdsa_free(&key);
}
else if (subcommand == 0x06) {
if (credentialId.id.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
*(raw_subpara - 1) = 0x06;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {
uint8_t *rp_id_hash = file_get_data(ef);
if (delete_file(ef) != 0)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
for (int j = 0; j < MAX_RESIDENT_CREDENTIALS; j++) {
file_t *rp_ef = search_dynamic_file(EF_RP + j);
if (file_has_data(rp_ef) && memcmp(file_get_data(rp_ef)+1, rp_id_hash, 32) == 0) {
uint8_t *rp_data = (uint8_t *)calloc(1, file_get_size(rp_ef));
memcpy(rp_data, file_get_data(rp_ef), file_get_size(rp_ef));
rp_data[0] -= 1;
if (rp_data[0] == 0)
delete_file(rp_ef);
else
flash_write_data_to_file(rp_ef, rp_data, file_get_size(rp_ef));
free(rp_data);
break;
}
}
low_flash_available();
goto err; //no error
}
}
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
}
else if (subcommand == 0x07) {
if (credentialId.id.present == false || user.id.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
*(raw_subpara - 1) = 0x07;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {
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)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t newcred[MAX_CRED_ID_LENGTH];
size_t newcred_len = 0;
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name, &user.displayName, &cred.opts, &cred.extensions, cred.use_sign_count, cred.alg, cred.curve, newcred, &newcred_len) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
credential_free(&cred);
if (credential_store(newcred, newcred_len, rp_id_hash) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
low_flash_available();
goto err; //no error
}
}
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
if (asserted == false) {
CBOR_FREE_BYTE_STRING(rpIdHash);
}
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
CBOR_FREE_BYTE_STRING(credentialId.type);
for (int n = 0; n < credentialId.transports_len; n++) {
CBOR_FREE_BYTE_STRING(credentialId.transports[n]);
}
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}

View File

@@ -0,0 +1,539 @@
/*
* 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/>.
*/
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#include "bsp/board.h"
#include "fido.h"
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
#include "hsm.h"
#include "apdu.h"
#include "cbor_make_credential.h"
#include "credential.h"
#include <math.h>
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
bool residentx = false;
Credential credsx[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
uint8_t credentialCounter = 1;
uint8_t numberOfCredentialsx = 0;
uint8_t flagsx = 0;
uint32_t timerx = 0;
uint8_t *datax = NULL;
size_t lenx = 0;
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
CborError error = CborNoError;
if (credentialCounter >= numberOfCredentialsx)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
if (timerx+30*1000 < board_millis())
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
CBOR_CHECK(cbor_get_assertion(datax, lenx, true));
timerx = board_millis();
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;
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
return 0;
}
int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
size_t resp_size = 0;
uint64_t pinUvAuthProtocol = 0, hmacSecretPinUvAuthProtocol = 1;
CredOptions options = {0};
CredExtensions extensions = {0};
CborParser parser;
CborEncoder encoder, mapEncoder, mapEncoder2;
CborValue map;
CborError error = CborNoError;
CborByteString pinUvAuthParam = {0}, clientDataHash = {0};
CborCharString rpId = {0};
PublicKeyCredentialDescriptor allowList[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
size_t allowList_len = 0, creds_len = 0;
uint8_t *aut_data = NULL;
bool asserted = false;
int64_t kty = 2, alg = 0, crv = 0;
CborByteString kax = {0}, kay = {0}, salt_enc = {0}, salt_auth = {0};
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
CBOR_PARSE_MAP_START(map, 1) {
uint64_t val_u = 0;
CBOR_FIELD_GET_UINT(val_u, 1);
if (val_c <= 2 && val_c != val_u)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (val_u < val_c)
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
val_c = val_u + 1;
if (val_u == 0x01) {
CBOR_FIELD_GET_TEXT(rpId, 1);
}
else if (val_u == 0x02) {
CBOR_FIELD_GET_BYTES(clientDataHash, 1);
}
else if (val_u == 0x03) { // excludeList
CBOR_PARSE_ARRAY_START(_f1, 2) {
PublicKeyCredentialDescriptor *pc = &allowList[allowList_len];
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", pc->id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pc->type);
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) {
CBOR_FIELD_GET_TEXT(pc->transports[pc->transports_len], 4);
pc->transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
allowList_len++;
}
CBOR_PARSE_ARRAY_END(_f1, 2);
}
else if (val_u == 0x04) { // extensions
extensions.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
if (strcmp(_fd2, "hmac-secret") == 0) {
extensions.hmac_secret = ptrue;
uint64_t ukey = 0;
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_FIELD_GET_UINT(ukey, 3);
if (ukey == 0x01) {
int64_t kkey = 0;
CBOR_PARSE_MAP_START(_f3, 4) {
CBOR_FIELD_GET_INT(kkey, 4);
if (kkey == 1) {
CBOR_FIELD_GET_INT(kty, 4);
}
else if (kkey == 3) {
CBOR_FIELD_GET_INT(alg, 4);
}
else if (kkey == -1) {
CBOR_FIELD_GET_INT(crv, 4);
}
else if (kkey == -2) {
CBOR_FIELD_GET_BYTES(kax, 4);
}
else if (kkey == -3) {
CBOR_FIELD_GET_BYTES(kay, 4);
}
else
CBOR_ADVANCE(4);
}
CBOR_PARSE_MAP_END(_f3, 4);
}
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_UINT(2, "credProtect", extensions.credProtect);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x05) { // options
options.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", options.rk);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "up", options.up);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "uv", options.uv);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x06) { // pinUvAuthParam
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
}
else if (val_u == 0x07) { // pinUvAuthProtocol
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
}
}
CBOR_PARSE_MAP_END(map, 1);
if (rpId.present == false || clientDataHash.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
uint8_t flags = 0;
uint8_t rp_id_hash[32];
mbedtls_sha256((uint8_t *)rpId.data, rpId.len, rp_id_hash, 0);
bool resident = false;
uint8_t numberOfCredentials = 0;
Credential *selcred = NULL;
if (next == false) {
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false)
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
if (!file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
else
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
else {
if (pinUvAuthProtocol == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
}
if (options.present) {
if (options.uv == ptrue) { //4.3
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
//if (options.up != NULL) { //4.5
// CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
//}
if (options.rk != NULL) {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
//else if (options.up == NULL) //5.7
//rup = ptrue;
}
if (pinUvAuthParam.present == true) { //6.1
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
if (ret != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (getUserVerifiedFlagValue() == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
flags |= FIDO2_AUT_FLAG_UV;
// Check pinUvAuthToken permissions. See 6.2.2.4
}
if (extensions.present == true && extensions.hmac_secret == ptrue) {
if (kax.present == false || kay.present == false || crv == 0 || alg == 0 || salt_enc.present == false || salt_auth.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (salt_enc.len != 32+(hmacSecretPinUvAuthProtocol-1)*IV_SIZE && salt_enc.len != 64+(hmacSecretPinUvAuthProtocol-1)*IV_SIZE)
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
}
if (allowList_len > 0) {
for (int e = 0; e < allowList_len; e++) {
if (allowList[e].type.present == false || allowList[e].id.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (strcmp(allowList[e].type.data, "public-key") != 0)
continue;
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
CBOR_FREE_BYTE_STRING(allowList[e].id);
credential_free(&creds[creds_len]);
}
else
creds_len++;
}
}
else {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
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]);
if (ret != 0)
credential_free(&creds[creds_len]);
else
creds_len++;
}
resident = true;
}
for (int i = 0; i < creds_len; i++) {
if (creds[i].present == true) {
if (creds[i].extensions.present == true) {
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV))
credential_free(&creds[i]);
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && resident == true && !(flags & FIDO2_AUT_FLAG_UV))
credential_free(&creds[i]);
else
creds[numberOfCredentials++] = creds[i];
}
else
creds[numberOfCredentials++] = creds[i];
}
}
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 (options.up == ptrue || options.present == false || options.up == NULL) { //9.1
if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) {
if (check_user_presence() == false)
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
}
else {
if (!(flags & FIDO2_AUT_FLAG_UP)) {
if (check_user_presence() == false)
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
}
flags |= FIDO2_AUT_FLAG_UP;
clearUserPresentFlag();
clearUserVerifiedFlag();
clearPinUvAuthTokenPermissionsExceptLbw();
}
if (!(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) {
selcred = &creds[0];
}
else {
selcred = &creds[0];
if (numberOfCredentials > 1) {
asserted = true;
residentx = resident;
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++)
credsx[i] = creds[i];
numberOfCredentialsx = numberOfCredentials;
datax = (uint8_t *)calloc(1, len);
memcpy(datax, data, len);
lenx = len;
flagsx = flags;
timerx = board_millis();
credentialCounter = 1;
}
}
}
else {
resident = residentx;
numberOfCredentials = numberOfCredentialsx;
flags = flagsx;
selcred = &credsx[credentialCounter];
}
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = fido_load_key(selcred->curve, selcred->id.data, &ekey);
if (ret != 0) {
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
}
size_t ext_len = 0;
uint8_t ext [512];
if (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)
l++;
if (extensions.credProtect != 0)
l++;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
}
if (extensions.hmac_secret != NULL) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
uint8_t sharedSecret[64];
mbedtls_ecp_point Qp;
mbedtls_ecp_point_init(&Qp);
mbedtls_mpi_lset(&Qp.Z, 1);
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
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(hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
mbedtls_ecp_point_free(&Qp);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify(hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, salt_enc.len, salt_auth.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
}
uint8_t salt_dec[64], poff = (hmacSecretPinUvAuthProtocol-1)*IV_SIZE;
ret = decrypt(hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, salt_enc.len, salt_dec);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t cred_random[64], *crd = NULL;
ret = credential_derive_hmac_key(selcred->id.data, selcred->id.len, cred_random);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (flags & FIDO2_AUT_FLAG_UV)
crd = cred_random + 32;
else
crd = cred_random;
uint8_t out1[64], hmac_res[80];
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
if (salt_enc.len == 64+poff)
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec+32, 32, out1+32);
encrypt(hmacSecretPinUvAuthProtocol, sharedSecret, out1, salt_enc.len-poff, hmac_res);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
uint32_t ctr = get_sign_counter();
size_t aut_data_len = 32 + 1 + 4 + ext_len;
aut_data = (uint8_t *)calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
*pa++ = ctr >> 24;
*pa++ = ctr >> 16;
*pa++ = ctr >> 8;
*pa++ = ctr & 0xff;
memcpy(pa, ext, ext_len); pa += ext_len;
if (pa-aut_data != aut_data_len)
CBOR_ERROR(CTAP1_ERR_OTHER);
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
size_t olen = 0;
ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL);
mbedtls_ecdsa_free(&ekey);
uint8_t lfields = 3;
if (selcred->opts.present == true && selcred->opts.rk == ptrue)
lfields++;
if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV))
lfields++;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
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) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
uint8_t lu = 1;
if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true)
lu++;
if (selcred->userDisplayName.present == true)
lu++;
}
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));
if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, selcred->userName.data));
}
if (selcred->userDisplayName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "displayName"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, selcred->userDisplayName.data));
}
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
ctr++;
flash_write_data_to_file(ef_counter, (uint8_t *)&ctr, sizeof(ctr));
low_flash_available();
err:
CBOR_FREE_BYTE_STRING(clientDataHash);
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(rpId);
if (asserted == false) {
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++)
credential_free(&creds[i]);
}
for (int m = 0; m < allowList_len; m++) {
CBOR_FREE_BYTE_STRING(allowList[m].type);
CBOR_FREE_BYTE_STRING(allowList[m].id);
for (int n = 0; n < allowList[m].transports_len; n++) {
CBOR_FREE_BYTE_STRING(allowList[m].transports[n]);
}
}
if (aut_data)
free(aut_data);
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}

88
src/fido/cbor_get_info.c Normal file
View File

@@ -0,0 +1,88 @@
/*
* 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/>.
*/
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "files.h"
#include "apdu.h"
#include "version.h"
int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder;
CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 9));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
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_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 5));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "authnrCfg"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "clientPin"));
if (file_has_data(ef_pin))
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
else
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "pinUvAuthToken"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, 1)); // PIN protocols
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, 2)); // PIN protocols
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDENTIAL_COUNT_IN_LIST)); // MAX_CRED_COUNT_IN_LIST
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0E));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err:
if (error != CborNoError)
return -CTAP2_ERR_INVALID_CBOR;
res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
return 0;
}

View File

@@ -0,0 +1,405 @@
/*
* 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/>.
*/
#include "common.h"
#include "ctap2_cbor.h"
#include "cbor_make_credential.h"
#include "fido.h"
#include "ctap.h"
#include "files.h"
#include "random.h"
#include "hsm.h"
#include <math.h>
#include "apdu.h"
#include "credential.h"
int cbor_make_credential(const uint8_t *data, size_t len) {
CborParser parser;
CborValue map;
CborError error = CborNoError;
CborByteString clientDataHash = {0}, pinUvAuthParam = {0};
PublicKeyCredentialRpEntity rp = {0};
PublicKeyCredentialUserEntity user = {0};
PublicKeyCredentialParameters pubKeyCredParams[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
size_t pubKeyCredParams_len = 0;
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
size_t excludeList_len = 0;
CredOptions options = {0};
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
uint8_t *aut_data = NULL;
size_t resp_size = 0;
CredExtensions extensions = {0};
//options.present = true;
//options.up = ptrue;
//options.uv = pfalse;
//options.rk = pfalse;
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
CBOR_PARSE_MAP_START(map, 1) {
uint64_t val_u = 0;
CBOR_FIELD_GET_UINT(val_u, 1);
if (val_c <= 4 && val_c != val_u)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (val_u < val_c)
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
val_c = val_u + 1;
if (val_u == 0x01) { // clientDataHash
CBOR_FIELD_GET_BYTES(clientDataHash, 1);
}
else if (val_u == 0x02) { // rp
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "id", rp.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", rp.parent.name);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x03) { // user
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "id", user.id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", user.parent.name);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "displayName", user.displayName);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x04) { // pubKeyCredParams
CBOR_PARSE_ARRAY_START(_f1, 2) {
PublicKeyCredentialParameters *pk = &pubKeyCredParams[pubKeyCredParams_len];
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pk->type);
CBOR_FIELD_KEY_TEXT_VAL_INT(3, "alg", pk->alg);
}
CBOR_PARSE_MAP_END(_f2, 3);
pubKeyCredParams_len++;
}
CBOR_PARSE_ARRAY_END(_f1, 2);
}
else if (val_u == 0x05) { // excludeList
CBOR_PARSE_ARRAY_START(_f1, 2) {
PublicKeyCredentialDescriptor *pc = &excludeList[excludeList_len];
CBOR_PARSE_MAP_START(_f2, 3) {
CBOR_FIELD_GET_KEY_TEXT(3);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", pc->id);
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pc->type);
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) {
CBOR_FIELD_GET_TEXT(pc->transports[pc->transports_len], 4);
pc->transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
excludeList_len++;
}
CBOR_PARSE_ARRAY_END(_f1, 2);
}
else if (val_u == 0x06) { // extensions
extensions.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x07) { // options
options.present = true;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", options.rk);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "up", options.up);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "uv", options.uv);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x08) { // pinUvAuthParam
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
}
else if (val_u == 0x09) { // pinUvAuthProtocol
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
}
else if (val_u == 0x0A) { // enterpriseAttestation
CBOR_FIELD_GET_UINT(enterpriseAttestation, 1);
}
}
CBOR_PARSE_MAP_END(map, 1);
uint8_t flags = FIDO2_AUT_FLAG_AT;
uint8_t rp_id_hash[32];
mbedtls_sha256((uint8_t *)rp.id.data, rp.id.len, rp_id_hash, 0);
int curve = -1, alg = 0;
if (pubKeyCredParams_len == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
for (int i = 0; i < pubKeyCredParams_len; i++) {
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0)
continue;
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256)
curve = FIDO2_CURVE_P256;
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384)
curve = FIDO2_CURVE_P384;
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512)
curve = FIDO2_CURVE_P521;
else if (pubKeyCredParams[i].alg == 0) // no present
curve = -1;
else
curve = 0;
if (curve > 0) {
alg = pubKeyCredParams[i].alg;
break;
}
}
if (curve == 0)
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_ALGORITHM);
else if (curve == -1)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthParam.present == true) {
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
if (check_user_presence() == false)
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
if (!file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
else
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
else {
if (pinUvAuthProtocol == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
}
if (options.present) {
if (options.uv == ptrue) { //5.3
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
if (options.up != NULL) { //5.6
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
//else if (options.up == NULL) //5.7
//rup = ptrue;
}
if (pinUvAuthParam.present == false && options.uv != ptrue && file_has_data(ef_pin)) { //8.1
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (enterpriseAttestation > 0) {
if (enterpriseAttestation != 1 && enterpriseAttestation != 2) { //9.2.1
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
//Unfinished. See 6.1.2.9
}
if (pinUvAuthParam.present == true) { //11.1
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
if (ret != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (getUserVerifiedFlagValue() == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
flags |= FIDO2_AUT_FLAG_UV;
// Check pinUvAuthToken permissions. See 6.1.2.11
}
for (int e = 0; e < excludeList_len; e++) { //12.1
if (excludeList[e].type.present == false || excludeList[e].id.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (strcmp(excludeList[e].type.data, "public-key") != 0)
continue;
Credential ecred;
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || flags & FIDO2_AUT_FLAG_UV))
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
if (options.up == ptrue || options.up == NULL) { //14.1
if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) {
if (check_user_presence() == false)
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
}
}
flags |= FIDO2_AUT_FLAG_UP;
clearUserPresentFlag();
clearUserVerifiedFlag();
clearPinUvAuthTokenPermissionsExceptLbw();
}
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH];
size_t cred_id_len = 0;
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));
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = fido_load_key(curve, cred_id, &ekey);
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
if (getUserVerifiedFlagValue())
flags |= FIDO2_AUT_FLAG_UV;
size_t ext_len = 0;
uint8_t ext [512];
CborEncoder encoder, mapEncoder, mapEncoder2;
if (extensions.present == true) {
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
int l = 0;
if (extensions.hmac_secret != NULL)
l++;
if (extensions.credProtect != 0)
l++;
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
}
if (extensions.hmac_secret != NULL) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
uint8_t pkey[66];
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
if (cinfo == NULL)
CBOR_ERROR(CTAP1_ERR_OTHER);
size_t olen = 0;
uint32_t ctr = get_sign_counter();
uint8_t cbor_buf[1024];
cbor_encoder_init(&encoder, cbor_buf, sizeof(cbor_buf), 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 3));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, -alg));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, curve));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 2));
mbedtls_mpi_write_binary(&ekey.Q.X, pkey, mbedtls_mpi_size(&ekey.Q.X));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, mbedtls_mpi_size(&ekey.Q.X)));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 3));
mbedtls_mpi_write_binary(&ekey.Q.Y, pkey, mbedtls_mpi_size(&ekey.Q.Y));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, mbedtls_mpi_size(&ekey.Q.Y)));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
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;
aut_data = (uint8_t *)calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
*pa++ = ctr >> 24;
*pa++ = ctr >> 16;
*pa++ = ctr >> 8;
*pa++ = ctr & 0xff;
memcpy(pa, aaguid, 16); pa += 16;
*pa++ = cred_id_len >> 8;
*pa++ = cred_id_len & 0xff;
memcpy(pa, cred_id, cred_id_len); pa += cred_id_len;
memcpy(pa, cbor_buf, rs); pa += rs;
memcpy(pa, ext, ext_len); pa += ext_len;
if (pa-aut_data != aut_data_len)
CBOR_ERROR(CTAP1_ERR_OTHER);
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
bool self_attestation = true;
if (ka && ka->use_self_attestation == pfalse) {
mbedtls_ecdsa_free(&ekey);
mbedtls_ecdsa_init(&ekey);
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
self_attestation = false;
}
ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL);
mbedtls_ecdsa_free(&ekey);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 3));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false ? 3 : 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation ? -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) {
CborEncoder arrEncoder;
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "x5c"));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder2, &arrEncoder, 1));
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_certdev), file_get_size(ef_certdev)));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder2, &arrEncoder));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
if (options.rk == ptrue) {
if (credential_store(cred_id, cred_id_len, rp_id_hash) != 0)
CBOR_ERROR(CTAP2_ERR_KEY_STORE_FULL);
}
err:
CBOR_FREE_BYTE_STRING(clientDataHash);
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(rp.id);
CBOR_FREE_BYTE_STRING(rp.parent.name);
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
for (int n = 0; n < pubKeyCredParams_len; n++) {
CBOR_FREE_BYTE_STRING(pubKeyCredParams[n].type);
}
for (int m = 0; m < excludeList_len; m++) {
CBOR_FREE_BYTE_STRING(excludeList[m].type);
CBOR_FREE_BYTE_STRING(excludeList[m].id);
for (int n = 0; n < excludeList[m].transports_len; n++) {
CBOR_FREE_BYTE_STRING(excludeList[m].transports[n]);
}
}
if (aut_data)
free(aut_data);
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}

View File

@@ -0,0 +1,60 @@
/*
* 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/>.
*/
#ifndef _CBOR_MAKE_CREDENTIAL_H_
#define _CBOR_MAKE_CREDENTIAL_H_
#include "common.h"
#include "mbedtls/chachapoly.h"
#include <stdlib.h>
#include "pico/stdlib.h"
#include "ctap2_cbor.h"
#include "random.h"
#include "mbedtls/sha256.h"
typedef struct PublicKeyCredentialEntity
{
CborCharString name;
} PublicKeyCredentialEntity;
typedef struct PublicKeyCredentialRpEntity
{
PublicKeyCredentialEntity parent;
CborCharString id;
} PublicKeyCredentialRpEntity;
typedef struct PublicKeyCredentialUserEntity
{
PublicKeyCredentialEntity parent;
CborByteString id;
CborCharString displayName;
} PublicKeyCredentialUserEntity;
typedef struct PublicKeyCredentialParameters {
CborCharString type;
int64_t alg;
} PublicKeyCredentialParameters;
typedef struct PublicKeyCredentialDescriptor {
CborCharString type;
CborByteString id;
CborCharString transports[8];
size_t transports_len;
} PublicKeyCredentialDescriptor;
#endif //_CBOR_MAKE_CREDENTIAL_H_

38
src/fido/cbor_reset.c Normal file
View File

@@ -0,0 +1,38 @@
/*
* 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/>.
*/
#include "ctap2_cbor.h"
#include "file.h"
#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "bsp/board.h"
extern void scan_all();
int cbor_reset() {
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET==1
if (board_millis() > 10000)
return CTAP2_ERR_NOT_ALLOWED;
#endif
if (wait_button_pressed() == true)
return CTAP2_ERR_USER_ACTION_TIMEOUT;
initialize_flash(true);
init_fido(true);
return 0;
}

28
src/fido/cbor_selection.c Normal file
View File

@@ -0,0 +1,28 @@
/*
* 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/>.
*/
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "bsp/board.h"
int cbor_selection() {
if (wait_button_pressed() == true)
return CTAP2_ERR_USER_ACTION_TIMEOUT;
return CTAP2_OK;
}

View File

@@ -18,69 +18,67 @@
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "u2f.h"
#include "ctap.h"
#include "mbedtls/ecdsa.h"
#include "random.h"
#include "files.h"
#include "credential.h"
int cmd_authenticate() {
U2F_AUTHENTICATE_REQ *req = (U2F_AUTHENTICATE_REQ *)apdu.data;
U2F_AUTHENTICATE_RESP *resp = (U2F_AUTHENTICATE_RESP *)res_APDU;
if (scan_files() != CCID_OK)
return SW_EXEC_ERROR();
if (req->keyHandleLen != KEY_HANDLE_LEN)
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *)apdu.data;
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *)res_APDU;
//if (scan_files(true) != CCID_OK)
// return SW_EXEC_ERROR();
if (apdu.nc < CTAP_CHAL_SIZE+CTAP_APPID_SIZE+1+1)
return SW_WRONG_DATA();
if (P1(apdu) == U2F_AUTH_ENFORCE && wait_button_pressed() == true)
if (req->keyHandleLen < KEY_HANDLE_LEN)
return SW_INCORRECT_PARAMS();
if (P1(apdu) == CTAP_AUTH_ENFORCE && wait_button_pressed() == true)
return SW_CONDITIONS_NOT_SATISFIED();
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, false, req->keyHandle, &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) {
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);
return SW_INCORRECT_PARAMS();
}
}
free(tmp_kh);
if (ret != CCID_OK) {
mbedtls_ecdsa_free(&key);
return SW_EXEC_ERROR();
}
if (P1(apdu) == U2F_AUTH_CHECK_ONLY) {
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
uint32_t k = *(uint32_t *)&req->keyHandle[i*sizeof(uint32_t)];
if (!(k & 0x80000000)) {
mbedtls_ecdsa_free(&key);
return SW_WRONG_DATA();
}
}
uint8_t hmac[32], d[32];
ret = mbedtls_ecp_write_key(&key, d, sizeof(d));
if (P1(apdu) == CTAP_AUTH_CHECK_ONLY) {
mbedtls_ecdsa_free(&key);
if (ret != 0)
return SW_WRONG_DATA();
uint8_t key_base[U2F_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, req->appId, U2F_APPID_SIZE);
memcpy(key_base + U2F_APPID_SIZE, req->keyHandle, KEY_PATH_LEN);
ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), d, 32, key_base, sizeof(key_base), hmac);
mbedtls_platform_zeroize(d, sizeof(d));
if (memcmp(req->keyHandle + KEY_PATH_LEN, hmac, sizeof(hmac)) != 0)
return SW_WRONG_DATA();
return SW_CONDITIONS_NOT_SATISFIED();
}
resp->flags = 0;
resp->flags |= P1(apdu) == U2F_AUTH_ENFORCE ? U2F_AUTH_FLAG_TUP : 0x0;
uint32_t ctr = *(uint32_t *)file_get_data(ef_counter);
resp->flags |= P1(apdu) == CTAP_AUTH_ENFORCE ? CTAP_AUTH_FLAG_TUP : 0x0;
uint32_t ctr = get_sign_counter();
resp->ctr[0] = ctr >> 24;
resp->ctr[1] = ctr >> 16;
resp->ctr[2] = ctr >> 8;
resp->ctr[3] = ctr & 0xff;
uint8_t hash[32], sig_base[U2F_APPID_SIZE + 1 + 4 + U2F_CHAL_SIZE];
memcpy(sig_base, req->appId, U2F_APPID_SIZE);
memcpy(sig_base+U2F_APPID_SIZE, &resp->flags, sizeof(uint8_t));
memcpy(sig_base + U2F_APPID_SIZE + 1, resp->ctr, 4);
memcpy(sig_base + U2F_APPID_SIZE + 1 + 4, req->chal, U2F_CHAL_SIZE);
uint8_t hash[32], sig_base[CTAP_APPID_SIZE + 1 + 4 + CTAP_CHAL_SIZE];
memcpy(sig_base, req->appId, CTAP_APPID_SIZE);
memcpy(sig_base+CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
memcpy(sig_base + CTAP_APPID_SIZE + 1, resp->ctr, 4);
memcpy(sig_base + CTAP_APPID_SIZE + 1 + 4, req->chal, CTAP_CHAL_SIZE);
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
if (ret != 0) {
mbedtls_ecdsa_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, U2F_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
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);
if (ret != 0)
return SW_EXEC_ERROR();

View File

@@ -18,43 +18,49 @@
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "u2f.h"
#include "ctap.h"
#include "mbedtls/ecdsa.h"
#include "random.h"
#include "files.h"
const uint8_t *bogus_firefox = (const uint8_t *)"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
const uint8_t *bogus_chrome = (const uint8_t *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
extern int ctap_error(uint8_t error);
int cmd_register() {
U2F_REGISTER_REQ *req = (U2F_REGISTER_REQ *)apdu.data;
U2F_REGISTER_RESP *resp = (U2F_REGISTER_RESP *)res_APDU;
resp->registerId = U2F_REGISTER_ID;
CTAP_REGISTER_REQ *req = (CTAP_REGISTER_REQ *)apdu.data;
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *)res_APDU;
resp->registerId = CTAP_REGISTER_ID;
resp->keyHandleLen = KEY_HANDLE_LEN;
if (scan_files() != CCID_OK)
return SW_EXEC_ERROR();
if (apdu.nc != U2F_APPID_SIZE + U2F_CHAL_SIZE)
//if (scan_files(true) != CCID_OK)
// return SW_EXEC_ERROR();
if (apdu.nc != CTAP_APPID_SIZE + CTAP_CHAL_SIZE)
return SW_WRONG_LENGTH();
if (wait_button_pressed() == true)
return SW_CONDITIONS_NOT_SATISFIED();
if (memcmp(req->appId, bogus_firefox, CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
return ctap_error(CTAP1_ERR_CHANNEL_BUSY);
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, &key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
if (ret != CCID_OK) {
mbedtls_ecdsa_free(&key);
return SW_EXEC_ERROR();
}
size_t olen = 0;
ret = mbedtls_ecp_point_write_binary(&key.grp, &key.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (uint8_t *)&resp->pubKey, U2F_EC_POINT_SIZE);
ret = mbedtls_ecp_point_write_binary(&key.grp, &key.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (uint8_t *)&resp->pubKey, CTAP_EC_POINT_SIZE);
mbedtls_ecdsa_free(&key);
if (ret != 0) {
return SW_EXEC_ERROR();
}
size_t ef_certdev_size = file_get_size(ef_certdev);
memcpy(resp->keyHandleCertSig + KEY_HANDLE_LEN, file_get_data(ef_certdev), ef_certdev_size);
uint8_t hash[32], sign_base[1 + U2F_APPID_SIZE + U2F_CHAL_SIZE + KEY_HANDLE_LEN + U2F_EC_POINT_SIZE];
sign_base[0] = U2F_REGISTER_HASH_ID;
memcpy(sign_base + 1, req->appId, U2F_APPID_SIZE);
memcpy(sign_base + 1 + U2F_APPID_SIZE, req->chal, U2F_CHAL_SIZE);
memcpy(sign_base + 1 + U2F_APPID_SIZE + U2F_CHAL_SIZE, resp->keyHandleCertSig, KEY_HANDLE_LEN);
memcpy(sign_base + 1 + U2F_APPID_SIZE + U2F_CHAL_SIZE + KEY_HANDLE_LEN, (uint8_t *)&resp->pubKey, U2F_EC_POINT_SIZE);
uint8_t hash[32], sign_base[1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN + CTAP_EC_POINT_SIZE];
sign_base[0] = CTAP_REGISTER_HASH_ID;
memcpy(sign_base + 1, req->appId, CTAP_APPID_SIZE);
memcpy(sign_base + 1 + CTAP_APPID_SIZE, req->chal, CTAP_CHAL_SIZE);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE, resp->keyHandleCertSig, KEY_HANDLE_LEN);
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN, (uint8_t *)&resp->pubKey, CTAP_EC_POINT_SIZE);
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sign_base, sizeof(sign_base), hash);
if (ret != 0)
return SW_EXEC_ERROR();
@@ -64,11 +70,10 @@ int cmd_register() {
mbedtls_ecdsa_free(&key);
return SW_EXEC_ERROR();
}
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *)resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, U2F_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *)resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
mbedtls_ecdsa_free(&key);
if (ret != 0)
return SW_EXEC_ERROR();
res_APDU_size = sizeof(U2F_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN + ef_certdev_size + olen;
DEBUG_PAYLOAD(res_APDU, res_APDU_size);
res_APDU_size = sizeof(CTAP_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN + ef_certdev_size + olen;
return SW_OK();
}

316
src/fido/credential.c Normal file
View File

@@ -0,0 +1,316 @@
/*
* 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/>.
*/
#include "common.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h"
#include "credential.h"
#include "bsp/board.h"
#include "fido.h"
#include "ctap.h"
#include "random.h"
#include "files.h"
#include "file.h"
#include "hsm.h"
int credential_derive_chacha_key(uint8_t *outk);
int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
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);
return mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
}
int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_t *cred_id_len) {
CborEncoder encoder, mapEncoder, mapEncoder2;
CborError error = CborNoError;
uint8_t rp_id_hash[32];
mbedtls_sha256((uint8_t *)rpId->data, rpId->len, rp_id_hash, 0);
cbor_encoder_init(&encoder, cred_id+4+12, MAX_CRED_ID_LENGTH-(4+12+16), 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, CborIndefiniteLength));
CBOR_APPEND_KEY_UINT_VAL_STRING(mapEncoder, 0x01, *rpId);
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, rp_id_hash, 32));
CBOR_APPEND_KEY_UINT_VAL_BYTES(mapEncoder, 0x03, *userId);
CBOR_APPEND_KEY_UINT_VAL_STRING(mapEncoder, 0x04, *userName);
CBOR_APPEND_KEY_UINT_VAL_STRING(mapEncoder, 0x05, *userDisplayName);
CBOR_APPEND_KEY_UINT_VAL_UINT(mapEncoder, 0x06, board_millis());
if (extensions->present == true) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength));
if (extensions->credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, extensions->credProtect));
}
if (extensions->hmac_secret != NULL) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, *extensions->hmac_secret));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, use_sign_count));
if (alg != FIDO2_ALG_ES256 || curve != FIDO2_CURVE_P256) {
CBOR_APPEND_KEY_UINT_VAL_INT(mapEncoder, 0x09, alg);
CBOR_APPEND_KEY_UINT_VAL_INT(mapEncoder, 0x0A, curve);
}
if (opts->present == true) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength));
if (opts->rk != NULL) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "rk"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, opts->rk == ptrue));
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
}
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];
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);
mbedtls_chachapoly_free(&chatx);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_OTHER);
}
memcpy(cred_id, CRED_PROTO, 4);
memcpy(cred_id + 4, iv, 12);
err:
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
return 0;
}
int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred) {
int ret = 0;
CborError error;
uint8_t *copy_cred_id = (uint8_t *)calloc(1, cred_id_len);
memcpy(copy_cred_id, cred_id, cred_id_len);
ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash);
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);
}
else {
CborParser parser;
CborValue map;
memset(cred, 0, sizeof(Credential));
cred->curve = FIDO2_CURVE_P256;
cred->alg = FIDO2_ALG_ES256;
CBOR_CHECK(cbor_parser_init(copy_cred_id + 4 + 12, cred_id_len - (4 + 12 + 16), 0, &parser, &map));
CBOR_PARSE_MAP_START(map, 1) {
uint64_t val_u = 0;
CBOR_FIELD_GET_UINT(val_u, 1);
if (val_u == 0x01) {
CBOR_FIELD_GET_TEXT(cred->rpId, 1);
}
else if (val_u == 0x03) {
CBOR_FIELD_GET_BYTES(cred->userId, 1);
}
else if (val_u == 0x04) {
CBOR_FIELD_GET_TEXT(cred->userName, 1);
}
else if (val_u == 0x05) {
CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1);
}
else if (val_u == 0x06) {
CBOR_FIELD_GET_UINT(cred->creation, 1);
}
else if (val_u == 0x07) {
cred->extensions.present = true;
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x08) {
CBOR_FIELD_GET_BOOL(cred->use_sign_count, 1);
}
else if (val_u == 0x09) {
CBOR_FIELD_GET_INT(cred->alg, 1);
}
else if (val_u == 0x0A) {
CBOR_FIELD_GET_INT(cred->curve, 1);
}
else if (val_u == 0x0B) {
cred->opts.present = true;
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", cred->opts.rk);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else {
CBOR_ADVANCE(1);
}
}
}
cred->id.present = true;
cred->id.data = (uint8_t *)calloc(1, cred_id_len);
memcpy(cred->id.data, cred_id, cred_id_len);
cred->id.len = cred_id_len;
cred->present = true;
err:
free(copy_cred_id);
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
return 0;
}
void credential_free(Credential *cred) {
CBOR_FREE_BYTE_STRING(cred->rpId);
CBOR_FREE_BYTE_STRING(cred->userId);
CBOR_FREE_BYTE_STRING(cred->userName);
CBOR_FREE_BYTE_STRING(cred->userDisplayName);
CBOR_FREE_BYTE_STRING(cred->id);
cred->present = false;
cred->extensions.present = false;
cred->opts.present = false;
}
int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
int sloti = -1;
Credential cred = {0};
int ret = 0;
bool new_record = true;
ret = credential_load(cred_id, cred_id_len, rp_id_hash, &cred);
if (ret != 0) {
credential_free(&cred);
return ret;
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
Credential rcred = {0};
if (!file_has_data(ef)) {
if (sloti == -1)
sloti = i;
continue;
}
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);
if (ret != 0) {
credential_free(&rcred);
continue;
}
if (memcmp(rcred.userId.data, cred.userId.data, MIN(rcred.userId.len, cred.userId.len)) == 0) {
sloti = i;
credential_free(&rcred);
new_record = false;
break;
}
credential_free(&rcred);
}
if (sloti == -1)
return -1;
uint8_t *data = (uint8_t *)calloc(1, cred_id_len+32);
memcpy(data, rp_id_hash, 32);
memcpy(data + 32, cred_id, cred_id_len);
file_t *ef = file_new(EF_CRED+sloti);
flash_write_data_to_file(ef, data, cred_id_len + 32);
free(data);
if (new_record == true) { //increase rps
sloti = -1;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
ef = search_dynamic_file(EF_RP + i);
if (!file_has_data(ef)) {
if (sloti == -1)
sloti = i;
continue;
}
if (memcmp(file_get_data(ef)+1, rp_id_hash, 32) == 0) {
sloti = i;
break;
}
}
if (sloti == -1)
return -1;
ef = search_dynamic_file(EF_RP + sloti);
if (file_has_data(ef)) {
data = (uint8_t *)calloc(1, file_get_size(ef));
memcpy(data, file_get_data(ef), file_get_size(ef));
data[0] += 1;
flash_write_data_to_file(ef, data, file_get_size(ef));
free(data);
}
else {
ef = file_new(EF_RP+sloti);
data = (uint8_t *)calloc(1, 1 + 32 + cred.rpId.len);
data[0] = 1;
memcpy(data+1, rp_id_hash, 32);
memcpy(data + 1 + 32, cred.rpId.data, cred.rpId.len);
flash_write_data_to_file(ef, data, 1 + 32 + cred.rpId.len);
free(data);
}
}
credential_free(&cred);
low_flash_available();
return 0;
}
int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
memset(outk, 0, 64);
int r = 0;
if ((r = load_keydev(outk)) != 0)
return r;
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 *)"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) {
memset(outk, 0, 32);
int r = 0;
if ((r = load_keydev(outk)) != 0)
return r;
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 *)"Encryption key", 14, outk);
return 0;
}

65
src/fido/credential.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* 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/>.
*/
#ifndef _CREDENTIAL_H_
#define _CREDENTIAL_H_
#include "ctap2_cbor.h"
typedef struct CredOptions {
const bool *rk;
const bool *up;
const bool *uv;
bool present;
} CredOptions;
typedef struct CredExtensions {
const bool *hmac_secret;
uint64_t credProtect;
bool present;
} CredExtensions;
typedef struct Credential
{
CborCharString rpId;
CborByteString userId;
CborCharString userName;
CborCharString userDisplayName;
uint64_t creation;
CredExtensions extensions;
const bool *use_sign_count;
int64_t alg;
int64_t curve;
CborByteString id;
CredOptions opts;
bool present;
} 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"
extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
extern int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_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, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred);
extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
#endif // _CREDENTIAL_H_

173
src/fido/ctap.h Normal file
View File

@@ -0,0 +1,173 @@
/*
* This file is part of the Pico HSM SDK distribution (https://github.com/polhenarejos/pico-hsm-sdk).
* 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/>.
*/
#ifndef _CTAP_H_
#define _CTAP_H_
#ifdef _MSC_VER // Windows
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long int uint64_t;
#else
#include <stdint.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// General constants
#define CTAP_EC_KEY_SIZE 32 // EC key size in bytes
#define CTAP_EC_POINT_SIZE ((CTAP_EC_KEY_SIZE * 2) + 1) // Size of EC point
#define CTAP_MAX_KH_SIZE 128 // Max size of key handle
#define CTAP_MAX_ATT_CERT_SIZE 2048 // Max size of attestation certificate
#define CTAP_MAX_EC_SIG_SIZE 72 // Max size of DER coded EC signature
#define CTAP_CTR_SIZE 4 // Size of counter field
#define CTAP_APPID_SIZE 32 // Size of application id
#define CTAP_CHAL_SIZE 32 // Size of challenge
#define ENC_SIZE(x) ((x + 7) & 0xfff8)
// EC (uncompressed) point
#define CTAP_POINT_UNCOMPRESSED 0x04 // Uncompressed point format
typedef struct {
uint8_t pointFormat; // Point type
uint8_t x[CTAP_EC_KEY_SIZE]; // X-value
uint8_t y[CTAP_EC_KEY_SIZE]; // Y-value
} CTAP_EC_POINT;
// CTAP MSG commands
#define CTAP_REGISTER 0x01 // Registration command
#define CTAP_AUTHENTICATE 0x02 // Authenticate/sign command
#define CTAP_VERSION 0x03 // Read version string command
#define CTAP_VENDOR_FIRST 0x40 // First vendor defined command
#define CTAP_VENDOR_LAST 0xbf // Last vendor defined command
// CTAP_CMD_REGISTER command defines
#define CTAP_REGISTER_ID 0x05 // Version 2 registration identifier
#define CTAP_REGISTER_HASH_ID 0x00 // Version 2 hash identintifier
typedef struct {
uint8_t chal[CTAP_CHAL_SIZE]; // Challenge
uint8_t appId[CTAP_APPID_SIZE]; // Application id
} CTAP_REGISTER_REQ;
typedef struct {
uint8_t registerId; // Registration identifier (CTAP_REGISTER_ID_V2)
CTAP_EC_POINT pubKey; // Generated public key
uint8_t keyHandleLen; // Length of key handle
uint8_t keyHandleCertSig[
CTAP_MAX_KH_SIZE + // Key handle
CTAP_MAX_ATT_CERT_SIZE + // Attestation certificate
CTAP_MAX_EC_SIG_SIZE]; // Registration signature
} CTAP_REGISTER_RESP;
// CTAP_CMD_AUTHENTICATE command defines
// Authentication control byte
#define CTAP_AUTH_ENFORCE 0x03 // Enforce user presence and sign
#define CTAP_AUTH_CHECK_ONLY 0x07 // Check only
#define CTAP_AUTH_FLAG_TUP 0x01 // Test of user presence set
typedef struct {
uint8_t chal[CTAP_CHAL_SIZE]; // Challenge
uint8_t appId[CTAP_APPID_SIZE]; // Application id
uint8_t keyHandleLen; // Length of key handle
uint8_t keyHandle[CTAP_MAX_KH_SIZE]; // Key handle
} CTAP_AUTHENTICATE_REQ;
typedef struct {
uint8_t flags; // CTAP_AUTH_FLAG_ values
uint8_t ctr[CTAP_CTR_SIZE]; // Counter field (big-endian)
uint8_t sig[CTAP_MAX_EC_SIG_SIZE]; // Signature
} CTAP_AUTHENTICATE_RESP;
// CTAP CBOR commands
#define CTAP_MAKE_CREDENTIAL 0x01
#define CTAP_GET_ASSERTION 0x02
#define CTAP_GET_INFO 0x04
#define CTAP_CLIENT_PIN 0x06
#define CTAP_RESET 0x07
#define CTAP_GET_NEXT_ASSERTION 0x08
#define CTAP_CREDENTIAL_MGMT 0x0A
#define CTAP_SELECTION 0x0B
// Command status responses
#define CTAP_SW_NO_ERROR 0x9000 // SW_NO_ERROR
#define CTAP_SW_WRONG_DATA 0x6A80 // SW_WRONG_DATA
#define CTAP_SW_CONDITIONS_NOT_SATISFIED 0x6985 // SW_CONDITIONS_NOT_SATISFIED
#define CTAP_SW_COMMAND_NOT_ALLOWED 0x6986 // SW_COMMAND_NOT_ALLOWED
#define CTAP_SW_INS_NOT_SUPPORTED 0x6D00 // SW_INS_NOT_SUPPORTED
#define CTAP2_OK 0x00
#define CTAP2_ERR_CBOR_UNEXPECTED_TYPE 0x11
#define CTAP2_ERR_INVALID_CBOR 0x12
#define CTAP2_ERR_MISSING_PARAMETER 0x14
#define CTAP2_ERR_LIMIT_EXCEEDED 0x15
#define CTAP2_ERR_FP_DATABASE_FULL 0x17
#define CTAP2_ERR_LARGE_BLOB_STORAGE_FULL 0x18
#define CTAP2_ERR_CREDENTIAL_EXCLUDED 0x19
#define CTAP2_ERR_PROCESSING 0x21
#define CTAP2_ERR_INVALID_CREDENTIAL 0x22
#define CTAP2_ERR_USER_ACTION_PENDING 0x23
#define CTAP2_ERR_OPERATION_PENDING 0x24
#define CTAP2_ERR_NO_OPERATIONS 0x25
#define CTAP2_ERR_UNSUPPORTED_ALGORITHM 0x26
#define CTAP2_ERR_OPERATION_DENIED 0x27
#define CTAP2_ERR_KEY_STORE_FULL 0x28
#define CTAP2_ERR_UNSUPPORTED_OPTION 0x2B
#define CTAP2_ERR_INVALID_OPTION 0x2C
#define CTAP2_ERR_KEEPALIVE_CANCEL 0x2D
#define CTAP2_ERR_NO_CREDENTIALS 0x2E
#define CTAP2_ERR_USER_ACTION_TIMEOUT 0x2F
#define CTAP2_ERR_NOT_ALLOWED 0x30
#define CTAP2_ERR_PIN_INVALID 0x31
#define CTAP2_ERR_PIN_BLOCKED 0x32
#define CTAP2_ERR_PIN_AUTH_INVALID 0x33
#define CTAP2_ERR_PIN_AUTH_BLOCKED 0x34
#define CTAP2_ERR_PIN_NOT_SET 0x35
#define CTAP2_ERR_PUAT_REQUIRED 0x36
#define CTAP2_ERR_PIN_POLICY_VIOLATION 0x37
#define CTAP2_ERR_REQUEST_TOO_LARGE 0x39
#define CTAP2_ERR_ACTION_TIMEOUT 0x3A
#define CTAP2_ERR_UP_REQUIRED 0x3B
#define CTAP2_ERR_UV_BLOCKED 0x3C
#define CTAP2_ERR_INTEGRITY_FAILURE 0x3D
#define CTAP2_ERR_INVALID_SUBCOMMAND 0x3E
#define CTAP2_ERR_UV_INVALID 0x3F
#define CTAP2_ERR_UNAUTHORIZED_PERMISSION 0x40
#define CTAP2_ERR_SPEC_LAST 0xDF
#define CTAP2_ERR_EXTENSION_FIRST 0xE0
#define CTAP2_ERR_EXTENSION_LAST 0xEF
#define CTAP2_ERR_VENDOR_FIRST 0xF0
#define CTAP2_ERR_VENDOR_LAST 0xFF
#ifdef __cplusplus
}
#endif
#endif // _CTAP_H_

243
src/fido/ctap2_cbor.h Normal file
View File

@@ -0,0 +1,243 @@
/*
* 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/>.
*/
#ifndef _CTAP2_CBOR_H_
#define _CTAP2_CBOR_H_
#include <stdlib.h>
#include "pico/stdlib.h"
#include <stdio.h>
#include "cbor.h"
extern uint8_t *driver_prepare_response();
extern void driver_exec_finished(size_t size_next);
extern int cbor_process(const uint8_t *data, size_t len);
extern const uint8_t aaguid[16];
extern const bool _btrue, _bfalse;
#define ptrue (&_btrue)
#define pfalse (&_bfalse)
#define CBOR_CHECK(f) \
do \
{ \
error = f; \
if (error != CborNoError) \
{ \
printf("Cannot encode CBOR [%s:%d]: %s (%d)\n", __FILE__, __LINE__, #f, error); \
goto err; \
} \
} while (0)
#define CBOR_FREE(x) \
do \
{ \
if (x) \
{ \
free(x); \
x = NULL;\
} \
} while(0)
#define CBOR_ERROR(e) \
do \
{ \
error = e; \
printf("Cbor ERROR [%s:%d]: %d\n", __FILE__, __LINE__, e); \
goto err; \
} while(0)
#define CBOR_ASSERT(c) \
do \
{ \
if (!c) \
{ \
error = CborErrorImproperValue; \
printf("Cbor ASSERT [%s:%d]: %s\n", __FILE__, __LINE__, #c); \
goto err; \
} \
} while(0)
#define PINUVAUTHTOKEN_MC 0x1
#define PINUVAUTHTOKEN_GA 0x2
#define PINUVAUTHTOKEN_CM 0x4
#define PINUVAUTHTOKEN_BE 0x8
#define PINUVAUTHTOKEN_LBW 0x10
#define PINUVAUTHTOKEN_ACFG 0x20
typedef struct CborByteString {
uint8_t *data;
size_t len;
bool present;
bool nofree;
} CborByteString;
typedef struct CborCharString {
char *data;
size_t len;
bool present;
bool nofree;
} CborCharString;
#define CBOR_FREE_BYTE_STRING(v) \
do \
{ \
if ((v).nofree != true) \
CBOR_FREE((v).data); \
else \
(v).data = NULL; \
(v).len = 0; \
(v).present = false; \
} while(0)
#define CBOR_PARSE_MAP_START(_p,_n) \
CBOR_ASSERT(cbor_value_is_map(&(_p)) == true); \
CborValue _f##_n; \
CBOR_CHECK(cbor_value_enter_container(&(_p), &(_f##_n))); \
while (cbor_value_at_end(&(_f##_n)) == false)
#define CBOR_PARSE_ARRAY_START(_p,_n) \
CBOR_ASSERT(cbor_value_is_array(&(_p)) == true); \
CborValue _f##_n; \
CBOR_CHECK(cbor_value_enter_container(&(_p), &(_f##_n))); \
while (cbor_value_at_end(&(_f##_n)) == false)
#define CBOR_FIELD_GET_UINT(v, _n) \
do { \
CBOR_ASSERT(cbor_value_is_unsigned_integer(&(_f##_n)) == true); \
CBOR_CHECK(cbor_value_get_uint64(&(_f##_n), &(v))); \
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
} while(0)
#define CBOR_FIELD_GET_INT(v, _n) \
do { \
CBOR_ASSERT(cbor_value_is_integer(&(_f##_n)) == true); \
CBOR_CHECK(cbor_value_get_int64(&(_f##_n), &(v))); \
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
} while(0)
#define CBOR_FIELD_GET_BYTES(v, _n) \
do { \
CBOR_ASSERT(cbor_value_is_byte_string(&(_f##_n)) == true); \
CBOR_CHECK(cbor_value_dup_byte_string(&(_f##_n), &(v).data, &(v).len, &(_f##_n))); \
(v).present = true; \
} while (0)
#define CBOR_FIELD_GET_TEXT(v, _n) \
do { \
CBOR_ASSERT(cbor_value_is_text_string(&(_f##_n)) == true); \
CBOR_CHECK(cbor_value_dup_text_string(&(_f##_n), &(v).data, &(v).len, &(_f##_n))); \
(v).present = true; \
} while (0)
#define CBOR_FIELD_GET_BOOL(v, _n) \
do { \
CBOR_ASSERT(cbor_value_is_boolean(&(_f##_n)) == true); \
bool val; \
CBOR_CHECK(cbor_value_get_boolean(&(_f##_n), &val)); \
v = (val == true ? ptrue : pfalse); \
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
} while(0)
#define CBOR_FIELD_GET_KEY_TEXT(_n) \
CBOR_ASSERT(cbor_value_is_text_string(&(_f##_n)) == true); \
char _fd##_n[64]; \
size_t _fdl##_n = sizeof(_fd##_n); \
CBOR_CHECK(cbor_value_copy_text_string(&(_f##_n), _fd##_n, &_fdl##_n, &(_f##_n)))
#define CBOR_FIELD_KEY_TEXT_VAL_TEXT(_n, _t, _v) \
if (strcmp(_fd##_n, _t) == 0) { \
CBOR_ASSERT(cbor_value_is_text_string(&_f##_n) == true); \
CBOR_CHECK(cbor_value_dup_text_string(&(_f##_n), &(_v).data, &(_v).len, &(_f##_n))); \
(_v).present = true; \
continue; \
}
#define CBOR_FIELD_KEY_TEXT_VAL_BYTES(_n, _t, _v) \
if (strcmp(_fd##_n, _t) == 0) { \
CBOR_ASSERT(cbor_value_is_byte_string(&_f##_n) == true); \
CBOR_CHECK(cbor_value_dup_byte_string(&(_f##_n), &(_v).data, &(_v).len, &(_f##_n))); \
(_v).present = true; \
continue; \
}
#define CBOR_FIELD_KEY_TEXT_VAL_INT(_n, _t, _v) \
if (strcmp(_fd##_n, _t) == 0) { \
CBOR_FIELD_GET_INT(_v, _n);\
continue; \
}
#define CBOR_FIELD_KEY_TEXT_VAL_UINT(_n, _t, _v) \
if (strcmp(_fd##_n, _t) == 0) { \
CBOR_FIELD_GET_UINT(_v, _n);\
continue; \
}
#define CBOR_FIELD_KEY_TEXT_VAL_BOOL(_n, _t, _v) \
if (strcmp(_fd##_n, _t) == 0) { \
CBOR_FIELD_GET_BOOL(_v, _n);\
continue; \
}
#define CBOR_PARSE_MAP_END(_p,_n) \
CBOR_CHECK(cbor_value_leave_container(&(_p), &(_f##_n)))
#define CBOR_PARSE_ARRAY_END(_p,_n) CBOR_PARSE_MAP_END(_p, _n)
#define CBOR_ADVANCE(_n) CBOR_CHECK(cbor_value_advance(&_f##_n));
#define CBOR_APPEND_KEY_UINT_VAL_BYTES(p, k, v) \
do { \
if ((v).data && (v).len > 0) { \
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
CBOR_CHECK(cbor_encode_byte_string(&(p), (v).data, (v).len)); \
} } while(0)
#define CBOR_APPEND_KEY_UINT_VAL_STRING(p, k, v) \
do { \
if ((v).data && (v).len > 0) { \
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
CBOR_CHECK(cbor_encode_text_stringz(&(p), (v).data)); \
} } while(0)
#define CBOR_APPEND_KEY_UINT_VAL_UINT(p, k, v) \
do { \
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
CBOR_CHECK(cbor_encode_uint(&(p), (v))); \
} while(0)
#define CBOR_APPEND_KEY_UINT_VAL_INT(p, k, v) \
do { \
CBOR_CHECK(cbor_encode_int(&(p), (k))); \
CBOR_CHECK(cbor_encode_int(&(p), (v))); \
} while(0)
#define CBOR_APPEND_KEY_UINT_VAL_BOOL(p, k, v) \
do { \
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
CBOR_CHECK(cbor_encode_boolean(&(p), (v))); \
} while(0)
#define CBOR_APPEND_KEY_UINT_VAL_PBOOL(p, k, v) \
do { \
if (v != NULL) {\
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
CBOR_CHECK(cbor_encode_boolean(&(p), v == ptrue ? true : false)); \
} } while(0)
#endif //_CTAP2_CBOR_H_

View File

@@ -19,22 +19,26 @@
#include "fido.h"
#include "hsm.h"
#include "apdu.h"
#include "u2f.h"
#include "ctap.h"
#include "files.h"
#include "file.h"
#include "usb.h"
#include "random.h"
#include "bsp/board.h"
#include "mbedtls/ecdsa.h"
#include "mbedtls/x509_crt.h"
#include "mbedtls/hkdf.h"
#include "pk_wrap.h"
#include "crypto_utils.h"
#include <math.h>
#include <stdio.h>
void init_fido();
void init_fido(bool);
int fido_process_apdu();
int fido_unload();
pinUvAuthToken_t paut = {0};
const uint8_t fido_aid[] = {
8,
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
@@ -45,7 +49,7 @@ app_t *fido_select(app_t *a) {
a->process_apdu = fido_process_apdu;
a->unload = fido_unload;
current_app = a;
init_fido();
//init_fido(false);
return a;
}
@@ -58,7 +62,35 @@ int fido_unload() {
return CCID_OK;
}
int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size) {
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
if (curve == FIDO2_CURVE_P256)
return MBEDTLS_ECP_DP_SECP256R1;
else if (curve == FIDO2_CURVE_P384)
return MBEDTLS_ECP_DP_SECP384R1;
else if (curve == FIDO2_CURVE_P521)
return MBEDTLS_ECP_DP_SECP521R1;
else if (curve == FIDO2_CURVE_P256K1)
return MBEDTLS_ECP_DP_SECP256K1;
else if (curve == FIDO2_CURVE_X25519)
return MBEDTLS_ECP_DP_CURVE25519;
else if (curve == FIDO2_CURVE_X448)
return MBEDTLS_ECP_DP_CURVE448;
return MBEDTLS_ECP_DP_NONE;
}
int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key) {
mbedtls_ecp_group_id mbedtls_curve = fido_curve_to_mbedtls(curve);
if (mbedtls_curve == MBEDTLS_ECP_DP_NONE)
return CTAP2_ERR_UNSUPPORTED_ALGORITHM;
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++)
*(uint32_t *)(key_path+i*sizeof(uint32_t)) |= 0x80000000;
return derive_key(NULL, false, key_path, mbedtls_curve, key);
}
int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size, bool core1) {
mbedtls_x509write_cert ctx;
mbedtls_x509write_crt_init(&ctx);
mbedtls_x509write_crt_set_version(&ctx, MBEDTLS_X509_CRT_VERSION_3);
@@ -67,7 +99,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
mbedtls_x509write_crt_set_subject_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
mbedtls_mpi serial;
mbedtls_mpi_init(&serial);
mbedtls_mpi_fill_random(&serial, 32, random_gen, NULL);
mbedtls_mpi_fill_random(&serial, 32, core1 ? random_gen : random_gen_core0, NULL);
mbedtls_x509write_crt_set_serial(&ctx, &serial);
mbedtls_pk_context key;
mbedtls_pk_init(&key);
@@ -80,7 +112,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
mbedtls_x509write_crt_set_subject_key_identifier(&ctx);
mbedtls_x509write_crt_set_authority_key_identifier(&ctx);
mbedtls_x509write_crt_set_key_usage(&ctx, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_CERT_SIGN);
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, random_gen, NULL);
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, core1 ? random_gen : random_gen_core0, NULL);
return ret;
}
@@ -92,29 +124,60 @@ int load_keydev(uint8_t *key) {
return CCID_OK;
}
int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, mbedtls_ecdsa_context *key) {
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *key) {
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
uint32_t k = *(uint32_t *)&keyHandle[i*sizeof(uint32_t)];
if (!(k & 0x80000000)) {
return -1;
}
}
mbedtls_ecdsa_context ctx;
if (key == NULL) {
mbedtls_ecdsa_init(&ctx);
key = &ctx;
if (derive_key(appId, false, (uint8_t *)keyHandle, MBEDTLS_ECP_DP_SECP256R1, &ctx) != 0) {
mbedtls_ecdsa_free(&ctx);
return -3;
}
}
uint8_t hmac[32], d[32];
int ret = mbedtls_ecp_write_key(key, d, sizeof(d));
if (key == NULL)
mbedtls_ecdsa_free(&ctx);
if (ret != 0)
return -2;
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, appId, CTAP_APPID_SIZE);
memcpy(key_base + CTAP_APPID_SIZE, keyHandle, KEY_PATH_LEN);
ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), d, 32, key_base, sizeof(key_base), hmac);
mbedtls_platform_zeroize(d, sizeof(d));
return memcmp(keyHandle + KEY_PATH_LEN, hmac, sizeof(hmac));
}
int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int curve, mbedtls_ecdsa_context *key) {
uint8_t outk[64] = {0};
int r = 0;
memset(outk, 0, sizeof(outk));
if ((r = load_keydev(outk)) != CCID_OK)
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 (int i = 0; i < KEY_PATH_ENTRIES; i++) {
if (new_key == true) {
uint32_t val = 0x80000000 | *((uint32_t *)random_bytes_get(sizeof(uint32_t)));
uint32_t val = 0;
random_gen(NULL, (uint8_t *) &val, sizeof(val));
val |= 0x80000000;
memcpy(&key_handle[i*sizeof(uint32_t)], &val, sizeof(uint32_t));
}
if ((r = mbedtls_hkdf(md_info, &key_handle[i], sizeof(uint32_t), outk, 32, outk + 32, 32, outk, sizeof(outk))) != 0)
{
r = mbedtls_hkdf(md_info, &key_handle[i * sizeof(uint32_t)], sizeof(uint32_t), outk, 32, outk + 32, 32, outk, sizeof(outk));
if (r != 0) {
mbedtls_platform_zeroize(outk, sizeof(outk));
return r;
}
}
if (new_key == true) {
uint8_t key_base[U2F_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, app_id, U2F_APPID_SIZE);
memcpy(key_base + U2F_APPID_SIZE, key_handle, KEY_PATH_LEN);
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
memcpy(key_base, app_id, CTAP_APPID_SIZE);
memcpy(key_base + CTAP_APPID_SIZE, key_handle, KEY_PATH_LEN);
if ((r = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, key_base, sizeof(key_base), key_handle + 32)) != 0)
{
mbedtls_platform_zeroize(outk, sizeof(outk));
@@ -122,26 +185,29 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, mbedtls
}
}
if (key != NULL) {
mbedtls_ecp_group_load(&key->grp, MBEDTLS_ECP_DP_SECP256R1);
r = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, key, outk, 32);
mbedtls_ecp_group_load(&key->grp, curve);
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(curve);
if (cinfo == NULL)
return 1;
r = mbedtls_ecp_read_key(curve, key, outk, ceil((float)cinfo->bit_size/8));
mbedtls_platform_zeroize(outk, sizeof(outk));
if (r != 0)
return r;
return mbedtls_ecp_mul(&key->grp, &key->Q, &key->d, &key->grp.G, random_gen, NULL );
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 scan_files(bool core1) {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
if (ef_keydev) {
if (!ef_keydev->data) {
if (!file_has_data(ef_keydev)) {
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
mbedtls_ecdsa_context ecdsa;
mbedtls_ecdsa_init(&ecdsa);
uint8_t index = 0;
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, random_gen, &index);
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, core1 ? random_gen : random_gen_core0, &index);
if (ret != 0) {
mbedtls_ecdsa_free(&ecdsa);
return ret;
@@ -151,8 +217,8 @@ int scan_files() {
mbedtls_mpi_write_binary(&ecdsa.d, kdata, key_size);
ret = flash_write_data_to_file(ef_keydev, kdata, key_size);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
mbedtls_ecdsa_free(&ecdsa);
if (ret != CCID_OK) {
mbedtls_ecdsa_free(&ecdsa);
return ret;
}
printf(" done!\n");
@@ -163,21 +229,21 @@ int scan_files() {
}
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
if (ef_certdev) {
if (file_get_size(ef_certdev) == 0 || !ef_certdev->data) {
if (!file_has_data(ef_certdev)) {
uint8_t cert[4096];
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), 32);
printf("ret %d\n", ret);
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), file_get_size(ef_keydev));
if (ret != 0)
return ret;
ret = x509_create_cert(&key, cert, sizeof(cert));
ret = mbedtls_ecp_mul(&key.grp, &key.Q, &key.d, &key.grp.G, core1 ? random_gen : random_gen_core0, NULL);
if (ret != 0)
return ret;
ret = x509_create_cert(&key, cert, sizeof(cert), core1);
mbedtls_ecdsa_free(&key);
printf("ret %d\n", ret);
if (ret <= 0)
return ret;
flash_write_data_to_file(ef_certdev, cert + sizeof(cert) - ret, ret);
DEBUG_PAYLOAD(cert + sizeof(cert) - ret, ret);
}
}
else {
@@ -185,7 +251,7 @@ int scan_files() {
}
ef_counter = search_by_fid(EF_COUNTER, NULL, SPECIFY_EF);
if (ef_counter) {
if (file_get_size(ef_counter) == 0 || !ef_counter->data) {
if (!file_has_data(ef_counter)) {
uint32_t v = 0;
flash_write_data_to_file(ef_counter, (uint8_t *)&v, sizeof(v));
}
@@ -193,25 +259,63 @@ int scan_files() {
else {
printf("FATAL ERROR: Global counter not found in memory!\r\n");
}
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
if (ef_authtoken) {
if (!file_has_data(ef_authtoken)) {
uint8_t t[32];
if (core1)
random_gen(NULL, t, sizeof(t));
else
random_gen_core0(NULL, t, sizeof(t));
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
}
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
}
else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
}
low_flash_available();
return CCID_OK;
}
void scan_all() {
void scan_all(bool core1) {
scan_flash();
scan_files(core1);
}
void init_fido() {
scan_all();
void init_fido(bool core1) {
scan_all(core1);
}
bool wait_button_pressed() {
uint32_t val = EV_PRESS_BUTTON;
#if defined(ENABLE_UP_BUTTON) && ENABLE_UP_BUTTON==1
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);
return val == EV_BUTTON_TIMEOUT;
#endif
return (val == EV_BUTTON_TIMEOUT);
}
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 (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);
}
typedef struct cmd
@@ -225,13 +329,15 @@ extern int cmd_authenticate();
extern int cmd_version();
static const cmd_t cmds[] = {
{ U2F_REGISTER, cmd_register },
{ U2F_AUTHENTICATE, cmd_authenticate },
{ U2F_VERSION, cmd_version },
{ CTAP_REGISTER, cmd_register },
{ CTAP_AUTHENTICATE, cmd_authenticate },
{ CTAP_VERSION, cmd_version },
{ 0x00, 0x0}
};
int fido_process_apdu() {
if (CLA(apdu) != 0x00)
return SW_CLA_NOT_SUPPORTED();
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++)
{
if (cmd->ins == INS(apdu)) {

View File

@@ -22,15 +22,92 @@
#include "pico/stdlib.h"
#include "common.h"
#include "mbedtls/ecdsa.h"
#include "ctap_hid.h"
#define U2F_PUBKEY_LEN (65)
#define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32)
#define KEY_PATH_ENTRIES (KEY_PATH_LEN / sizeof(uint32_t))
#define SHA256_DIGEST_LENGTH (32)
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
extern int scan_files();
extern int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, mbedtls_ecdsa_context *key);
extern int scan_files(bool);
extern int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int, mbedtls_ecdsa_context *key);
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *);
extern bool wait_button_pressed();
extern CTAPHID_FRAME *ctap_req, *ctap_resp;
extern void init_fido(bool);
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key);
extern int load_keydev(uint8_t *key);
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out);
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out);
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
#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_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
#define FIDO2_CURVE_P256 1
#define FIDO2_CURVE_P384 2
#define FIDO2_CURVE_P521 3
#define FIDO2_CURVE_X25519 4
#define FIDO2_CURVE_X448 5
#define FIDO2_CURVE_ED25519 6
#define FIDO2_CURVE_ED448 7
#define FIDO2_CURVE_P256K1 8
#define FIDO2_AUT_FLAG_UP 0x1
#define FIDO2_AUT_FLAG_UV 0x4
#define FIDO2_AUT_FLAG_AT 0x40
#define FIDO2_AUT_FLAG_ED 0x80
#define FIDO2_PERMISSION_MC 0x1
#define FIDO2_PERMISSION_GA 0x2
#define FIDO2_PERMISSION_CM 0x4
#define FIDO2_PERMISSION_BE 0x8
#define FIDO2_PERMISSION_LBW 0x10
#define FIDO2_PERMISSION_ACFG 0x20
#define MAX_PIN_RETRIES 8
extern bool getUserPresentFlagValue();
extern bool getUserVerifiedFlagValue();
extern void clearUserPresentFlag();
extern void clearUserVerifiedFlag();
extern void clearPinUvAuthTokenPermissionsExceptLbw();
extern void send_keepalive();
extern uint32_t get_sign_counter();
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
#define MAX_CRED_ID_LENGTH 1024
#define MAX_RESIDENT_CREDENTIALS 256
typedef struct known_app {
const uint8_t *rp_id_hash;
const char *label;
const bool *use_sign_count;
const bool *use_self_attestation;
} known_app_t;
extern const known_app_t *find_app_by_rp_id_hash(const uint8_t *rp_id_hash);
#define TRANSPORT_TIME_LIMIT (30*1000) //USB
bool check_user_presence();
typedef struct pinUvAuthToken {
uint8_t *data;
size_t len;
bool in_use;
uint8_t permissions;
uint8_t rp_id_hash[32];
bool user_present;
bool user_verified;
} pinUvAuthToken_t;
extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut;
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign);
#endif //_FIDO_H

View File

@@ -20,9 +20,11 @@
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 | FILE_PERSISTENT, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key
{.fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH | FILE_PERSISTENT, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // End Entity Certificate Device
{.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_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_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 = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end
};
@@ -31,3 +33,5 @@ const file_t *file_last = &file_entries[sizeof(file_entries)/sizeof(file_t)-1];
file_t *ef_keydev = NULL;
file_t *ef_certdev = NULL;
file_t *ef_counter = NULL;
file_t *ef_pin = NULL;
file_t *ef_authtoken = NULL;

View File

@@ -23,9 +23,15 @@
#define EF_KEY_DEV 0xCC00
#define EF_EE_DEV 0xCE00
#define EF_COUNTER 0xC000
#define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
extern file_t *ef_keydev;
extern file_t *ef_certdev;
extern file_t *ef_counter;
extern file_t *ef_pin;
extern file_t *ef_authtoken;
#endif //_FILES_H_

311
src/fido/known_apps.c Normal file
View File

@@ -0,0 +1,311 @@
/*
* 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/>.
*/
#include <string.h>
#include "fido.h"
#include "ctap2_cbor.h"
static const known_app_t kapps[] = {
{
.rp_id_hash = (const uint8_t *)"\x96\x89\x78\xa2\x99\x53\xde\x52\xd3\xef\x0f\x0c\x71\xb7\xb7\xb6\xb1\xaf\x9f\x08\xe2\x57\x89\x6a\x8d\x81\x26\x91\x85\x30\x29\x3b",
.label = "aws.amazon.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xc3\x40\x8c\x04\x47\x88\xae\xa5\xb3\xdf\x30\x89\x52\xfd\x8c\xa3\xc7\x0e\x21\xfe\xf4\xf6\xc1\xc2\x37\x4c\xaa\x1d\xf9\xb2\x8d\xdd",
.label = "www.binance.com",
.use_sign_count = pfalse,
.use_self_attestation = ptrue,
},
{
.rp_id_hash = (const uint8_t *)"\x20\xf6\x61\xb1\x94\x0c\x34\x70\xac\x54\xfa\x2e\xb4\x99\x90\xfd\x33\xb5\x6d\xe8\xde\x60\x18\x70\xff\x02\xa8\x06\x0f\x3b\x7c\x58",
.label = "binance.com",
.use_sign_count = pfalse,
.use_self_attestation = ptrue,
},
{
.rp_id_hash = (const uint8_t *)"\x12\x74\x3b\x92\x12\x97\xb7\x7f\x11\x35\xe4\x1f\xde\xdd\x4a\x84\x6a\xfe\x82\xe1\xf3\x69\x32\xa9\x91\x2f\x3b\x0d\x8d\xfb\x7d\x0e",
//U2F key for Bitbucket
.label = "bitbucket.org",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x30\x2f\xd5\xb4\x49\x2a\x07\xb9\xfe\xbb\x30\xe7\x32\x69\xec\xa5\x01\x20\x5c\xcf\xe0\xc2\x0b\xf7\xb4\x72\xfa\x2d\x31\xe2\x1e\x63",
//U2F key for Bitfinex
.label = "www.bitfinex.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xa3\x4d\x30\x9f\xfa\x28\xc1\x24\x14\xb8\xba\x6c\x07\xee\x1e\xfa\xe1\xa8\x5e\x8a\x04\x61\x48\x59\xa6\x7c\x04\x93\xb6\x95\x61\x90",
//U2F key for Bitwarden
.label = "vault.bitwarden.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x19\x81\x5c\xb9\xa5\xfb\x25\xd8\x05\xde\xbd\x7b\x32\x53\x7e\xd5\x78\x63\x9b\x3e\xd1\x08\xec\x7c\x5b\xb9\xe8\xf0\xdf\xb1\x68\x73",
//WebAuthn key for Cloudflare
.label = "dash.cloudflare.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xe2\x7d\x61\xb4\xe9\x9d\xe0\xed\x98\x16\x3c\xb3\x8b\x7a\xf9\x33\xc6\x66\x5e\x55\x09\xe8\x49\x08\x37\x05\x58\x13\x77\x8e\x23\x6a",
//WebAuthn key for Coinbase
.label = "coinbase.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x68\x20\x19\x15\xd7\x4c\xb4\x2a\xf5\xb3\xcc\x5c\x95\xb9\x55\x3e\x3e\x3a\x83\xb4\xd2\xa9\x3b\x45\xfb\xad\xaa\x84\x69\xff\x8e\x6e",
//U2F key for Dashlane
.label = "www.dashlane.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xc5\x0f\x8a\x7b\x70\x8e\x92\xf8\x2e\x7a\x50\xe2\xbd\xc5\x5d\x8f\xd9\x1a\x22\xfe\x6b\x29\xc0\xcd\xf7\x80\x55\x30\x84\x2a\xf5\x81",
//U2F key for Dropbox
.label = "www.dropbox.com",
.use_sign_count = pfalse,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x82\xf4\xa8\xc9\x5f\xec\x94\xb2\x6b\xaf\x9e\x37\x25\x0e\x95\x63\xd9\xa3\x66\xc7\xbe\x26\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83",
//WebAuthn key for Dropbox
.label = "www.dropbox.com",
.use_sign_count = pfalse,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xf3\xe2\x04\x2f\x94\x60\x7d\xa0\xa9\xc1\xf3\xb9\x5e\x0d\x2f\x2b\xb2\xe0\x69\xc5\xbb\x4f\xa7\x64\xaf\xfa\x64\x7d\x84\x7b\x7e\xd6",
//U2F key for Duo
.label = "duosecurity.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x31\x19\x33\x28\xf8\xe2\x1d\xfb\x6c\x99\xf3\x22\xd2\x2d\x7b\x0b\x50\x87\x78\xe6\x4f\xfb\xba\x86\xe5\x22\x93\x37\x90\x31\xb8\x74",
//WebAuthn key for Facebook
.label = "facebook.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x69\x66\xab\xe3\x67\x4e\xa2\xf5\x30\x79\xeb\x71\x01\x97\x84\x8c\x9b\xe6\xf3\x63\x99\x2f\xd0\x29\xe9\x89\x84\x47\xcb\x9f\x00\x84",
//U2F key for FastMail
.label = "www.fastmail.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x3f\xcb\x82\x82\xb8\x46\x76\xeb\xee\x71\x40\xe3\x9e\xca\xe1\x6e\xeb\x19\x90\x64\xc7\xc7\xe4\x43\x2e\x28\xc9\xb5\x7e\x4b\x60\x39",
.label = "fastmail.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x9d\x61\x44\x2f\x5c\xe1\x33\xbd\x46\x54\x4f\xc4\x2f\x0a\x6d\x54\xc0\xde\xb8\x88\x40\xca\xc2\xb6\xae\xfa\x65\x14\xf8\x93\x49\xe9",
.label = "fedoraproject.org",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xa4\xe2\x2d\xca\xfe\xa7\xe9\x0e\x12\x89\x50\x11\x39\x89\xfc\x45\x97\x8d\xc9\xfb\x87\x76\x75\x60\x51\x6c\x1c\x69\xdf\xdf\xd1\x96",
.label = "gandi.net",
.use_sign_count = pfalse,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x54\xce\x65\x1e\xd7\x15\xb4\xaa\xa7\x55\xee\xce\xbd\x4e\xa0\x95\x08\x15\xb3\x34\xbd\x07\xd1\x09\x89\x3e\x96\x30\x18\xcd\xdb\xd9",
//WebAuthn key for Gandi
.label = "gandi.net",
.use_sign_count = pfalse,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x86\x06\xc1\x68\xe5\x1f\xc1\x31\xe5\x46\xad\x57\xa1\x9f\x32\x97\xb1\x1e\x0e\x5c\xe8\x3e\x8e\x89\x31\xb2\x85\x08\x11\xcf\xa8\x81",
//WebAuthn key for Gemini
.label = "gemini.com",
.use_sign_count = pfalse,
.use_self_attestation = ptrue,
},
{
.rp_id_hash = (const uint8_t *)"\x70\x61\x7d\xfe\xd0\x65\x86\x3a\xf4\x7c\x15\x55\x6c\x91\x79\x88\x80\x82\x8c\xc4\x07\xfd\xf7\x0a\xe8\x50\x11\x56\x94\x65\xa0\x75",
//U2F key for GitHub
.label = "github.com",
.use_sign_count = ptrue,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x3a\xeb\x00\x24\x60\x38\x1c\x6f\x25\x8e\x83\x95\xd3\x02\x6f\x57\x1f\x0d\x9a\x76\x48\x8d\xcd\x83\x76\x39\xb1\x3a\xed\x31\x65\x60",
//WebAuthn key for GitHub
.label = "github.com",
.use_sign_count = ptrue,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xe7\xbe\x96\xa5\x1b\xd0\x19\x2a\x72\x84\x0d\x2e\x59\x09\xf7\x2b\xa8\x2a\x2f\xe9\x3f\xaa\x62\x4f\x03\x39\x6b\x30\xe4\x94\xc8\x04",
//U2F key for GitLab
.label = "gitlab.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xa5\x46\x72\xb2\x22\xc4\xcf\x95\xe1\x51\xed\x8d\x4d\x3c\x76\x7a\x6c\xc3\x49\x43\x59\x43\x79\x4e\x88\x4f\x3d\x02\x3a\x82\x29\xfd",
//U2F key for Google
.label = "google.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xd4\xc9\xd9\x02\x73\x26\x27\x1a\x89\xce\x51\xfc\xaf\x32\x8e\xd6\x73\xf1\x7b\xe3\x34\x69\xff\x97\x9e\x8a\xb8\xdd\x50\x1e\x66\x4f",
//WebAuthn key for Google
.label = "google.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x53\xa1\x5b\xa4\x2a\x7c\x03\x25\xb8\xdb\xee\x28\x96\x34\xa4\x8f\x58\xae\xa3\x24\x66\x45\xd5\xff\x41\x8f\x9b\xb8\x81\x98\x85\xa9",
//U2F key for Keeper
.label = "keepersecurity.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xd6\x5f\x00\x5e\xf4\xde\xa9\x32\x0c\x99\x73\x05\x3c\x95\xff\x60\x20\x11\x5d\x5f\xec\x1b\x7f\xee\x41\xa5\x78\xe1\x8d\xf9\xca\x8c",
//U2F key for Keeper
.label = "keepersecurity.eu",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x3f\x37\x50\x85\x33\x2c\xac\x4f\xad\xf9\xe5\xdd\x28\xcd\x54\x69\x8f\xab\x98\x4b\x75\xd9\xc3\x6a\x07\x2c\xb1\x60\x77\x3f\x91\x52",
//WebAuthn key for Kraken
.label = "kraken.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xf8\x3f\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbb\x76\xf6\xb2\xf5\x60\x60\x17\x66\x72\x68\xe5\xb9\xc4\x5e",
//WebAuthn key for login.gov
.label = "secure.login.gov",
.use_sign_count = pfalse,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x35\x6c\x9e\xd4\xa0\x93\x21\xb9\x69\x5f\x1e\xaf\x91\x82\x03\xf1\xb5\x5f\x68\x9d\xa6\x1f\xbc\x96\x18\x4c\x15\x7d\xda\x68\x0c\x81",
//WebAuthn key for Microsoft
.label = "login.microsoft.com",
.use_sign_count = pfalse,
.use_self_attestation = pfalse,
},
{
.rp_id_hash = (const uint8_t *)"\xab\x2d\xaf\x07\x43\xde\x78\x2a\x70\x18\x9a\x0f\x5e\xfc\x30\x90\x2f\x92\x5b\x9f\x9a\x18\xc5\xd7\x14\x1b\x7b\x12\xf8\xa0\x10\x0c",
//WebAuthn key for mojeID
.label = "mojeid.cz",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x85\x71\x01\x36\x1b\x20\xa9\x54\x4c\xdb\x9b\xef\x65\x85\x8b\x6b\xac\x70\x13\x55\x0d\x8f\x84\xf7\xef\xee\x25\x2b\x96\xfa\x7c\x1e",
//WebAuthn key for Namecheap
.label = "www.namecheap.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x08\xb2\xa3\xd4\x19\x39\xaa\x31\x66\x84\x93\xcb\x36\xcd\xcc\x4f\x16\xc4\xd9\xb4\xc8\x23\x8b\x73\xc2\xf6\x72\xc0\x33\x00\x71\x97",
//U2F key for Slush Pool
.label = "slushpool.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x38\x80\x4f\x2e\xff\x74\xf2\x28\xb7\x41\x51\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf\x23\xfa\x14\x6b\x13\xa3\x76\x66\x31\x4f",
//U2F key for Slush Pool
.label = "slushpool.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x2a\xc6\xad\x09\xa6\xd0\x77\x2c\x44\xda\x73\xa6\x07\x2f\x9d\x24\x0f\xc6\x85\x4a\x70\xd7\x9c\x10\x24\xff\x7c\x75\x59\x59\x32\x92",
//U2F key for Stripe
.label = "stripe.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xfa\xbe\xec\xe3\x98\x2f\xad\x9d\xdc\xc9\x8f\x91\xbd\x2e\x75\xaf\xc7\xd1\xf4\xca\x54\x49\x29\xb2\xd0\xd0\x42\x12\xdf\xfa\x30\xfa",
//U2F key for Tutanota
.label = "tutanota.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x1b\x3c\x16\xdd\x2f\x7c\x46\xe2\xb4\xc2\x89\xdc\x16\x74\x6b\xcc\x60\xdf\xcf\x0f\xb8\x18\xe1\x32\x15\x52\x6e\x14\x08\xe7\xf4\x68",
//U2F key for u2f.bin.coffee
.label = "u2f.bin.coffee",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xa6\x42\xd2\x1b\x7c\x6d\x55\xe1\xce\x23\xc5\x39\x98\x28\xd2\xc7\x49\xbf\x6a\x6e\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xed\x53\x08\x8b",
//WebAuthn key for webauthn.bin.coffee
.label = "webauthn.bin.coffee",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\x74\xa6\xea\x92\x13\xc9\x9c\x2f\x74\xb2\x24\x92\xb3\x20\xcf\x40\x26\x2a\x94\xc1\xa9\x50\xa0\x39\x7f\x29\x25\x0b\x60\x84\x1e\xf0",
//WebAuthn key for WebAuthn.io
.label = "webauthn.io",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xf9\x5b\xc7\x38\x28\xee\x21\x0f\x9f\xd3\xbb\xe7\x2d\x97\x90\x80\x13\xb0\xa3\x75\x9e\x9a\xea\x3d\x0a\xe3\x18\x76\x6c\xd2\xe1\xad",
//WebAuthn key for WebAuthn.me
.label = "webauthn.me",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = (const uint8_t *)"\xc4\x6c\xef\x82\xad\x1b\x54\x64\x77\x59\x1d\x00\x8b\x08\x75\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94\x74\xbf\xea\x69\x69\x92\x5d\x03\xb7",
//WebAuthn key for demo.yubico.com
.label = "demo.yubico.com",
.use_sign_count = NULL,
.use_self_attestation = NULL,
},
{
.rp_id_hash = NULL,
.label = NULL,
.use_sign_count = NULL,
.use_self_attestation = NULL
}
};
const known_app_t *find_app_by_rp_id_hash(const uint8_t *rp_id_hash) {
for (const known_app_t *ka = &kapps[0]; ka->rp_id_hash != NULL; ka++) {
if (memcmp(rp_id_hash, ka->rp_id_hash, 32) == 0)
return ka;
}
return NULL;
}

View File

@@ -1,107 +0,0 @@
// Common U2F raw message format header - Review Draft
// 2014-10-08
// Editor: Jakob Ehrensvard, Yubico, jakob@yubico.com
#ifndef __U2F_H_INCLUDED__
#define __U2F_H_INCLUDED__
#ifdef _MSC_VER // Windows
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long int uint64_t;
#else
#include <stdint.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
// General constants
#define U2F_EC_KEY_SIZE 32 // EC key size in bytes
#define U2F_EC_POINT_SIZE ((U2F_EC_KEY_SIZE * 2) + 1) // Size of EC point
#define U2F_MAX_KH_SIZE 128 // Max size of key handle
#define U2F_MAX_ATT_CERT_SIZE 2048 // Max size of attestation certificate
#define U2F_MAX_EC_SIG_SIZE 72 // Max size of DER coded EC signature
#define U2F_CTR_SIZE 4 // Size of counter field
#define U2F_APPID_SIZE 32 // Size of application id
#define U2F_CHAL_SIZE 32 // Size of challenge
#define ENC_SIZE(x) ((x + 7) & 0xfff8)
// EC (uncompressed) point
#define U2F_POINT_UNCOMPRESSED 0x04 // Uncompressed point format
typedef struct {
uint8_t pointFormat; // Point type
uint8_t x[U2F_EC_KEY_SIZE]; // X-value
uint8_t y[U2F_EC_KEY_SIZE]; // Y-value
} U2F_EC_POINT;
// U2F native commands
#define U2F_REGISTER 0x01 // Registration command
#define U2F_AUTHENTICATE 0x02 // Authenticate/sign command
#define U2F_VERSION 0x03 // Read version string command
#define U2F_VENDOR_FIRST 0x40 // First vendor defined command
#define U2F_VENDOR_LAST 0xbf // Last vendor defined command
// U2F_CMD_REGISTER command defines
#define U2F_REGISTER_ID 0x05 // Version 2 registration identifier
#define U2F_REGISTER_HASH_ID 0x00 // Version 2 hash identintifier
typedef struct {
uint8_t chal[U2F_CHAL_SIZE]; // Challenge
uint8_t appId[U2F_APPID_SIZE]; // Application id
} U2F_REGISTER_REQ;
typedef struct {
uint8_t registerId; // Registration identifier (U2F_REGISTER_ID_V2)
U2F_EC_POINT pubKey; // Generated public key
uint8_t keyHandleLen; // Length of key handle
uint8_t keyHandleCertSig[
U2F_MAX_KH_SIZE + // Key handle
U2F_MAX_ATT_CERT_SIZE + // Attestation certificate
U2F_MAX_EC_SIG_SIZE]; // Registration signature
} U2F_REGISTER_RESP;
// U2F_CMD_AUTHENTICATE command defines
// Authentication control byte
#define U2F_AUTH_ENFORCE 0x03 // Enforce user presence and sign
#define U2F_AUTH_CHECK_ONLY 0x07 // Check only
#define U2F_AUTH_FLAG_TUP 0x01 // Test of user presence set
typedef struct {
uint8_t chal[U2F_CHAL_SIZE]; // Challenge
uint8_t appId[U2F_APPID_SIZE]; // Application id
uint8_t keyHandleLen; // Length of key handle
uint8_t keyHandle[U2F_MAX_KH_SIZE]; // Key handle
} U2F_AUTHENTICATE_REQ;
typedef struct {
uint8_t flags; // U2F_AUTH_FLAG_ values
uint8_t ctr[U2F_CTR_SIZE]; // Counter field (big-endian)
uint8_t sig[U2F_MAX_EC_SIG_SIZE]; // Signature
} U2F_AUTHENTICATE_RESP;
// Command status responses
#define U2F_SW_NO_ERROR 0x9000 // SW_NO_ERROR
#define U2F_SW_WRONG_DATA 0x6A80 // SW_WRONG_DATA
#define U2F_SW_CONDITIONS_NOT_SATISFIED 0x6985 // SW_CONDITIONS_NOT_SATISFIED
#define U2F_SW_COMMAND_NOT_ALLOWED 0x6986 // SW_COMMAND_NOT_ALLOWED
#define U2F_SW_INS_NOT_SUPPORTED 0x6D00 // SW_INS_NOT_SUPPORTED
#ifdef __cplusplus
}
#endif
#endif // __U2F_H_INCLUDED__

View File

@@ -18,7 +18,7 @@
#ifndef __VERSION_H_
#define __VERSION_H_
#define PICO_FIDO_VERSION 0x0102
#define PICO_FIDO_VERSION 0x0202
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)

395
tests/conftest.py Normal file
View File

@@ -0,0 +1,395 @@
from http import client
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError, _Ctap1ClientBackend
from fido2.attestation import FidoU2FAttestation
from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server
from fido2.ctap import CtapError
from fido2.webauthn import CollectedClientData, AttestedCredentialData
from utils import *
from fido2.cose import ES256
import sys
import pytest
import os
import struct
DEFAULT_PIN='12345678'
class Packet(object):
def __init__(self, data):
self.data = data
def ToWireFormat(
self,
):
return self.data
@staticmethod
def FromWireFormat(pkt_size, data):
return Packet(data)
class CliInteraction(UserInteraction):
def prompt_up(self):
print("\nTouch your authenticator device now...\n")
def request_pin(self, permissions, rd_id):
return DEFAULT_PIN
def request_uv(self, permissions, rd_id):
print("User Verification required.")
return True
class DeviceSelectCredential:
def __init__(self, number):
pass
def __call__(self, status):
pass
class Device():
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 __set_client(self, origin, user_interaction, uv):
self.__uv = uv
self.__dev = None
self.__origin = origin
self.__user_interaction = user_interaction
# Locate a device
self.__dev = next(CtapHidDevice.list_devices(), None)
self.dev = self.__dev
if self.__dev is not None:
print("Use USB HID channel.")
else:
try:
from fido2.pcsc import CtapPcscDevice
self.__dev = next(CtapPcscDevice.list_devices(), None)
print("Use NFC channel.")
except Exception as e:
print("NFC channel search error:", e)
if not self.__dev:
print("No FIDO device found")
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)
# 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._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
self.ctap1 = self.__client1._backend.ctap1
def __set_server(self, rp, attestation):
self.__rp = rp
self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
def client(self):
return self.__client
def user(self, user=None):
if (self.__user is None):
self.__user = {"id": b"user_id", "name": "A. User"}
if (user is not None):
self.__user = user
return self.__user
def rp(self, rp=None):
if (self.__rp is None):
self.__rp = {"id": "example.com", "name": "Example RP"}
if (rp is not None):
self.__rp = rp
return self.__rp
def send_data(self, cmd, data, timeout = 1.0, on_keepalive = None):
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
with Timeout(timeout) as event:
event.is_set()
return self.dev.call(cmd, data, event, on_keepalive = on_keepalive)
def cid(self):
return self.dev._channel_id
def set_cid(self, cid):
self.dev._channel_id = int.from_bytes(cid, 'big')
def recv_raw(self):
with Timeout(1.0):
r = self.dev._connection.read_packet()
return r[4], r[7:]
def send_raw(self, data, cid=None):
if cid is None:
cid = self.dev._channel_id.to_bytes(4, 'big')
elif not isinstance(cid, bytes):
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
if not isinstance(data, bytes):
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
data = cid + data
l = len(data)
if l != 64:
pad = "\x00" * (64 - l)
pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad])
data = data + pad
data = bytes(data)
assert len(data) == 64
self.dev._connection.write_packet(data)
def reset(self):
print("Resetting Authenticator...")
try:
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
except CtapError:
# Some authenticators need a power cycle
print("Need to power cycle authentictor to reset..")
self.reboot()
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
def reboot(self):
print("Please reboot authenticator and hit enter")
input()
self.__set_client(self.__origin, self.__user_interaction, self.__uv)
self.__set_server(rp=self.__rp, attestation=self.__attestation)
def MC(self, client_data_hash=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None, enterprise_attestation=None):
client_data_hash = client_data_hash if client_data_hash is not Ellipsis else os.urandom(32)
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
att_obj = self.__client._backend.ctap2.make_credential(
client_data_hash=client_data_hash,
rp=rp,
user=user,
key_params=key_params,
exclude_list=exclude_list,
extensions=extensions,
options=options,
pin_uv_param=pin_uv_param,
pin_uv_protocol=pin_uv_protocol,
enterprise_attestation=enterprise_attestation
)
return {'res':att_obj,'req':{'client_data_hash':client_data_hash,
'rp':rp,
'user':user,
'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)
)
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
if (ctap1 is True):
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,
extensions=extensions,
rk=rk,
user_verification=user_verification,
enterprise_attestation=enterprise_attestation,
event=event
)
return {'res':result,'req':{'client_data':client_data,
'rp':rp,
'user':user,
'key_params':key_params}}
def try_make_credential(self, options=None):
if (options is None):
options, _ = self.__server.register_begin(
self.user(), user_verification=self.__uv, authenticator_attachment="cross-platform"
)
try:
result = self.__client.make_credential(options["publicKey"])
except ClientError as e:
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN)
result = self.__client.make_credential(options["publicKey"])
return result
def register(self, uv=None):
# Prepare parameters for makeCredential
create_options, state = self.__server.register_begin(
self.user(), user_verification=uv or self.__uv, authenticator_attachment="cross-platform"
)
# Create a credential
result = self.try_make_credential(create_options)
# Complete registration
auth_data = self.__server.register_complete(
state, result.client_data, result.attestation_object
)
credentials = [auth_data.credential_data]
print("New credential created!")
print("CLIENT DATA:", result.client_data)
print("ATTESTATION OBJECT:", result.attestation_object)
print()
print("CREDENTIAL DATA:", auth_data.credential_data)
return (result, auth_data)
def authenticate(self, credentials):
# Prepare parameters for getAssertion
request_options, state = self.__server.authenticate_begin(credentials, user_verification=self.__uv)
# Authenticate the credential
result = self.__client.get_assertion(request_options["publicKey"])
# Only one cred in allowCredentials, only one response.
result = result.get_response(0)
# Complete authenticator
self.__server.authenticate_complete(
state,
credentials,
result.credential_id,
result.client_data,
result.authenticator_data,
result.signature,
)
print("Credential authenticated!")
print("CLIENT DATA:", result.client_data)
print()
print("AUTH DATA:", result.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']
client_data_hash = client_data_hash if client_data_hash is not Ellipsis else os.urandom(32)
att_obj = self.__client._backend.ctap2.get_assertion(
rp_id=rp_id,
client_data_hash=client_data_hash,
allow_list=allow_list,
extensions=extensions,
options=options,
pin_uv_param=pin_uv_param,
pin_uv_protocol=pin_uv_protocol
)
return {'res':att_obj,'req':{'rp_id':rp_id,
'client_data_hash':client_data_hash}}
def GNA(self):
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.CREATE, origin=self.__origin, challenge=os.urandom(32)
)
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
try:
result = client._backend.do_get_assertion(
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
except ClientError as e:
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN)
result = client._backend.do_get_assertion(
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
else:
raise
return {'res':result,'req':{'client_data':client_data,
'rp_id':rp_id}}
@pytest.fixture(scope="session")
def device():
dev = Device()
return dev
@pytest.fixture(scope="session")
def info(device):
return device.client()._backend.info
@pytest.fixture(scope="session")
def MCRes(device, *args):
return device.doMC(*args)
@pytest.fixture(scope="session")
def resetdevice(device):
device.reset()
return device
@pytest.fixture(scope="session")
def GARes(device, MCRes, *args):
res = device.doGA(allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
assertions = res['res'].get_assertions()
for a in assertions:
verify(MCRes['res'].attestation_object, a, res['req']['client_data'].hash)
return res
@pytest.fixture(scope="session")
def MCRes_DC(device, *args):
return device.doMC(rk=True, *args)
@pytest.fixture(scope="session")
def GARes_DC(device, MCRes_DC, *args):
res = device.GA(allow_list=[
{"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
verify(MCRes_DC['res'].attestation_object, res['res'], res['req']['client_data_hash'])
return res
@pytest.fixture(scope="module")
def RegRes(resetdevice, *args):
res = resetdevice.doMC(ctap1=True, *args)
att = FidoU2FAttestation()
att.verify(res['res'].attestation_object.att_stmt, res['res'].attestation_object.auth_data, res['req']['client_data'].hash)
return res
@pytest.fixture(scope="class")
def AuthRes(device, RegRes, *args):
res = device.doGA(ctap1=True, allow_list=[
{"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)
return aut_data
@pytest.fixture(scope="class")
def client_pin(resetdevice):
return ClientPin(resetdevice.client()._backend.ctap2)

View File

@@ -0,0 +1,194 @@
from fido2.utils import sha256
from fido2.client import CtapError
import pytest
def test_authenticate(device):
device.reset()
REGRes,AUTData = device.register()
credentials = [AUTData.credential_data]
AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes):
assert len(GARes['res'].get_response(0).authenticator_data) == 37
def test_Check_that_AT_flag_is_not_set(GARes):
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
assert res['res'].user == None
assert res['res'].number_of_credentials == None
def test_empty_allowList(device):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[])
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
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_registrations = []
rp2_registrations = []
rp1_assertions = []
rp2_assertions = []
l1 = 4
for i in range(0, l1):
res = device.doMC(rp=rp1)['res'].attestation_object
rp1_registrations.append(res)
allow_list.append({
"id": res.auth_data.credential_data.credential_id[:],
"type": "public-key",
})
l2 = 6
for i in range(0, l2):
res = device.doMC(rp=rp2)['res'].attestation_object
rp2_registrations.append(res)
allow_list.append({
"id": res.auth_data.credential_data.credential_id[:],
"type": "public-key",
})
# CTAP 2.1: If allowlist is passed, only one (any) applicable
# credential signs, and numberOfCredentials = None is returned.
# <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#:~:text=If%20the%20allowList%20parameter%20is%20present%3A,Go%20to%20Step%2013>
#
# CTAP 2.0: Expects the authenticator to return the total number
# even when allowlist is passed (and hence keep the credential IDs
# cached.
# Should authenticate to all credentials matching rp1
rp1_assertions = device.doGA(rp_id=rp1['id'], allow_list=allow_list)['res'].get_assertions()
# Should authenticate to all credentials matching rp2
rp2_assertions = device.doGA(rp_id=rp2['id'], allow_list=allow_list)['res'].get_assertions()
counts = (
len(rp1_assertions),
len(rp2_assertions)
)
assert counts in [(None, None), (l1, l2)]
def test_corrupt_credId(device, MCRes):
# apply bit flip
badid = list(MCRes['res'].attestation_object.auth_data.credential_data.credential_id[:])
badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1
badid = bytes(badid)
allow_list = [{"id": badid, "type": "public-key"}]
with pytest.raises(CtapError) as e:
device.doGA(allow_list=allow_list)['res']
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_mismatched_rp(device, GARes):
rp_id = device.rp()['id']
rp_id += ".com"
with pytest.raises(CtapError) as e:
device.doGA(rp_id=rp_id)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_missing_rp(device):
with pytest.raises(CtapError) as e:
device.doGA(rp_id=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_rp(device):
with pytest.raises(CtapError) as e:
device.doGA(rp_id={"id": {"type": "wrong"}})
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
device.GA(client_data_hash=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_cdh(device):
with pytest.raises(CtapError) as e:
device.GA(client_data_hash={"type": "wrong"})
def test_bad_allow_list(device):
with pytest.raises(CtapError) as e:
device.doGA(allow_list={"type": "wrong"})
def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=["wrong"] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_unknown_option(device, MCRes):
device.GA(options={"unknown": True}, allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
def test_option_uv(device, info, GARes):
if "uv" in info.options:
if info.options["uv"]:
res = device.doGA(options={"uv": True})['res']
assert res.auth_data.flags & (1 << 2)
def test_option_up(device, info, GARes):
if "up" in info.options:
if info.options["up"]:
res = device.doGA(options={"up": True})['res']
assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes):
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],
)
def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"id": b"1234"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_allow_list_field_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": b"public-key", "id": b"1234"}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(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):
res = device.GA(options={"up": False}, allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
def test_credential_resets(device, MCRes, GARes):
device.reset()
with pytest.raises(CtapError) as e:
new_auth = device.doGA()
assert e.value.code == CtapError.ERR.NO_CREDENTIALS

View File

@@ -0,0 +1,358 @@
import pytest
import time
import random
from fido2.ctap import CtapError
from fido2.ctap2 import CredentialManagement
from fido2.utils import sha256, hmac_sha256
from fido2.ctap2.pin import PinProtocolV2
from binascii import hexlify
from utils import generate_random_user
PIN = "12345678"
@pytest.fixture(params=[PIN], scope = 'function')
def PinToken(request, device, client_pin):
#device.reboot()
device.reset()
pin = request.param
client_pin.set_pin(pin)
return client_pin.get_pin_token(pin)
@pytest.fixture(scope = 'function')
def MC_RK_Res(device, PinToken):
rp = {"id": "ssh:", "name": "Bate Goiko"}
device.doMC(rp=rp, rk=True)
rp = {"id": "xakcop.com", "name": "John Doe"}
device.doMC(rp=rp, rk=True)
@pytest.fixture(scope = 'function')
def CredMgmt(device, PinToken):
pin_protocol = PinProtocolV2()
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, PinToken)
def _test_enumeration(CredMgmt, rp_map):
"Enumerate credentials using BFS"
res = CredMgmt.enumerate_rps()
assert len(rp_map.keys()) == len(res)
for rp in res:
creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"]))
assert len(creds) == rp_map[rp[3]["id"].decode()]
def _test_enumeration_interleaved(CredMgmt, rp_map):
"Enumerate credentials using DFS"
first_rp = CredMgmt.enumerate_rps_begin()
assert len(rp_map.keys()) == first_rp[CredentialManagement.RESULT.TOTAL_RPS]
rk_count = 1
first_rk = CredMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"]))
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
c = CredMgmt.enumerate_creds_next()
rk_count += 1
assert rk_count == rp_map[first_rp[3]["id"].decode()]
for i in range(1, first_rp[CredentialManagement.RESULT.TOTAL_RPS]):
next_rp = CredMgmt.enumerate_rps_next()
rk_count = 1
first_rk = CredMgmt.enumerate_creds_begin(
sha256(next_rp[3]["id"])
)
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
c = CredMgmt.enumerate_creds_next()
rk_count += 1
assert rk_count == rp_map[next_rp[3]["id"].decode()]
def CredMgmtWrongPinAuth(device, pin_token):
pin_protocol = PinProtocolV2()
wrong_pt = bytearray(pin_token)
wrong_pt[0] = (wrong_pt[0] + 1) % 256
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, bytes(wrong_pt))
def assert_cred_response_has_all_fields(cred_res):
for i in (
CredentialManagement.RESULT.USER,
CredentialManagement.RESULT.CREDENTIAL_ID,
CredentialManagement.RESULT.PUBLIC_KEY,
CredentialManagement.RESULT.TOTAL_CREDENTIALS,
CredentialManagement.RESULT.CRED_PROTECT,
):
assert i in cred_res
def test_get_info(info):
assert "credMgmt" in info.options
assert info.options["credMgmt"] == True
assert 0x7 in info
assert info[0x7] > 1
assert 0x8 in info
assert info[0x8] > 1
def test_get_metadata(CredMgmt, MC_RK_Res):
metadata = CredMgmt.get_metadata()
assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2
assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48
def test_enumerate_rps(CredMgmt, MC_RK_Res):
res = CredMgmt.enumerate_rps()
assert len(res) == 2
assert res[0][CredentialManagement.RESULT.RP]["id"] == b"ssh:"
assert res[0][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"ssh:")
# Solo doesn't store rpId with the exception of "ssh:"
assert res[1][CredentialManagement.RESULT.RP]["id"] == b"xakcop.com"
assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com")
def test_enumarate_creds(CredMgmt, MC_RK_Res):
res = CredMgmt.enumerate_creds(sha256(b"ssh:"))
assert len(res) == 1
assert_cred_response_has_all_fields(res[0])
res = CredMgmt.enumerate_creds(sha256(b"xakcop.com"))
assert len(res) == 1
assert_cred_response_has_all_fields(res[0])
res = CredMgmt.enumerate_creds(sha256(b"missing.com"))
assert not res
def test_get_metadata_wrong_pinauth(device, MC_RK_Res, PinToken):
cmd = lambda credMgmt: credMgmt.get_metadata()
_test_wrong_pinauth(device, cmd, PinToken)
def test_rpbegin_wrong_pinauth(device, MC_RK_Res, PinToken):
cmd = lambda credMgmt: credMgmt.enumerate_rps_begin()
_test_wrong_pinauth(device, cmd, PinToken)
def test_rkbegin_wrong_pinauth(device, MC_RK_Res, PinToken):
cmd = lambda credMgmt: credMgmt.enumerate_creds_begin(sha256(b"ssh:"))
_test_wrong_pinauth(device, cmd, PinToken)
def test_rpnext_without_rpbegin(device, CredMgmt, MC_RK_Res):
CredMgmt.enumerate_creds_begin(sha256(b"ssh:"))
with pytest.raises(CtapError) as e:
CredMgmt.enumerate_rps_next()
assert e.value.code == CtapError.ERR.NOT_ALLOWED
def test_rknext_without_rkbegin(device, CredMgmt, MC_RK_Res):
CredMgmt.enumerate_rps_begin()
with pytest.raises(CtapError) as e:
CredMgmt.enumerate_creds_next()
assert e.value.code == CtapError.ERR.NOT_ALLOWED
def test_delete(device, PinToken, CredMgmt):
# create a new RK
rp = {"id": "example_3.com", "name": "John Doe 2"}
reg = device.doMC(rp=rp, rk=True)['res'].attestation_object
# make sure it works
auth = device.doGA(rp_id=rp['id'])
# get the ID from enumeration
creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
for cred in creds:
if cred[7]["id"] == reg.auth_data.credential_data.credential_id:
break
# delete it
cred = {"id": cred[7]["id"], "type": "public-key"}
CredMgmt.delete_cred(cred)
# make sure it doesn't work
with pytest.raises(CtapError) as e:
auth = device.doGA(rp_id=rp['id'])
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_add_delete(device, PinToken, CredMgmt):
""" Delete a credential in the 'middle' and ensure other credentials are not affected. """
rp = {"id": "example_4.com", "name": "John Doe 3"}
regs = []
# create 3 new RK's
for i in range(0, 3):
reg = device.doMC(rp=rp, rk=True, user=generate_random_user())['res'].attestation_object
regs.append(reg)
# Check they all enumerate
res = CredMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash)
assert len(res) == 3
# delete the middle one
creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
for cred in creds:
if cred[7]["id"] == regs[1].auth_data.credential_data.credential_id:
break
assert cred[7]["id"] == regs[1].auth_data.credential_data.credential_id
cred = {"id": cred[7]["id"], "type": "public-key"}
CredMgmt.delete_cred(cred)
# Check one less enumerates
res = CredMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash)
assert len(res) == 2
def test_multiple_creds_per_multiple_rps(
device, PinToken, CredMgmt, MC_RK_Res
):
res = CredMgmt.enumerate_rps()
assert len(res) == 2
new_rps = [
{"id": "new_example_1.com", "name": "Example-3-creds"},
{"id": "new_example_2.com", "name": "Example-3-creds"},
{"id": "new_example_3.com", "name": "Example-3-creds"},
]
# create 3 new credentials per RP
for rp in new_rps:
for i in range(0, 3):
reg = device.doMC(rp=rp, rk=True, user=generate_random_user())
res = CredMgmt.enumerate_rps()
assert len(res) == 5
for rp in res:
if rp[3]["id"][:12] == "new_example_":
creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8")))
assert len(creds) == 3
@pytest.mark.parametrize(
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
)
def test_multiple_enumeration(
device, PinToken, MC_RK_Res, CredMgmt, enumeration_test
):
""" Test enumerate still works after different commands """
res = CredMgmt.enumerate_rps()
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
enumeration_test(CredMgmt, expected_enumeration)
new_rps = [
{"id": "example-2.com", "name": "Example-2-creds", "count": 2},
{"id": "example-1.com", "name": "Example-1-creds", "count": 1},
{"id": "example-5.com", "name": "Example-5-creds", "count": 5},
]
# create 3 new credentials per RP
for rp in new_rps:
for i in range(0, rp["count"]):
reg = device.doMC(rp={"id": rp["id"], "name": rp["name"]}, rk=True, user=generate_random_user())
# Now expect creds from this RP
expected_enumeration[rp["id"]] = rp["count"]
enumeration_test(CredMgmt, expected_enumeration)
enumeration_test(CredMgmt, expected_enumeration)
metadata = CredMgmt.get_metadata()
enumeration_test(CredMgmt, expected_enumeration)
enumeration_test(CredMgmt, expected_enumeration)
@pytest.mark.parametrize(
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
)
def test_multiple_enumeration_with_deletions(
device, PinToken, MC_RK_Res, CredMgmt, enumeration_test
):
""" Create each credential in random order. Test enumerate still works after randomly deleting each credential"""
res = CredMgmt.enumerate_rps()
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
enumeration_test(CredMgmt, expected_enumeration)
new_rps = [
{"id": "example-1.com", "name": "Example-1-creds"},
{"id": "example-2.com", "name": "Example-2-creds"},
{"id": "example-2.com", "name": "Example-2-creds"},
{"id": "example-3.com", "name": "Example-3-creds"},
{"id": "example-3.com", "name": "Example-3-creds"},
{"id": "example-3.com", "name": "Example-3-creds"},
]
random.shuffle(new_rps)
# create new credentials per RP in random order
for rp in new_rps:
device.doMC(rp=rp, rk=True, user=generate_random_user())
if rp["id"] not in expected_enumeration:
expected_enumeration[rp["id"]] = 1
else:
expected_enumeration[rp["id"]] += 1
enumeration_test(CredMgmt, expected_enumeration)
total_creds = len(new_rps)
while total_creds != 0:
rp = random.choice(list(expected_enumeration.keys()))
num = expected_enumeration[rp]
index = 0 if num == 1 else random.randint(0, num - 1)
cred = CredMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index]
# print('Delete %d index (%d total) cred of %s' % (index, expected_enumeration[rp], rp))
CredMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"})
expected_enumeration[rp] -= 1
if expected_enumeration[rp] == 0:
del expected_enumeration[rp]
if len(list(expected_enumeration.keys())) == 0:
break
enumeration_test(CredMgmt, expected_enumeration)
def _test_wrong_pinauth(device, cmd, PinToken):
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
for i in range(2):
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
#device.reboot()
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
for i in range(2):
time.sleep(0.2)
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
#device.reboot()
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
for i in range(1):
time.sleep(0.2)
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
with pytest.raises(CtapError) as e:
cmd(credMgmt)
assert e.value.code == CtapError.ERR.PIN_BLOCKED

View File

@@ -0,0 +1,152 @@
import pytest
from fido2.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError
class CredProtect:
UserVerificationOptional = 1
UserVerificationOptionalWithCredentialId = 2
UserVerificationRequired = 3
@pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice):
res = resetdevice.doMC(rk=True, 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
return res
@pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
return res
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(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(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(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)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
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.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
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)
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
# works
res = device.doGA()['res'].get_assertions()[0]
# If there's only one credential, this is None
assert res.number_of_credentials == None
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 = [res1]
if res1.number_of_credentials == 2:
res2 = device.GNA()['res']
results.append(res2)
# 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(resetdevice, MCCredProtectOptional
):
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
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",
},
]
pin = "12345678"
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)

View File

@@ -0,0 +1,23 @@
# Test U2F register works with FIDO2 auth
def test_ctap1_register(RegRes):
pass
def test_ctap1_authenticate(RegRes, AuthRes):
pass
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
# Test FIDO2 register works with U2F auth
def test_ctap1_authenticate_attestation(MCRes, device):
key_handle = MCRes['res'].attestation_object.auth_data.credential_data.credential_id
if len(key_handle) <= 255:
res = device.doGA(ctap1=True, allow_list=[
{"id": key_handle, "type": "public-key"}
])
else:
print("ctap2 credId is longer than 255 bytes, cannot use with U2F.")

View File

@@ -0,0 +1,239 @@
from fido2.client import CtapError
import pytest
import random
from utils import *
@pytest.mark.parametrize("do_reboot", [False, True])
def test_user_info_returned_when_using_allowlist(device, MCRes_DC, GARes_DC, do_reboot):
assert "id" in GARes_DC['res'].user.keys()
allow_list = [
{
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
if do_reboot:
device.reboot()
ga_res = device.GA(allow_list=allow_list)['res']
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
assert "id" in GARes_DC['res'].user.keys()
allow_list = [
{
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
ga_res = device.GA(allow_list=allow_list)['res']
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
device.reset()
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
def test_resident_key_auth(MCRes_DC, GARes_DC):
pass
def test_user_info_returned(device, MCRes_DC, GARes_DC):
assert "id" in GARes_DC['res'].user.keys()
assert (
MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id
== GARes_DC['res'].credential["id"]
)
assert MCRes_DC["req"]["user"]["id"] == GARes_DC['res'].user["id"]
if not GARes_DC['res'].number_of_credentials:
assert "id" in GARes_DC['res'].user.keys() and len(GARes_DC['res'].user.keys()) == 1
else:
assert MCRes_DC["req"]["user"] == GARes_DC['res'].user
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"}
for i in range(0, 3):
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
regs.append(res)
# time.sleep(2)
res = device.doGA(rp_id=rp['id'])['res']
auths = res.get_assertions()
assert len(regs) == 3
assert len(regs) == len(auths)
for x in auths:
for y in ("name", "displayName", "id"):
if y not in x.user.keys():
print("FAIL: %s was not in user: " % y, x.user)
def test_rk_maximum_size_nodisplay(device):
"""
Check the lengths of the fields according to the FIDO2 spec
https://github.com/solokeys/solo/issues/158#issue-426613303
https://www.w3.org/TR/webauthn/#dom-publickeycredentialuserentity-displayname
"""
device.reset()
user_max = generate_user_maximum()
resMC = device.doMC(user=user_max, rk=True)
resGA = device.doGA()['res']
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]
def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
"""
Test maximum returned capacity of the RK for the given RP
"""
# 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:
RK_CAPACITY_PER_RP = 19
users = []
def get_user():
user = generate_user_maximum()
users.append(user)
return user
# Use unique RP to not collide with other credentials from other tests.
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
# res = device.sendGA(*req.toGA())
current_credentials_count = 0
auths = []
regs = [MCRes_DC]
RK_to_generate = RK_CAPACITY_PER_RP - current_credentials_count
for i in range(RK_to_generate):
res = device.doMC(user=get_user(), rp=rp, rk=True)['res'].attestation_object
regs.append(res)
res = device.GA(rp_id = rp['id'])['res']
assert res.number_of_credentials == RK_CAPACITY_PER_RP
auths.append(res)
for i in range(RK_CAPACITY_PER_RP - 1):
auths.append(device.GNA())
with pytest.raises(CtapError) as e:
device.GNA()
auths = auths[::-1][-RK_to_generate:]
regs = regs[-RK_to_generate:]
users = users[-RK_to_generate:]
assert len(auths) == len(users)
for x, u in zip(auths, users):
for y in ("name", "displayName", "id"):
assert y in x.user.keys()
assert x.user[y] == u[y]
assert len(auths) == len(regs)
def test_rk_with_allowlist_of_different_rp(resetdevice):
"""
Test that a rk credential is not found when using an allowList item for a different RP
"""
rk_rp = {"id": "rk-cred.org", "name": "Example"}
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
server_rp = {"id": "server-cred.com", "name": "Example"}
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
allow_list_with_different_rp_cred = [
{
"id": server_res.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e:
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
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.
"""
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user()
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
# Should overwrite the first credential.
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
# If there's only one credential, this is None
assert ga_res.number_of_credentials == None
def test_larger_icon_than_128(device):
"""
Test it works if we give an icon value larger than 128 bytes
"""
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
device.doMC(rp = rp, rk=True, user = user)
def test_returned_credential(device):
"""
Test that when two rk credentials put in allow_list,
only 1 will get returned.
"""
device.reset()
regs = []
allow_list = []
for i in range(0, 2):
res = device.doMC(rk=True, user = {
"id": b'123456' + bytes([i]), "name": f'Test User {i}', "displayName": f'Test User display {i}'
})['res'].attestation_object
regs.append(res)
allow_list.append({"id": res.auth_data.credential_data.credential_id[:], "type": "public-key"})
ga_res = device.GA(allow_list=allow_list,options={'up':False})['res']
# No other credentials should be returned
with pytest.raises(CtapError) as e:
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

View File

@@ -0,0 +1,27 @@
import pytest
from fido2.client import CtapError
def test_getinfo(device):
pass
def test_get_info_version(info):
assert "FIDO_2_0" in info.versions
def test_Check_pin_protocols_field(info):
if len(info.pin_uv_protocols):
assert sum(info.pin_uv_protocols) > 0
def test_Check_options_field(info):
for x in info.options:
assert info.options[x] in [True, False]
def test_Check_up_option(device, info):
if "up" not in info.options or info.options["up"]:
with pytest.raises(CtapError) as e:
device.MC(options={"up": True})
assert e.value.code == CtapError.ERR.INVALID_OPTION

253
tests/pico-fido/test_hid.py Normal file
View File

@@ -0,0 +1,253 @@
import os
import socket
import time
from binascii import hexlify, unhexlify
import pytest
from fido2.ctap import CtapError
from fido2.hid import CTAPHID
from utils import Timeout
class TestHID(object):
def test_long_ping(self, device):
amt = 1000
pingdata = os.urandom(amt)
t1 = time.time() * 1000
r = device.send_data(CTAPHID.PING, pingdata)
t2 = time.time() * 1000
delt = t2 - t1
assert not (delt > 555 * (amt / 1000))
assert r == pingdata
def test_init(self, device, check_timeouts=False):
if check_timeouts:
with pytest.raises(socket.timeout):
cmd, resp = self.recv_raw()
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
r = device.send_data(CTAPHID.INIT, payload)
print(r)
assert r[:8] == payload
def test_ping(self, device):
pingdata = os.urandom(100)
r = device.send_data(CTAPHID.PING, pingdata)
assert r == pingdata
def test_wink(self, device):
r = device.send_data(CTAPHID.WINK, "")
def test_cbor_no_payload(self, device):
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
r = device.send_data(CTAPHID.INIT, payload)
capabilities = r[16]
if (capabilities ^ 0x04) != 0:
print("Implements CBOR.")
with pytest.raises(CtapError) as e:
r = device.send_data(CTAPHID.CBOR, "")
assert e.value.code == CtapError.ERR.INVALID_LENGTH
else:
print("CBOR is not implemented.")
def test_no_data_in_u2f_msg(self, device):
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
r = device.send_data(CTAPHID.INIT, payload)
capabilities = r[16]
if (capabilities ^ 0x08) == 0:
print("U2F implemented.")
with pytest.raises(CtapError) as e:
r = device.send_data(CTAPHID.MSG, "")
print(hexlify(r))
assert e.value.code == CtapError.ERR.INVALID_LENGTH
else:
print("U2F not implemented.")
def test_invalid_hid_cmd(self, device):
r = device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
with pytest.raises(CtapError) as e:
r = device.send_data(0x66, "")
assert e.value.code == CtapError.ERR.INVALID_COMMAND
def test_oversize_packet(self, device):
device.send_raw("\x81\x1d\xba\x00")
cmd, resp = device.recv_raw()
assert resp[0] == CtapError.ERR.INVALID_LENGTH
def test_skip_sequence_number(self, device):
r = device.send_data(CTAPHID.PING, "\x44" * 200)
device.send_raw("\x81\x04\x90")
device.send_raw("\x00")
device.send_raw("\x01")
# skip 2
device.send_raw("\x03")
cmd, resp = device.recv_raw()
assert resp[0] == CtapError.ERR.INVALID_SEQ
def test_resync_and_ping(self, device):
r = device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
pingdata = os.urandom(100)
r = device.send_data(CTAPHID.PING, pingdata)
if r != pingdata:
raise ValueError("Ping data not echo'd")
def test_ping_abort(self, device):
device.send_raw("\x81\x04\x00")
device.send_raw("\x00")
device.send_raw("\x01")
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
def test_ping_abort_from_different_cid(self, device, check_timeouts=False):
oldcid = device.dev._channel_id
newcid = int.from_bytes(b"\x11\x22\x33\x44", 'big')
device.send_raw("\x81\x10\x00")
device.send_raw("\x00")
device.send_raw("\x01")
device.dev._channel_id = newcid
device.send_raw(
"\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88"
) # init from different cid
print("wait for init response")
cmd, r = device.recv_raw() # init response
assert cmd == 0x86
device.dev._channel_id = oldcid
if check_timeouts:
# print('wait for timeout')
cmd, r = device.recv_raw() # timeout response
assert cmd == 0xBF
def test_timeout(self, device):
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
t1 = time.time() * 1000
device.send_raw("\x81\x04\x00")
device.send_raw("\x00")
device.send_raw("\x01")
cmd, r = device.recv_raw() # timeout response
t2 = time.time() * 1000
delt = t2 - t1
assert cmd == 0xBF
assert r[0] == CtapError.ERR.TIMEOUT
assert delt < 1000 and delt > 400
def test_not_cont(self, device, check_timeouts=False):
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
device.send_raw("\x81\x04\x00")
device.send_raw("\x00")
device.send_raw("\x01")
device.send_raw("\x81\x10\x00") # init packet
cmd, r = device.recv_raw() # timeout response
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_SEQ
if check_timeouts:
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
device.send_raw("\x01\x10\x00")
with pytest.raises(socket.timeout):
cmd, r = device.recv_raw() # timeout response
def test_check_busy(self, device):
t1 = time.time() * 1000
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
oldcid = device.cid()
newcid = b"\x11\x22\x33\x44"
device.send_raw("\x81\x04\x00")
device.set_cid(newcid)
device.send_raw("\x81\x04\x00")
cmd, r = device.recv_raw() # busy response
t2 = time.time() * 1000
assert t2 - t1 < 100
assert cmd == 0xBF
assert r[0] == CtapError.ERR.CHANNEL_BUSY
device.set_cid(oldcid)
cmd, r = device.recv_raw() # timeout response
assert cmd == 0xBF
assert r[0] == CtapError.ERR.TIMEOUT
def test_check_busy_interleaved(self, device):
cid1 = b"\x11\x22\x33\x44"
cid2 = b"\x01\x22\x33\x44"
device.set_cid(cid2)
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
device.set_cid(cid1)
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
device.send_raw("\x81\x00\x63") # echo 99 bytes first channel
device.set_cid(cid2) # send ping on 2nd channel
device.send_raw("\x81\x00\x39")
time.sleep(0.1)
device.send_raw("\x00")
cmd, r = device.recv_raw() # busy response
device.set_cid(cid1) # finish 1st channel ping
device.send_raw("\x00")
device.set_cid(cid2)
assert cmd == 0xBF
assert r[0] == CtapError.ERR.CHANNEL_BUSY
device.set_cid(cid1)
cmd, r = device.recv_raw() # ping response
assert cmd == 0x81
assert len(r) == 0x39
def test_cid_0(self, device):
device.set_cid("\x00\x00\x00\x00")
device.send_raw(
"\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00"
)
cmd, r = device.recv_raw() # timeout
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_CHANNEL
device.set_cid("\x05\x04\x03\x02")
def test_cid_ffffffff(self, device):
device.set_cid("\xff\xff\xff\xff")
device.send_raw(
"\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\xff\xff\xff\xff"
)
cmd, r = device.recv_raw() # timeout
assert cmd == 0xBF
assert r[0] == CtapError.ERR.INVALID_CHANNEL
device.set_cid("\x05\x04\x03\x02")
def test_keep_alive(self, device, check_timeouts=False):
precanned_make_credential = unhexlify(
'01a401582031323334353637383961626364656630313233343536373'\
'8396162636465663002a26269646b6578616d706c652e6f7267646e61'\
'6d65694578616d706c65525003a462696446cc2abaf119f26469636f6'\
'e781f68747470733a2f2f7777772e77332e6f72672f54522f77656261'\
'7574686e2f646e616d657256696e204f6c696d7069612047657272696'\
'56b646973706c61794e616d65781c446973706c617965642056696e20'\
'4f6c696d706961204765727269650481a263616c672664747970656a7'\
'075626c69632d6b6579')
count = 0
def count_keepalive(_x):
nonlocal count
count += 1
# We should get a keepalive within .5s
try:
r = device.send_data(CTAPHID.CBOR, precanned_make_credential, timeout = .50, on_keepalive = count_keepalive)
except CtapError as e:
assert e.code == CtapError.ERR.KEEPALIVE_CANCEL
assert count > 0
# wait for authnr to get UP or timeout
while True:
try:
r = device.send_data(CTAPHID.CBOR, '\x04') # getInfo
break
except CtapError as e:
assert e.code == CtapError.ERR.CHANNEL_BUSY

View File

@@ -0,0 +1,199 @@
import pytest
from fido2.ctap import CtapError
from fido2.ctap2.extensions import HmacSecretExtension
from fido2.utils import hmac_sha256
from fido2.ctap2.pin import PinProtocolV2
from fido2.webauthn import UserVerificationRequirement
from utils import *
salt1 = b"\xa5" * 32
salt2 = b"\x96" * 32
salt3 = b"\x03" * 32
salt4 = b"\x5a" * 16
salt5 = b"\x96" * 64
@pytest.fixture(scope="class")
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
assert MCHmacSecret.auth_data.extensions["hmac-secret"] == True
def test_hmac_secret_info(info):
assert "hmac-secret" in info.extensions
def test_fake_extension(device):
device.doMC(extensions={"tetris": True})
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, 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
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
if len(salts) == 1:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.6
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.6
if len(salts) == 2:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.6
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.6
def get_output(device, MCHmacSecret, hmac, 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
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
if len(salts) == 2:
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
else:
return ext["hmacGetSecret"]['output1']
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
output12 = get_output(
device, MCHmacSecret, hmac, (salt1, salt2)
)
output21 = get_output(
device, MCHmacSecret, hmac, (salt2, salt1)
)
assert output12[0] == output1
assert output21[1] == output1
assert output21[0] == output12[1]
assert output12[0] != output12[1]
def test_missing_keyAgreement(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError):
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
def test_missing_saltAuth(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_missing_saltEnc(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_auth(device, hmac, 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)
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 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:
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
else:
hout = hmac.process_get_input({"hmacGetSecret":{"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
):
""" 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]}})
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
accounts = 3
regs = []
auths = []
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
fixed_users = [generate_random_user() for _ in range(accounts)]
for i in range(accounts):
res = device.doMC(extensions={"hmacCreateSecret": True},
rk=True,
rp=rp,
user=fixed_users[i])['res'].attestation_object
regs.append(res)
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
ga = device.doGA(extensions={"hmacGetSecret": hout}, rp_id=rp['id'])
auths = ga['res'].get_assertions()
for x in auths:
assert x.auth_data.flags & (1 << 7) # has extension
ext = x.auth_data.extensions
assert ext
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):
salts = [salt1]
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
ext_no_uv = auth_no_uv.auth_data.extensions
assert ext_no_uv
assert "hmac-secret" in ext_no_uv
assert isinstance(ext_no_uv["hmac-secret"], bytes)
assert len(ext_no_uv["hmac-secret"]) == len(salts) * 32 + 16
# Now get same auth with UV
hout = {'salt1':salts[0]}
if (len(salts) > 1):
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 ext_uv
assert "hmacGetSecret" in ext_uv
assert len(ext_uv["hmacGetSecret"]) == len(salts)
# Now see if the hmac-secrets are different
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']

241
tests/pico-fido/test_pin.py Normal file
View File

@@ -0,0 +1,241 @@
import os
import pytest
from fido2.ctap import CtapError
from fido2.client import ClientPin
from fido2.webauthn import UserVerificationRequirement
from fido2.utils import hmac_sha256
from utils import *
def test_lockout(device, resetdevice, client_pin):
pin = "TestPin"
client_pin.set_pin(pin)
pin_token = client_pin.get_pin_token(pin)
for i in range(1, 10):
err = CtapError.ERR.PIN_INVALID
if i in (3, 6):
err = CtapError.ERR.PIN_AUTH_BLOCKED
elif i >= 8:
err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID]
with pytest.raises(CtapError) as e:
client_pin.get_pin_token("WrongPin")
assert e.value.code == err or e.value.code in err
attempts = 8 - i
if i > 8:
attempts = 0
res = client_pin.get_pin_retries()
assert res[0] == attempts
if err == CtapError.ERR.PIN_AUTH_BLOCKED:
device.reboot()
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
with pytest.raises(CtapError) as e:
device.doMC()
device.reboot()
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
with pytest.raises(CtapError) as e:
client_pin.get_pin_token(pin)
assert e.value.code == CtapError.ERR.PIN_BLOCKED
def test_send_zero_length_pin_auth(device):
device.reset()
with pytest.raises(CtapError) as e:
reg = device.MC(pin_uv_param=b"")
assert e.value.code == CtapError.ERR.PIN_NOT_SET
with pytest.raises(CtapError) as e:
reg = device.GA(pin_uv_param=b"")
assert e.value.code in (CtapError.ERR.PIN_NOT_SET, CtapError.ERR.NO_CREDENTIALS)
def test_set_pin(device, client_pin):
device.reset()
client_pin.set_pin("TestPin")
device.reset()
def test_set_pin_too_big(client_pin):
with pytest.raises(CtapError) as e:
client_pin.set_pin("A" * 64)
assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION
def test_get_pin_token_but_no_pin_set(resetdevice, client_pin):
with pytest.raises(CtapError) as e:
client_pin.get_pin_token("TestPin")
assert e.value.code == CtapError.ERR.PIN_NOT_SET
def test_change_pin_but_no_pin_set(device, client_pin):
with pytest.raises(CtapError) as e:
client_pin.change_pin("TestPin", "1234")
assert e.value.code == CtapError.ERR.PIN_NOT_SET
def test_setting_pin_and_get_info(device, client_pin, info):
device.reset()
client_pin.set_pin("TestPin")
with pytest.raises(CtapError) as e:
client_pin.set_pin("TestPin")
assert info.options["clientPin"]
pin_token = client_pin.get_pin_token("TestPin")
res = client_pin.get_pin_retries()
assert res[0] == 8
device.reset()
PIN1 = "12345678"
PIN2 = "ABCDEF"
@pytest.fixture(scope="class", params=[PIN1])
def SetPinRes(request, device, client_pin):
device.reset()
pin = request.param
client_pin.set_pin(pin)
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
return res
@pytest.fixture(scope="class")
def CPRes(request, device, client_pin):
res = client_pin.ctap.client_pin(2, ClientPin.CMD.GET_KEY_AGREEMENT)
return res
@pytest.fixture(scope="class")
def MCPinRes(device):
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
return res
@pytest.fixture(scope="class")
def GAPinRes(device, MCPinRes):
res = device.doGA()
return res
def test_pin(CPRes):
pass
def test_set_pin_twice(device, client_pin):
""" Setting pin when a pin is already set should result in error NotAllowed. """
with pytest.raises(CtapError) as e:
client_pin.set_pin('1234')
client_pin.set_pin('1234')
assert e.value.code == CtapError.ERR.NOT_ALLOWED
def test_get_key_agreement_fields(CPRes):
key = CPRes[1]
assert "Is public key" and key[1] == 2
assert "Is P256" and key[-1] == 1
assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25
assert "Right key" and len(key[-3]) == 32 and isinstance(key[-3], bytes)
def test_verify_flag(device, SetPinRes):
reg = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)['res'].attestation_object
assert reg.auth_data.flags & (1 << 2)
def test_get_no_pin_auth(device):
reg = device.doMC()['res'].attestation_object
allow_list = [
{"type": "public-key", "id": reg.auth_data.credential_data.credential_id}
]
auth = device.GA(allow_list=allow_list)['res']
assert not (auth.auth_data.flags & (1 << 2))
with pytest.raises(CtapError) as e:
reg = device.MC()
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
def test_zero_length_pin_auth(device):
with pytest.raises(CtapError) as e:
reg = device.MC(pin_uv_param=b"")
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
with pytest.raises(CtapError) as e:
reg = device.GA(pin_uv_param=b"")
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
def test_make_credential_no_pin(device):
with pytest.raises(CtapError) as e:
reg = device.MC()
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
def test_get_assertion_no_pin(device):
with pytest.raises(CtapError) as e:
reg = device.GA()
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_change_pin(device, client_pin):
device.reset()
client_pin.set_pin(PIN1)
client_pin.change_pin(PIN1, PIN2)
pin_token = client_pin.get_pin_token(pin=PIN2)
cdh = os.urandom(32)
pin_auth = hmac_sha256(pin_token, cdh)[:32]
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
pin_token = client_pin.get_pin_token(pin=PIN2)
pin_auth = hmac_sha256(pin_token, cdh)[:32]
auth = device.GA(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh, allow_list=[{
"type": "public-key",
"id": reg.auth_data.credential_data.credential_id,
}])['res']
assert reg.auth_data.flags & (1 << 2)
assert auth.auth_data.flags & (1 << 2)
verify(reg, auth, client_data_hash=cdh)
def test_pin_attempts(device, client_pin):
# Flip 1 bit
pin = PIN1
device.reset()
client_pin.set_pin(pin)
pin_wrong = list(pin)
c = pin[len(pin) // 2]
pin_wrong[len(pin) // 2] = chr(ord(c) ^ 1)
pin_wrong = "".join(pin_wrong)
for i in range(1, 3):
with pytest.raises(CtapError) as e:
pin_token = client_pin.get_pin_token(pin=pin_wrong)
assert e.value.code == CtapError.ERR.PIN_INVALID
print("Check there is %d pin attempts left" % (8 - i))
res = client_pin.get_pin_retries()
assert res[0] == (8 - i)
for i in range(1, 3):
with pytest.raises(CtapError) as e:
client_pin.get_pin_token(pin_wrong)
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
device.reboot()
client_pin = ClientPin(device.client()._backend.ctap2)
pin_token = client_pin.get_pin_token(pin=pin)
cdh = os.urandom(32)
pin_auth = hmac_sha256(pin_token, cdh)[:32]
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
res = client_pin.get_pin_retries()
assert res[0] == (8)

View File

@@ -0,0 +1,164 @@
from fido2.client import CtapError
from fido2.cose import ES256
import pytest
def test_register(device):
device.reset()
REGRes,AUTData = device.register()
def test_make_credential():
pass
def test_attestation_format(MCRes):
assert MCRes['res'].attestation_object.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
def test_authdata_length(MCRes):
assert len(MCRes['res'].attestation_object.auth_data) >= 77
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
device.MC(client_data_hash=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_cdh(device):
with pytest.raises(CtapError) as e:
device.MC(client_data_hash=b'\xff')
def test_missing_user(device):
with pytest.raises(CtapError) as e:
device.doMC(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")
def test_missing_rp(device):
with pytest.raises(CtapError) as e:
device.MC(rp=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_rp(device):
with pytest.raises(CtapError) as e:
device.MC(rp=b"12345678")
def test_missing_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.MC(key_params=b"12345678")
def test_bad_type_excludeList(device):
with pytest.raises(CtapError) as e:
device.MC(exclude_list=8)
def test_bad_type_extensions(device):
with pytest.raises(CtapError) as e:
device.MC(extensions=8)
def test_bad_type_options(device):
with pytest.raises(CtapError) as e:
device.MC(options=8)
def test_bad_type_rp_name(device):
with pytest.raises(CtapError) as e:
device.doMC(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})
def test_bad_type_user_name(device):
with pytest.raises(CtapError) as e:
device.doMC(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"})
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"])
def test_missing_pubKeyCredParams_type(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_missing_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"type": "public-key"}])
assert e.value.code in [
CtapError.ERR.MISSING_PARAMETER,
CtapError.ERR.UNSUPPORTED_ALGORITHM,
]
def test_bad_type_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
def test_unsupported_algorithm(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": 1337, "type": "public-key"}])
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
def test_exclude_list(resetdevice):
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
def test_exclude_list2(resetdevice):
resetdevice.doMC(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"])
def test_missing_exclude_list_type(device):
with pytest.raises(CtapError) as e:
device.doMC(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"}])
def test_bad_type_exclude_list_id(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
def test_bad_type_exclude_list_type(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": b"public-key", "id": b"1234"}])
def test_exclude_list_excluded(device):
res = device.doMC()['res'].attestation_object
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[
{"id": res.auth_data.credential_data.credential_id, "type": "public-key"}
])
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
def test_unknown_option(resetdevice):
resetdevice.MC(options={"unknown": False})

110
tests/pico-fido/test_u2f.py Normal file
View File

@@ -0,0 +1,110 @@
import pytest
import os
from fido2.ctap1 import APDU, ApduError, Ctap1
from fido2.webauthn import CollectedClientData
from fido2.utils import sha256
def test_u2f_reg(RegRes):
pass
def test_u2f_auth(RegRes, AuthRes):
pass
def test_u2f_auth_check_only(device, RegRes):
with pytest.raises(ApduError) as e:
device.ctap1.authenticate(
RegRes['req']['client_data'].hash,
RegRes['res'].attestation_object.auth_data.rp_id_hash,
RegRes['res'].attestation_object.auth_data.credential_data.credential_id,
check_only=True,
)
assert e.value.code == APDU.USE_NOT_SATISFIED
def test_version(device):
assert device.ctap1.get_version() == "U2F_V2"
def test_bad_ins(device):
with pytest.raises(ApduError) as e:
device.ctap1.send_apdu(0, 0, 0, 0, b"")
assert e.value.code == 0x6D00
def test_bad_cla(device):
with pytest.raises(ApduError) as e:
device.ctap1.send_apdu(1, Ctap1.INS.VERSION, 0, 0, b"abc")
assert e.value.code == 0x6E00
@pytest.mark.parametrize("iterations", (5,))
def test_u2f_it(device, iterations):
lastc = 0
regs = []
cd = CollectedClientData.create(
type=CollectedClientData.TYPE.CREATE, origin=None, challenge=os.urandom(32)
)
cdh = cd.hash
rih = sha256(device.rp()['id'].encode())
for i in range(0, iterations):
reg = device.ctap1.register(cdh, rih)
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
auth.verify(rih, cdh, reg.public_key)
regs.append(reg)
# check endianness
if lastc:
assert (auth.counter - lastc) < 256
lastc = auth.counter
if lastc > 0x80000000:
print("WARNING: counter is unusually high: %04x" % lastc)
assert 0
for reg in regs:
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
device.reboot()
for reg in regs:
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
for reg in regs:
with pytest.raises(ApduError) as e:
auth = device.ctap1.authenticate(
cdh, rih, reg.key_handle, check_only=True
)
assert e.value.code == APDU.USE_NOT_SATISFIED
def test_bad_key_handle(device, RegRes):
kh = bytearray(RegRes['res'].attestation_object.auth_data.credential_data.credential_id)
kh[0] = kh[0] ^ (0x40)
with pytest.raises(ApduError) as e:
device.ctap1.authenticate(
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh, check_only=True
)
assert e.value.code == APDU.WRONG_DATA
with pytest.raises(ApduError) as e:
device.ctap1.authenticate(
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh
)
assert e.value.code == APDU.WRONG_DATA
def test_bad_key_handle_length(device, RegRes):
kh = bytearray(RegRes['res'].attestation_object.auth_data.credential_data.credential_id)
with pytest.raises(ApduError) as e:
device.ctap1.authenticate(
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh[: len(kh) // 2]
)
assert e.value.code == APDU.WRONG_DATA
def test_incorrect_appid(device, RegRes):
badid = bytearray(RegRes['res'].attestation_object.auth_data.rp_id_hash)
badid[0] = badid[0] ^ (0x40)
with pytest.raises(ApduError) as e:
device.ctap1.authenticate(
RegRes['res'].client_data.hash, badid, RegRes['res'].attestation_object.auth_data.credential_data.credential_id
)
assert e.value.code == APDU.WRONG_DATA

117
tests/utils.py Normal file
View File

@@ -0,0 +1,117 @@
from fido2.webauthn import AttestedCredentialData
import random
import string
import secrets
import math
from threading import Event, Timer
from numbers import Number
def verify(MC, GA, client_data_hash):
credential_data = AttestedCredentialData(MC.auth_data.credential_data)
GA.verify(client_data_hash, credential_data.public_key)
def generate_random_user():
# https://www.w3.org/TR/webauthn/#user-handle
user_id_length = random.randint(1, 64)
user_id = secrets.token_bytes(user_id_length)
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
name = "User name"
display_name = "Displayed " + name
return {"id": user_id, "name": name, "displayName": display_name}
counter = 1
def generate_user_maximum():
"""
Generate RK with the maximum lengths of the fields, according to the minimal requirements of the FIDO2 spec
"""
global counter
# https://www.w3.org/TR/webauthn/#user-handle
user_id_length = 64
user_id = secrets.token_bytes(user_id_length)
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
name = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(64))
name = f"{counter}: {name}"
icon = "https://www.w3.org/TR/webauthn/" + "A" * 128
display_name = "Displayed " + name
name = name[:64]
display_name = display_name[:64]
icon = icon[:128]
counter += 1
return {"id": user_id, "name": name, "icon": icon, "displayName": display_name}
def shannon_entropy(data):
s = 0.0
total = len(data)
for x in range(0, 256):
freq = data.count(x)
p = freq / total
if p > 0:
s -= p * math.log2(p)
return s
# Timeout from:
# https://github.com/Yubico/python-fido2/blob/f1dc028d6158e1d6d51558f72055c65717519b9b/fido2/utils.py
# Copyright (c) 2013 Yubico AB
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
class Timeout(object):
"""Utility class for adding a timeout to an event.
:param time_or_event: A number, in seconds, or a threading.Event object.
:ivar event: The Event associated with the Timeout.
:ivar timer: The Timer associated with the Timeout, if any.
"""
def __init__(self, time_or_event):
if isinstance(time_or_event, Number):
self.event = Event()
self.timer = Timer(time_or_event, self.event.set)
else:
self.event = time_or_event
self.timer = None
def __enter__(self):
if self.timer:
self.timer.start()
return self.event
def __exit__(self, exc_type, exc_val, exc_tb):
if self.timer:
self.timer.cancel()
self.timer.join()

13
workflows/autobuild.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
git submodule update --init --recursive
sudo apt update
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 submodule update --init
cd ..
mkdir build
cd build
cmake -DPICO_SDK_PATH=../pico-sdk ..
make