Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31991a31c3 | ||
|
|
fcc9b49799 | ||
|
|
cf40b8dff8 | ||
|
|
1f5e106f22 | ||
|
|
39208c2167 | ||
|
|
f5c0793a8d | ||
|
|
abcfe6e87b | ||
|
|
d0526d7de6 | ||
|
|
4ce816e9f6 | ||
|
|
85bd329e3b | ||
|
|
5f45a6b75b | ||
|
|
f97b942d11 | ||
|
|
93bba4fb76 | ||
|
|
0dc2b73de4 | ||
|
|
dc572bcc81 | ||
|
|
f6a1d146e7 | ||
|
|
0d89a21be7 | ||
|
|
65194e3775 | ||
|
|
5b778f2e27 | ||
|
|
b0180711e7 | ||
|
|
4bcbf7f9a9 | ||
|
|
cf0686f857 | ||
|
|
c54a6fa6fe | ||
|
|
8b08618875 | ||
|
|
a59cdef8e6 | ||
|
|
d4f2d04487 | ||
|
|
522b7d5841 | ||
|
|
6b93938040 | ||
|
|
898c88dc6d | ||
|
|
51c13b0f0b | ||
|
|
d424f0dea7 | ||
|
|
de1bf3d2d4 | ||
|
|
85423fed85 | ||
|
|
6c85421eca | ||
|
|
3e9d1a4eb4 | ||
|
|
c6dba5df43 | ||
|
|
eae22a97fb | ||
|
|
1b8ee2fc87 | ||
|
|
7d97b21ca4 | ||
|
|
665f029593 | ||
|
|
78de56f0a9 | ||
|
|
b25e4bed6c | ||
|
|
56b6b4a8b9 | ||
|
|
9b254a0738 | ||
|
|
e4f8caa1ba | ||
|
|
7e720e8c23 | ||
|
|
b3b3a5eecc | ||
|
|
bf484d8663 | ||
|
|
6b636d0bf4 | ||
|
|
54fb02995f | ||
|
|
56d5c61044 | ||
|
|
1ac628d241 | ||
|
|
48cc417546 | ||
|
|
2919b37e9c | ||
|
|
6836ffaf02 | ||
|
|
d1c61536e0 | ||
|
|
351242d377 | ||
|
|
3fe3a9d2ec | ||
|
|
35a043f261 | ||
|
|
44c5ad4adb | ||
|
|
a5fd31a5d6 | ||
|
|
fdf97f5469 | ||
|
|
d30ebde4f0 | ||
|
|
f7ba3eec38 | ||
|
|
66ecd6a7fc | ||
|
|
d1dccf3762 | ||
|
|
292a9e8d8a | ||
|
|
73a7856866 | ||
|
|
2b640a5c36 | ||
|
|
bf1072781b | ||
|
|
81e03cefda | ||
|
|
5facbf61cd | ||
|
|
669f6041bd | ||
|
|
db679e4143 | ||
|
|
8b317042a8 | ||
|
|
71512ae61a | ||
|
|
fcd29a0717 | ||
|
|
bb79e6d726 | ||
|
|
a9c35afda3 | ||
|
|
be2ab59cd1 | ||
|
|
9c28f72d17 | ||
|
|
0518ac3655 | ||
|
|
b4d9e8b693 | ||
|
|
93523faf02 | ||
|
|
a018a7f66c | ||
|
|
9b75c5c175 | ||
|
|
513642663b | ||
|
|
e4ed703b6b | ||
|
|
91aaee5beb | ||
|
|
a61bb91824 |
50
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
50
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
## Summary
|
||||
|
||||
Describe in plain language what this PR does and why.
|
||||
|
||||
- What problem does it solve?
|
||||
- Is it a bug fix, a new feature, a cleanup/refactor…?
|
||||
|
||||
|
||||
## Details / Impact
|
||||
|
||||
Please include any relevant details:
|
||||
|
||||
- Hardware / board(s) tested:
|
||||
- Firmware / commit/base version:
|
||||
- Security impact (if any):
|
||||
- e.g. changes PIN handling, touches key storage, affects attestation, etc.
|
||||
- Behavior changes:
|
||||
- e.g. new command, new API surface, different defaults, etc.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
How did you test this change?
|
||||
|
||||
- Steps to reproduce / validate:
|
||||
- Expected vs actual results:
|
||||
- Any logs / traces (please remove secrets):
|
||||
|
||||
|
||||
## Licensing confirmation (required)
|
||||
|
||||
By checking the box below, you confirm ALL of the following:
|
||||
|
||||
- You are the author of this contribution, or you have the right to contribute it.
|
||||
- You have read `CONTRIBUTING.md`.
|
||||
- You agree that this contribution may be merged, used, modified, and redistributed:
|
||||
- under the AGPLv3 Community Edition, **and**
|
||||
- under any proprietary / commercial / Enterprise editions of this project,
|
||||
now or in the future.
|
||||
- You understand that submitting this PR does not create any support obligation,
|
||||
SLA, or guarantee of merge.
|
||||
|
||||
**I confirm the above licensing terms:**
|
||||
|
||||
- [ ] Yes, I agree
|
||||
|
||||
|
||||
## Anything else?
|
||||
|
||||
Optional: mention known limitations, follow-ups, or if this is related to an existing Issue.
|
||||
7
.github/workflows/nightly.yml
vendored
7
.github/workflows/nightly.yml
vendored
@@ -19,13 +19,20 @@ jobs:
|
||||
with:
|
||||
ref: ${{ matrix.refs }}
|
||||
submodules: 'recursive'
|
||||
- name: Restore private key
|
||||
run: |
|
||||
echo "${{ secrets.PRIVATE_KEY_B64 }}" | base64 -d > private.pem
|
||||
chmod 600 private.pem
|
||||
- name : Build
|
||||
env:
|
||||
PICO_SDK_PATH: ../pico-sdk
|
||||
SECURE_BOOT_PKEY: ../private.pem
|
||||
run: |
|
||||
./workflows/autobuild.sh pico
|
||||
./build_pico_fido.sh --no-eddsa
|
||||
./workflows/autobuild.sh esp32
|
||||
- name: Delete private key
|
||||
run: rm private.pem
|
||||
- name: Update nightly release
|
||||
uses: pyTooling/Actions/releaser@main
|
||||
with:
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
if(ESP_PLATFORM)
|
||||
set(DEBUG_APDU 1)
|
||||
set(DENABLE_POWER_ON_RESET 0)
|
||||
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
@@ -35,11 +34,6 @@ project(pico_fido C CXX ASM)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(ENABLE_EMULATION)
|
||||
else()
|
||||
pico_sdk_init()
|
||||
endif()
|
||||
|
||||
add_executable(pico_fido)
|
||||
endif()
|
||||
|
||||
@@ -77,6 +71,13 @@ else()
|
||||
set(USB_ITF_CCID 0)
|
||||
endif()
|
||||
|
||||
set(USB_ITF_HID 1)
|
||||
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
|
||||
|
||||
if(NOT ESP_PLATFORM)
|
||||
set(SOURCES ${PICO_KEYS_SOURCES})
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.c
|
||||
@@ -98,6 +99,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_large_blobs.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/management.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/defs.c
|
||||
)
|
||||
if (${ENABLE_OATH_APP})
|
||||
set(SOURCES ${SOURCES}
|
||||
@@ -110,9 +112,7 @@ set(SOURCES ${SOURCES}
|
||||
)
|
||||
endif()
|
||||
|
||||
set(USB_ITF_HID 1)
|
||||
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
|
||||
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 1)
|
||||
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 2)
|
||||
if(ESP_PLATFORM)
|
||||
project(pico_fido)
|
||||
endif()
|
||||
@@ -150,6 +150,12 @@ if(ENABLE_EMULATION)
|
||||
target_link_options(pico_fido PUBLIC
|
||||
-Wl,-dead_strip
|
||||
)
|
||||
if(DEBUG_APDU)
|
||||
target_compile_options(pico_fido PUBLIC
|
||||
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
|
||||
target_link_options(pico_fido PUBLIC
|
||||
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
|
||||
endif()
|
||||
else()
|
||||
target_link_options(pico_fido PUBLIC
|
||||
-Wl,--gc-sections
|
||||
|
||||
105
CONTRIBUTING.md
Normal file
105
CONTRIBUTING.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for your interest in contributing to this project.
|
||||
|
||||
This repository is published in two forms:
|
||||
- a Community Edition released under AGPLv3, and
|
||||
- a proprietary / commercial / Enterprise Edition offered to organizations.
|
||||
|
||||
To keep that model legally clean, we need to be explicit about how contributions can be used.
|
||||
|
||||
By opening a pull request, you agree to all of the following:
|
||||
|
||||
1. **You have the right to contribute this code.**
|
||||
You are either the original author of the contribution, or you have obtained the necessary rights/permissions to contribute it under these terms.
|
||||
|
||||
2. **Dual licensing permission.**
|
||||
You agree that your contribution may be:
|
||||
- merged into this repository, and
|
||||
- used, copied, modified, sublicensed, and redistributed
|
||||
- under the AGPLv3 Community Edition, and
|
||||
- under any proprietary / commercial / Enterprise editions of this project,
|
||||
now or in the future.
|
||||
|
||||
In other words: you are granting the project maintainer(s) the right to include
|
||||
your contribution in both the open-source (AGPLv3) codebase and in closed-source /
|
||||
commercially licensed builds, without any additional approval or payment.
|
||||
|
||||
3. **Attribution.**
|
||||
The maintainers may keep or add attribution lines such as
|
||||
`Copyright (c) <your name>` or an AUTHORS / CONTRIBUTORS list.
|
||||
The maintainers may also make changes for clarity, style, security, refactoring,
|
||||
or integration reasons.
|
||||
|
||||
4. **No automatic SLA.**
|
||||
Submitting a pull request does *not* create any support obligation,
|
||||
service-level agreement, warranty, or guarantee that the contribution
|
||||
will be reviewed, merged, or maintained.
|
||||
|
||||
5. **Potential rejection for business reasons.**
|
||||
Features that fall under "Enterprise / Commercial" functionality
|
||||
(e.g. multi-tenant provisioning at scale, centralized audit trails,
|
||||
corporate policy enforcement, attestation/branding flows, key escrow / dual-control,
|
||||
etc.) may be declined for the public AGPLv3 tree even if technically valid.
|
||||
That is normal: some functionality is intentionally offered only
|
||||
under commercial terms.
|
||||
|
||||
If you are not comfortable with these terms, **do not open a pull request yet.**
|
||||
Instead, please open an Issue to start a discussion.
|
||||
|
||||
## How to contribute (technical side)
|
||||
|
||||
### 1. Bug reports / issues
|
||||
- Please include:
|
||||
- hardware / board revision
|
||||
- firmware / commit hash
|
||||
- exact steps to reproduce
|
||||
- expected vs actual behavior
|
||||
- logs / traces if available (strip secrets)
|
||||
|
||||
Security-sensitive findings: do **not** post publicly.
|
||||
Send a short report by email instead so it can be triaged responsibly.
|
||||
|
||||
### 2. Small fixes / minor improvements
|
||||
- You can open a PR directly for:
|
||||
- bug fixes
|
||||
- portability fixes / new board definitions
|
||||
- clarifications in code comments
|
||||
- build / tooling cleanup
|
||||
- documentation of existing behavior
|
||||
|
||||
Please keep PRs focused (one logical change per PR if possible).
|
||||
|
||||
### 3. Larger features / behavior changes
|
||||
- Please open an Issue first and describe:
|
||||
- what problem you're solving (not just "add feature X")
|
||||
- impact on existing flows / security model
|
||||
- any new dependencies
|
||||
|
||||
This helps avoid doing a bunch of work on something that won't be accepted
|
||||
in the Community Edition.
|
||||
|
||||
### 4. Coding style / security posture
|
||||
- Aim for clarity and small, auditable changes. This code runs in places
|
||||
where secrets live.
|
||||
- No debug backdoors, no "just for testing" shortcuts left enabled.
|
||||
- Keep external dependencies minimal and license-compatible
|
||||
(MIT / Apache 2.0 / similarly permissive is usually fine).
|
||||
|
||||
### 5. Commit / PR format
|
||||
- Use descriptive commit messages ("Fix PIN retry counter wrap" is better than "fix stuff").
|
||||
- In the PR description, please include a short summary of what was changed and why.
|
||||
- At the bottom of the PR description, **copy/paste and confirm the licensing line below**:
|
||||
|
||||
> I confirm that I have read `CONTRIBUTING.md` and I agree that this contribution may be used under both the AGPLv3 Community Edition and any proprietary / commercial / Enterprise editions of this project, now or in the future.
|
||||
|
||||
A PR without that confirmation may be delayed or closed without merge.
|
||||
|
||||
## Thank you
|
||||
|
||||
This project exists because people build on it, break it, fix it,
|
||||
and push it into places it wasn't originally designed to go.
|
||||
|
||||
Whether you are here for research, hacking on hardware,
|
||||
rolling out secure keys for a team, or building a commercial product:
|
||||
thank you for helping improve it.
|
||||
116
ENTERPRISE.md
Normal file
116
ENTERPRISE.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Enterprise / Commercial Edition
|
||||
|
||||
This project is offered under two editions:
|
||||
|
||||
## 1. Community Edition (FOSS)
|
||||
|
||||
The Community Edition is released under the GNU Affero General Public License v3 (AGPLv3).
|
||||
|
||||
Intended for:
|
||||
- individual users and researchers
|
||||
- evaluation / prototyping
|
||||
- internal lab / security testing
|
||||
|
||||
You are allowed to:
|
||||
- read and study the source code
|
||||
- modify it
|
||||
- run it internally
|
||||
|
||||
Obligations under AGPLv3:
|
||||
- If you distribute modified firmware/binaries/libraries to third parties, you must provide the corresponding source code of your modifications.
|
||||
- If you run a modified version of this project as a network-accessible service (internal or external), you must offer the source code of those modifications to the users of that service.
|
||||
- No warranty, no support, no SLA.
|
||||
- Enterprise features (bulk provisioning, multi-user policy enforcement, device inventory / revocation, corporate PIN rules, custom attestation/identity, etc.) are NOT included.
|
||||
|
||||
The Community Edition will continue to exist.
|
||||
|
||||
## 2. Enterprise / Commercial Edition
|
||||
|
||||
The Enterprise / Commercial Edition is a proprietary license for organizations that need to:
|
||||
|
||||
- deploy this in production at scale (multiple devices / multiple users / multiple teams)
|
||||
- integrate it into their own physical product or appliance
|
||||
- run it as an internal service (VM / container / private cloud "HSM / auth backend") for multiple internal teams or tenants
|
||||
- enforce internal security policy (admin vs user roles, mandatory PIN rules, secure offboarding / revocation)
|
||||
- avoid any AGPLv3 disclosure obligations for their own modifications and integration code
|
||||
|
||||
### What the Enterprise Edition provides
|
||||
|
||||
**Base license package (always included):**
|
||||
- **Commercial license (proprietary).**
|
||||
You may run and integrate the software/firmware in production — including virtualized / internal-cloud style deployments — without being required to disclose derivative source code under AGPLv3.
|
||||
- **Official signed builds.**
|
||||
You receive signed builds from the original developer so you can prove integrity and provenance.
|
||||
- **Onboarding call (up to 1 hour).**
|
||||
A live remote session to get you from "we have it" to "it’s actually running in our environment" with minimal guesswork.
|
||||
|
||||
**Optional enterprise components (available on demand, scoped and priced per customer):**
|
||||
- **Production / multi-user readiness.**
|
||||
Permission to operate the system with multiple users, multiple devices and multiple teams in real environments.
|
||||
- **Bulk / fleet provisioning.**
|
||||
Automated enrollment for many tokens/devices/users at once (CSV / directory import), scripted onboarding of new users, initial PIN assignment / reset workflows, and role-based access (admin vs user).
|
||||
- **Policy & lifecycle tooling.**
|
||||
Corporate PIN policy enforcement, per-user / per-team access control, device inventory / traceability, and secure revocation / retirement when someone leaves.
|
||||
- **Custom attestation / per-organization identity.**
|
||||
Per-company certificate chains and attestation keys so devices can prove "this token/HSM is officially ours," including anti-cloning / unique device identity for OEM and fleet use.
|
||||
- **Virtualization / internal cloud deployment support.**
|
||||
Guidance and components to run this as an internal service (VM, container, private-cloud HSM/auth backend) serving multiple internal teams or tenants under your brand.
|
||||
- **Post-quantum (PQC) key material handling.**
|
||||
Integration/roadmap support for PQC algorithms (auth / signing) and secure PQC key storage inside the device or service.
|
||||
- **Hierarchical deterministic key derivation (HD).**
|
||||
Wallet-style hierarchical key trees (BIP32-like concepts adapted to this platform) for issuing per-user / per-tenant / per-purpose subkeys without exporting the root secret — e.g. embedded wallet logic, tenant isolation, firmware signing trees, large fleets.
|
||||
- **Cryptographically signed audit trail / tamper-evident event logging.**
|
||||
High-assurance logging of sensitive actions (key use, provisioning, PIN resets, revocations) with integrity protection for forensic / compliance needs.
|
||||
- **Dual-control / two-person approval ("four-eyes").**
|
||||
Require multi-party authorization for high-risk actions such as firmware signing, key export, or critical configuration changes — standard in high-assurance / regulated environments.
|
||||
- **Secure key escrow / disaster recovery design.**
|
||||
Split-secret or escrowed backup strategies so you don’t lose critical signing keys if a single admin disappears or hardware is lost.
|
||||
- **Release-signing / supply-chain hardening pipeline.**
|
||||
Reference tooling and process so every production firmware/binary is signed with hardware-backed keys, proving origin and preventing tampering in transit or at manufacturing.
|
||||
- **Policy-locked hardened mode ("FIPS-style profile").**
|
||||
Restricted algorithms, debug disabled, no raw key export, tamper-evident configuration for regulated / high-assurance deployments.
|
||||
- **Priority support / security response SLA.**
|
||||
A direct line and guaranteed response window for production-impacting security issues.
|
||||
- **White-label demo / pre-sales bundle.**
|
||||
Branded demo firmware + safe onboarding script so you can show "your product" to your own customers without exposing real production secrets.
|
||||
|
||||
These components are NOT automatically bundled. They are available case-by-case depending on your use case and are priced separately.
|
||||
|
||||
### Licensing models
|
||||
|
||||
- **Internal Use License**
|
||||
Internal production use within one legal entity (your company), including internal private cloud / virtualized deployments for multiple internal teams.
|
||||
Optional enterprise components can be added as needed.
|
||||
|
||||
- **OEM / Redistribution / Service License**
|
||||
Integration into a product/appliance you ship to customers, OR operating this as a managed service / hosted feature for external clients or third parties.
|
||||
Optional enterprise components (attestation branding, PQC support, HD key derivation, multi-tenant service hardening, audit trail, etc.) can be added as required.
|
||||
|
||||
Pricing depends on scope, fleet size, number of users/tenants, regulatory requirements, and which optional components you select.
|
||||
|
||||
### Request a quote
|
||||
|
||||
Email: pol@henarejos.me
|
||||
Subject: `ENTERPRISE LICENSE <your company name>`
|
||||
|
||||
Please include:
|
||||
- Company name and country
|
||||
- Intended use:
|
||||
- Internal private deployment
|
||||
- OEM / external service to third parties
|
||||
- Approximate scale (number of devices/tokens, number of users/tenants)
|
||||
- Which optional components you are interested in (bulk provisioning, policy & lifecycle tooling, attestation branding / anti-cloning, virtualization/cloud, PQC, HD key derivation, audit trail, dual-control, key escrow, supply-chain signing, hardened mode, SLA, white-label demo)
|
||||
|
||||
You will receive:
|
||||
1. A short commercial license agreement naming your company.
|
||||
2. Access to the base package (and any optional components agreed).
|
||||
3. Scheduling of the onboarding call.
|
||||
|
||||
## Why Enterprise exists
|
||||
|
||||
- Companies often need hardware-backed security (HSM, FIDO2, OpenPGP, etc.) under their own control, but cannot or will not open-source their internal security workflows.
|
||||
- They also need multi-user / fleet-management features that hobby users do not.
|
||||
- The commercial license funds continued development, maintenance and new hardware support.
|
||||
|
||||
The Community Edition remains AGPLv3.
|
||||
The Enterprise Edition is for production, scale, and legal clarity.
|
||||
143
LICENSE
143
LICENSE
@@ -1,5 +1,5 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@@ -7,17 +7,15 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
49
README.md
49
README.md
@@ -1,6 +1,8 @@
|
||||
# Pico FIDO
|
||||
This project transforms your Raspberry Pi Pico or ESP32 microcontroller into an integrated FIDO Passkey, functioning like a standard USB Passkey for authentication.
|
||||
|
||||
If you are looking for a Fido + OpenPGP, see: https://github.com/polhenarejos/pico-fido2
|
||||
|
||||
## Features
|
||||
Pico FIDO includes the following features:
|
||||
|
||||
@@ -135,6 +137,53 @@ To run a subset of tests, use the `-k <test>` flag:
|
||||
pytest -k test_credprotect
|
||||
```
|
||||
|
||||
## License and Commercial Use
|
||||
|
||||
This project is available under two editions:
|
||||
|
||||
**Community Edition (FOSS)**
|
||||
- Released under the GNU Affero General Public License v3 (AGPLv3).
|
||||
- You are free to study, modify, and run the code, including for internal evaluation.
|
||||
- If you distribute modified binaries/firmware, OR if you run a modified version of this project as a network-accessible service, you must provide the corresponding source code to the users of that binary or service, as required by AGPLv3.
|
||||
- No warranty. No SLA. No guaranteed support.
|
||||
|
||||
**Enterprise / Commercial Edition**
|
||||
- Proprietary license for organizations that want to:
|
||||
- run this in production with multiple users/devices,
|
||||
- integrate it into their own product/appliance,
|
||||
- enforce corporate policies (PIN policy, admin/user roles, revocation),
|
||||
- deploy it as an internal virtualized / cloud-style service,
|
||||
- and *not* be required to publish derivative source code.
|
||||
- Base package includes:
|
||||
- commercial license (no AGPLv3 disclosure obligation for your modifications / integration)
|
||||
- onboarding call
|
||||
- access to officially signed builds
|
||||
- Optional / on-demand enterprise components that can be added case-by-case:
|
||||
- ability to operate in multi-user / multi-device environments
|
||||
- device inventory, traceability and secure revocation/offboarding
|
||||
- custom attestation, per-organization device identity / anti-cloning
|
||||
- virtualization / internal "HSM or auth backend" service for multiple teams or tenants
|
||||
- post-quantum (PQC) key material handling and secure PQC credential storage
|
||||
- hierarchical deterministic key derivation (HD wallet–style key trees for per-user / per-tenant keys, firmware signing trees, etc.)
|
||||
- cryptographically signed audit trail / tamper-evident logging
|
||||
- dual-control / two-person approval for high-risk operations
|
||||
- secure key escrow / disaster recovery strategy
|
||||
- release-signing / supply-chain hardening toolchain
|
||||
- policy-locked hardened mode ("FIPS-style profile")
|
||||
- priority security-response SLA
|
||||
- white-label demo / pre-sales bundle
|
||||
|
||||
Typical licensing models:
|
||||
- Internal use (single legal entity, including internal private cloud / virtualized deployments).
|
||||
- OEM / Redistribution / Service (ship in your product OR offer it as a service to third parties).
|
||||
|
||||
These options are scoped and priced individually depending on which components you actually need.
|
||||
|
||||
For commercial licensing and enterprise features, email pol@henarejos.me
|
||||
Subject: `ENTERPRISE LICENSE <your company name>`
|
||||
|
||||
See `ENTERPRISE.md` for details.
|
||||
|
||||
## Credits
|
||||
Pico FIDO uses the following libraries or portion of code:
|
||||
- MbedTLS for cryptographic operations.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION_MAJOR="6"
|
||||
VERSION_MINOR="6"
|
||||
VERSION_MAJOR="7"
|
||||
VERSION_MINOR="0"
|
||||
NO_EDDSA=0
|
||||
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
|
||||
#if ! [[ -z "${GITHUB_SHA}" ]]; then
|
||||
@@ -23,12 +23,13 @@ fi
|
||||
cd build_release
|
||||
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
|
||||
SECURE_BOOT_PKEY="${SECURE_BOOT_PKEY:-../../ec_private_key.pem}"
|
||||
board_dir=${PICO_SDK_PATH}/src/boards/include/boards
|
||||
for board in "$board_dir"/*
|
||||
do
|
||||
board_name="$(basename -- "$board" .h)"
|
||||
rm -rf -- ./*
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=../../ec_private_key.pem
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY}
|
||||
make -j`nproc`
|
||||
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX.uf2
|
||||
done
|
||||
@@ -40,8 +41,8 @@ if [[ $NO_EDDSA -eq 0 ]]; then
|
||||
do
|
||||
board_name="$(basename -- "$board" .h)"
|
||||
rm -rf -- ./*
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=../../ec_private_key.pem -DENABLE_EDDSA=1
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY} -DENABLE_EDDSA=1
|
||||
make -j`nproc`
|
||||
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX-eddsa1.uf2
|
||||
mv pico_fido.uf2 ../release_eddsa/pico_fido_$board_name-$SUFFIX-eddsa1.uf2
|
||||
done
|
||||
fi
|
||||
|
||||
Submodule pico-keys-sdk updated: 580b0acffa...d0dea3d0c5
@@ -10,6 +10,8 @@ CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_WL_SECTOR_SIZE_512=y
|
||||
CONFIG_WL_SECTOR_MODE_PERF=y
|
||||
COMPILER_OPTIMIZATION="Performance"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS . ../../pico-keys-sdk/src ../../pico-keys-sdk/src/fs ../../pico-keys-sdk/src/rng ../../pico-keys-sdk/src/usb ../../pico-keys-sdk/tinycbor/src
|
||||
REQUIRES bootloader_support esp_partition esp_tinyusb zorxx__neopixel mbedtls efuse
|
||||
REQUIRES esp_tinyusb mbedtls efuse
|
||||
)
|
||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -104,7 +104,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
return CTAP1_ERR_INVALID_CMD;
|
||||
}
|
||||
|
||||
void cbor_thread(void) {
|
||||
void *cbor_thread(void *arg) {
|
||||
(void)arg;
|
||||
card_init_core1();
|
||||
while (1) {
|
||||
uint32_t m;
|
||||
@@ -115,17 +116,17 @@ void cbor_thread(void) {
|
||||
if (m == EV_EXIT) {
|
||||
break;
|
||||
}
|
||||
apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len);
|
||||
apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len);
|
||||
if (apdu.sw == 0) {
|
||||
DEBUG_DATA(res_APDU, res_APDU_size);
|
||||
}
|
||||
else {
|
||||
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
|
||||
res_APDU[-1] = apdu.sw;
|
||||
res_APDU[-1] = (uint8_t)apdu.sw;
|
||||
apdu.sw = 0;
|
||||
}
|
||||
else {
|
||||
res_APDU[0] = apdu.sw;
|
||||
res_APDU[0] = (uint8_t)apdu.sw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,9 +135,7 @@ void cbor_thread(void) {
|
||||
flag = EV_EXEC_FINISHED;
|
||||
queue_add_blocking(&card_to_usb_q, &flag);
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
vTaskDelete(NULL);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
||||
@@ -214,6 +213,9 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
|
||||
alg = FIDO2_ALG_EDDSA;
|
||||
}
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_ED448) {
|
||||
alg = FIDO2_ALG_ED448;
|
||||
}
|
||||
#endif
|
||||
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#ifndef ESP_PLATFORM
|
||||
#include "common.h"
|
||||
#else
|
||||
@@ -27,7 +28,7 @@
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -35,7 +36,6 @@
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "kek.h"
|
||||
|
||||
@@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000;
|
||||
bool needs_power_cycle = false;
|
||||
static mbedtls_ecdh_context hkey;
|
||||
static bool hkey_init = false;
|
||||
extern int encrypt_keydev_f1(const uint8_t keydev[32]);
|
||||
|
||||
int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||
paut.user_present = userIsPresent;
|
||||
@@ -105,11 +106,7 @@ int regenerate() {
|
||||
mbedtls_ecdh_init(&hkey);
|
||||
hkey_init = true;
|
||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
&hkey.ctx.mbed_ecdh.Q,
|
||||
random_gen,
|
||||
NULL);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
|
||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
@@ -125,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
||||
buf,
|
||||
sizeof(buf),
|
||||
sharedSecret);
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
ret = mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 HMAC key",
|
||||
14,
|
||||
sharedSecret,
|
||||
32);
|
||||
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 AES key",
|
||||
13,
|
||||
sharedSecret + 32,
|
||||
32);
|
||||
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -160,26 +138,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
|
||||
mbedtls_mpi z;
|
||||
mbedtls_mpi_init(&z);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
|
||||
&z,
|
||||
Q,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
random_gen,
|
||||
NULL);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
|
||||
ret = kdf(protocol, &z, sharedSecret);
|
||||
mbedtls_mpi_free(&z);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int resetPinUvAuthToken() {
|
||||
void resetAuthToken(bool persistent) {
|
||||
uint16_t fid = EF_AUTHTOKEN;
|
||||
if (persistent) {
|
||||
fid = EF_PAUTHTOKEN;
|
||||
}
|
||||
file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
|
||||
uint8_t t[32];
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
file_put_data(ef_authtoken, t, sizeof(t));
|
||||
file_put_data(ef, t, sizeof(t));
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
int resetPinUvAuthToken() {
|
||||
resetAuthToken(false);
|
||||
paut.permissions = 0;
|
||||
paut.data = file_get_data(ef_authtoken);
|
||||
paut.len = file_get_size(ef_authtoken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
low_flash_available();
|
||||
int resetPersistentPinUvAuthToken() {
|
||||
resetAuthToken(true);
|
||||
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
|
||||
ppaut.permissions = 0;
|
||||
ppaut.data = file_get_data(ef_pauthtoken);
|
||||
ppaut.len = file_get_size(ef_pauthtoken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
|
||||
return -1;
|
||||
}
|
||||
|
||||
int authenticate(uint8_t protocol,
|
||||
const uint8_t *key,
|
||||
const uint8_t *data,
|
||||
size_t len,
|
||||
uint8_t *sign) {
|
||||
int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
|
||||
uint8_t hmac[32];
|
||||
int ret =
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||
@@ -242,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return memcmp(sign, hmac, 16);
|
||||
return ct_memcmp(sign, hmac, 16);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
return memcmp(sign, hmac, 32);
|
||||
return ct_memcmp(sign, hmac, 32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -280,17 +266,14 @@ int pinUvAuthTokenUsageTimerObserver() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_mkek_encrypted(const uint8_t *dhash) {
|
||||
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
|
||||
hash_multi(dhash, 16, session_pin); // Only for storing MKEK
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
int ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return CTAP2_ERR_PIN_AUTH_INVALID;
|
||||
}
|
||||
int check_keydev_encrypted(const uint8_t pin_token[32]) {
|
||||
if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
|
||||
uint8_t tmp_keydev[61];
|
||||
tmp_keydev[0] = 0x02; // Change format to encrypted
|
||||
encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
|
||||
file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
|
||||
mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
|
||||
low_flash_available();
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -305,11 +288,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CborEncoder encoder, mapEncoder;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
|
||||
kay = { 0 };
|
||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
|
||||
CborCharString rpId = { 0 };
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
uint8_t keydev[32] = {0};
|
||||
if (hkey_init == false) {
|
||||
initialize();
|
||||
}
|
||||
@@ -431,15 +414,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
uint8_t hsh[34], dhash[32];
|
||||
uint8_t hsh[35], dhash[32];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
hsh[1] = pin_len;
|
||||
hsh[2] = 1; // New format indicator
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
pin_derive_verifier(dhash, 16, hsh + 3);
|
||||
file_put_data(ef_pin, hsh, sizeof(hsh));
|
||||
low_flash_available();
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
pin_derive_session(dhash, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
@@ -486,10 +471,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
uint8_t pin_data[35];
|
||||
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64];
|
||||
@@ -498,9 +483,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
uint8_t dhash[32], off = 3;
|
||||
if (file_get_size(ef_pin) == 34) {
|
||||
off = 2;
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
}
|
||||
else {
|
||||
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||
}
|
||||
|
||||
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
@@ -514,10 +506,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
if (off == 2) {
|
||||
// Upgrade pin file to new format
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
|
||||
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
}
|
||||
pin_derive_session(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
new_pin_mismatches = 0;
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
@@ -539,35 +549,33 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
uint8_t hsh[34];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
hsh[1] = pin_len;
|
||||
|
||||
// New PIN is valid and verified
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
|
||||
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
|
||||
pin_derive_session(dhash, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
low_flash_available();
|
||||
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
pin_data[1] = pin_len;
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(dhash, 16, pin_data + 3);
|
||||
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
ret = load_mkek(mkek);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(dhash, 16, session_pin);
|
||||
ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
|
||||
@@ -578,6 +586,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
}
|
||||
low_flash_available();
|
||||
resetPinUvAuthToken();
|
||||
resetPersistentPinUvAuthToken();
|
||||
goto err; // No return
|
||||
}
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
||||
@@ -598,7 +607,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
}
|
||||
|
||||
if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
}
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
@@ -618,10 +629,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
uint8_t pin_data[35];
|
||||
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
|
||||
@@ -630,11 +641,18 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
uint8_t dhash[32], off = 3;
|
||||
if (file_get_size(ef_pin) == 34) {
|
||||
off = 2;
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
}
|
||||
else {
|
||||
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||
}
|
||||
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
if (retries == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
@@ -646,39 +664,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
|
||||
ret = check_mkek_encrypted(paddedNewPin);
|
||||
if (off == 2) {
|
||||
// Upgrade pin file to new format
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
}
|
||||
|
||||
pin_derive_session(paddedNewPin, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
new_pin_mismatches = 0;
|
||||
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
|
||||
low_flash_available();
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
if (subcommand == 0x05) {
|
||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
||||
}
|
||||
paut.permissions = (uint8_t)permissions;
|
||||
if (rpId.present == true) {
|
||||
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
|
||||
paut.has_rp_id = true;
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
|
||||
if (permissions & CTAP_PERMISSION_PCMR) {
|
||||
ppaut.permissions = CTAP_PERMISSION_PCMR;
|
||||
pdata = ppaut.data;
|
||||
}
|
||||
else {
|
||||
paut.has_rp_id = false;
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
if (subcommand == 0x05) {
|
||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
||||
}
|
||||
paut.permissions = (uint8_t)permissions;
|
||||
if (rpId.present == true) {
|
||||
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
|
||||
paut.has_rp_id = true;
|
||||
}
|
||||
else {
|
||||
paut.has_rp_id = false;
|
||||
}
|
||||
pdata = paut.data;
|
||||
}
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
|
||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
|
||||
@@ -695,6 +733,7 @@ err:
|
||||
CBOR_FREE_BYTE_STRING(kax);
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(rpId);
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
@@ -22,7 +23,6 @@
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
@@ -31,14 +31,16 @@
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
extern void resetPersistentPinUvAuthToken();
|
||||
extern void resetPinUvAuthToken();
|
||||
|
||||
int cbor_config(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
|
||||
CborCharString minPinLengthRPIDs[32] = { 0 };
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
|
||||
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
|
||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||
CborEncoder encoder;
|
||||
//CborEncoder mapEncoder;
|
||||
@@ -66,13 +68,19 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
if (subcommand == 0x7f) { // Config Aut
|
||||
if (subcommand == 0xFF) { // Vendor
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
|
||||
CBOR_FIELD_GET_BYTES(vendorParamByteString, 2);
|
||||
}
|
||||
else if (subpara == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
|
||||
}
|
||||
else if (subpara == 0x04) {
|
||||
CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x03) { // Extensions
|
||||
@@ -95,15 +103,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x1B) { // PHY
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_UINT(vendorParam, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||
@@ -122,9 +121,12 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
if (pinUvAuthParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
|
||||
memset(verify_payload, 0xff, 32);
|
||||
@@ -141,8 +143,14 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
if (subcommand == 0x7f) {
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
|
||||
if (subcommand == 0xFF) {
|
||||
#ifndef ENABLE_EMULATION
|
||||
const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_OPTS);
|
||||
#endif
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
|
||||
if (!file_has_data(ef_keydev_enc)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
@@ -163,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
|
||||
int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
@@ -171,7 +179,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
uint8_t key_dev_enc[12 + 32 + 16];
|
||||
random_gen(NULL, key_dev_enc, 12);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data);
|
||||
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0) {
|
||||
@@ -184,9 +192,58 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
|
||||
phy_data.vid = (vendorParamInt >> 16) & 0xFFFF;
|
||||
phy_data.pid = vendorParamInt & 0xFFFF;
|
||||
phy_data.vidpid_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
|
||||
phy_data.led_gpio = (uint8_t)vendorParamInt;
|
||||
phy_data.led_gpio_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
|
||||
phy_data.led_brightness = (uint8_t)vendorParamInt;
|
||||
phy_data.led_brightness_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
|
||||
phy_data.opts = (uint16_t)vendorParamInt;
|
||||
}
|
||||
#endif
|
||||
else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
|
||||
if (vendorParamByteString.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (ef_ee_ea) {
|
||||
file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len);
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
|
||||
file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
|
||||
if (ef_pin_policy) {
|
||||
uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
|
||||
if (val) {
|
||||
// Not ready yet for integer param
|
||||
// val[0] = (uint8_t)(vendorParamInt >> 8);
|
||||
// val[1] = (uint8_t)(vendorParamInt & 0xFF);
|
||||
memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
|
||||
file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len);
|
||||
free(val);
|
||||
}
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (is_phy && phy_save() != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
#endif
|
||||
goto err;
|
||||
}
|
||||
else if (subcommand == 0x03) {
|
||||
@@ -207,6 +264,10 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
|
||||
forceChangePin = ptrue;
|
||||
}
|
||||
if (forceChangePin) {
|
||||
resetPersistentPinUvAuthToken();
|
||||
resetPinUvAuthToken();
|
||||
}
|
||||
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
||||
dataf[0] = (uint8_t)newMinPinLength;
|
||||
dataf[1] = forceChangePin == ptrue ? 1 : 0;
|
||||
@@ -222,35 +283,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
set_opts(get_opts() | FIDO2_OPT_EA);
|
||||
goto err;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (subcommand == 0x1B) {
|
||||
if (vendorParam == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
|
||||
phy_data.vid = (vendorParam >> 16) & 0xFFFF;
|
||||
phy_data.pid = vendorParam & 0xFFFF;
|
||||
phy_data.vidpid_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
|
||||
phy_data.led_gpio = (uint8_t)vendorParam;
|
||||
phy_data.led_gpio_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
|
||||
phy_data.led_brightness = (uint8_t)vendorParam;
|
||||
phy_data.led_brightness_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
|
||||
phy_data.opts = (uint16_t)vendorParam;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
if (phy_save() != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
@@ -259,7 +291,8 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorAutCt);
|
||||
CBOR_FREE_BYTE_STRING(vendorParamByteString);
|
||||
CBOR_FREE_BYTE_STRING(vendorParamTextString);
|
||||
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -22,7 +23,6 @@
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
uint8_t rp_counter = 1;
|
||||
uint8_t rp_total = 0;
|
||||
@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
if (strcmp(_fd3, "transports") == 0) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
|
||||
transports_len], 4);
|
||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
|
||||
credentialId.transports_len++;
|
||||
}
|
||||
CBOR_PARSE_ARRAY_END(_f3, 4);
|
||||
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
if (subcommand == 0x01) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
uint8_t existing = 0;
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
else if (subcommand == 0x02 || subcommand == 0x03) {
|
||||
file_t *rp_ef = NULL;
|
||||
if (subcommand == 0x02) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
rp_counter = 1;
|
||||
rp_total = 0;
|
||||
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
if (subcommand == 0x04) {
|
||||
*(raw_subpara - 1) = 0x04;
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
cred_counter = 1;
|
||||
cred_total = 0;
|
||||
@@ -239,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
Credential cred = { 0 };
|
||||
if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) {
|
||||
if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@@ -296,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(cred.id.data, cred.id.len, cred_idr);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
@@ -352,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (delete_file(ef) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
@@ -394,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
Credential cred = { 0 };
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) {
|
||||
if (credential_load_resident(ef, rp_id_hash, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
|
||||
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t newcred[MAX_CRED_ID_LENGTH];
|
||||
size_t newcred_len = 0;
|
||||
uint16_t newcred_len = 0;
|
||||
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
|
||||
&user.displayName, &cred.opts, &cred.extensions,
|
||||
cred.use_sign_count, (int)cred.alg,
|
||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||
&user.displayName, &cred.opts, &cred.extensions,
|
||||
cred.use_sign_count, (int)cred.alg,
|
||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||
credential_free(&cred);
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@@ -3,28 +3,28 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "files.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "credential.h"
|
||||
@@ -295,27 +295,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
else {
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
// Even we provide allowList, we need to check if the credential is resident
|
||||
if (!resident) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
// Should never happen
|
||||
credential_free(&creds[creds_len]);
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
|
||||
resident = true;
|
||||
resident = true;
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
else {
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
// Even we provide allowList, we need to check if the credential is resident
|
||||
if (!resident) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
|
||||
resident = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resident) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resident) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
|
||||
int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]);
|
||||
if (ret != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
@@ -343,8 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
}
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
|
||||
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
}
|
||||
else {
|
||||
@@ -375,8 +395,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,10 +463,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
else {
|
||||
selcred = &creds[0];
|
||||
if (resident && allowList_len > 1) {
|
||||
numberOfCredentials = 1;
|
||||
}
|
||||
if (numberOfCredentials > 1) {
|
||||
asserted = true;
|
||||
residentx = resident;
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
credential_free(&credsx[i]);
|
||||
}
|
||||
for (int i = 0; i < numberOfCredentials; i++) {
|
||||
credsx[i] = creds[i];
|
||||
}
|
||||
numberOfCredentialsx = numberOfCredentials;
|
||||
@@ -633,7 +675,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
if (selcred) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
|
||||
if (resident) {
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
|
||||
}
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
|
||||
@@ -660,8 +709,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
|
||||
selcred->userId.len));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
|
||||
if (numberOfCredentials > 1 && allowList_len == 0) {
|
||||
if (selcred->userName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
@@ -27,22 +28,36 @@ int cbor_get_info() {
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
|
||||
uint8_t lfields = 14;
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (phy_data.vid != 0x1050) {
|
||||
lfields++;
|
||||
}
|
||||
#else
|
||||
lfields++;
|
||||
#endif
|
||||
file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_pin_policy)) {
|
||||
lfields += 2;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
@@ -150,13 +165,36 @@ int cbor_get_info() {
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (phy_data.vid != 0x1050) {
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
uint8_t enabled_cmds = 4;
|
||||
#ifndef ENABLE_EMULATION
|
||||
enabled_cmds += 4;
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
|
||||
#ifndef ENABLE_EMULATION
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS));
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
#ifndef ENABLE_EMULATION
|
||||
}
|
||||
if (file_has_data(ef_pin_policy)) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
err:
|
||||
if (error != CborNoError) {
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
static uint64_t expectedLength = 0, expectedNextOffset = 0;
|
||||
@@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
|
||||
uint8_t verify_data[70] = { 0 };
|
||||
memset(verify_data, 0xff, 32);
|
||||
verify_data[32] = 0x0C;
|
||||
put_uint32_t_le(offset, verify_data + 34);
|
||||
put_uint32_t_le((uint32_t)offset, verify_data + 34);
|
||||
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -25,7 +26,7 @@
|
||||
#include "credential.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "random.h"
|
||||
#include "pico_keys.h"
|
||||
#include "crypto_utils.h"
|
||||
|
||||
int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
@@ -39,7 +40,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
size_t excludeList_len = 0;
|
||||
CredOptions options = { 0 };
|
||||
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
|
||||
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1;
|
||||
int64_t kty = 2, hmac_alg = 0, crv = 0;
|
||||
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
||||
bool hmac_secret_mc = false;
|
||||
const bool *pin_complexity_policy = NULL;
|
||||
uint8_t *aut_data = NULL;
|
||||
size_t resp_size = 0;
|
||||
CredExtensions extensions = { 0 };
|
||||
@@ -127,12 +132,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
if (strcmp(_fd2, "hmac-secret-mc") == 0) {
|
||||
hmac_secret_mc = true;
|
||||
uint64_t ukey = 0;
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_UINT(ukey, 3);
|
||||
if (ukey == 0x01) {
|
||||
CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay));
|
||||
}
|
||||
else if (ukey == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(salt_enc, 3);
|
||||
}
|
||||
else if (ukey == 0x03) {
|
||||
CBOR_FIELD_GET_BYTES(salt_auth, 3);
|
||||
}
|
||||
else if (ukey == 0x04) {
|
||||
CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
|
||||
}
|
||||
else {
|
||||
CBOR_ADVANCE(3);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
continue;
|
||||
}
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
|
||||
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -202,21 +234,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
|
||||
}
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P256;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P384;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P521;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP256R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP384R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP512R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
|
||||
#ifndef ENABLE_EMULATION
|
||||
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
|
||||
@@ -227,11 +274,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
}
|
||||
}
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_ED25519;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_ED448;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
|
||||
// pass
|
||||
@@ -298,12 +350,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
continue;
|
||||
}
|
||||
Credential ecred = {0};
|
||||
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))) {
|
||||
if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
uint8_t *cred_idr = file_get_data(ef_cred) + 32;
|
||||
if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
|
||||
credential_free(&ecred);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
|
||||
credential_free(&ecred);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
}
|
||||
credential_free(&ecred);
|
||||
}
|
||||
@@ -313,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (hmac_secret_mc && extensions.hmac_secret != ptrue) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
if (options.up == ptrue || options.up == NULL) { //14.1
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (getUserPresentFlagValue() == false) {
|
||||
@@ -332,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
|
||||
|
||||
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
|
||||
size_t cred_id_len = 0;
|
||||
uint16_t cred_id_len = 0;
|
||||
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options,
|
||||
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
|
||||
cred_id, &cred_id_len));
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
|
||||
|
||||
if (getUserVerifiedFlagValue()) {
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
@@ -372,6 +440,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (extensions.credBlob.present == true) {
|
||||
l++;
|
||||
}
|
||||
if (hmac_secret_mc) {
|
||||
l++;
|
||||
}
|
||||
if (pin_complexity_policy == ptrue) {
|
||||
l++;
|
||||
}
|
||||
if (l > 0) {
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credBlob.present == true) {
|
||||
@@ -383,15 +457,69 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
}
|
||||
if (extensions.hmac_secret == ptrue) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
}
|
||||
if (minPinLen > 0) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||
}
|
||||
if (hmac_secret_mc) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
|
||||
|
||||
uint8_t sharedSecret[64] = {0};
|
||||
mbedtls_ecp_point Qp;
|
||||
mbedtls_ecp_point_init(&Qp);
|
||||
mbedtls_mpi_lset(&Qp.Z, 1);
|
||||
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
|
||||
}
|
||||
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
|
||||
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t cred_random[64] = {0}, *crd = NULL;
|
||||
ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (flags & FIDO2_AUT_FLAG_UV) {
|
||||
crd = cred_random + 32;
|
||||
}
|
||||
else {
|
||||
crd = cred_random;
|
||||
}
|
||||
uint8_t out1[64] = {0}, hmac_res[80] = {0};
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
|
||||
if ((uint8_t)salt_enc.len == 64 + poff) {
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
|
||||
}
|
||||
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
||||
}
|
||||
if (pin_complexity_policy == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
|
||||
file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||
@@ -417,15 +545,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
|
||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
|
||||
|
||||
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
|
||||
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len;
|
||||
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
|
||||
uint8_t *pa = aut_data;
|
||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||
*pa++ = flags;
|
||||
pa += put_uint32_t_be(ctr, pa);
|
||||
memcpy(pa, aaguid, 16); pa += 16;
|
||||
pa += put_uint16_t_be(cred_id_len, pa);
|
||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
||||
if (options.rk == ptrue) {
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
pa += put_uint16_t_be(sizeof(cred_idr), pa);
|
||||
credential_derive_resident(cred_id, cred_id_len, cred_idr);
|
||||
memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr);
|
||||
}
|
||||
else {
|
||||
pa += put_uint16_t_be(cred_id_len, pa);
|
||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
||||
}
|
||||
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
|
||||
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
|
||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||
@@ -436,14 +572,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
||||
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
|
||||
}
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
}
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
|
||||
md = NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
#ifndef ENABLE_EMULATION
|
||||
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
|
||||
if (memcmp(p, "CommissionProfile", 17) == 0) {
|
||||
ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data);
|
||||
ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data);
|
||||
if (ret == PICOKEY_OK) {
|
||||
ret = phy_save();
|
||||
}
|
||||
@@ -519,12 +655,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
||||
if (self_attestation == false || is_nitrokey) {
|
||||
if (self_attestation == false || is_nk) {
|
||||
CborEncoder arrEncoder;
|
||||
file_t *ef_cert = NULL;
|
||||
if (enterpriseAttestation == 2) {
|
||||
@@ -569,6 +705,10 @@ err:
|
||||
CBOR_FREE_BYTE_STRING(user.id);
|
||||
CBOR_FREE_BYTE_STRING(user.displayName);
|
||||
CBOR_FREE_BYTE_STRING(user.parent.name);
|
||||
CBOR_FREE_BYTE_STRING(kax);
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(salt_enc);
|
||||
CBOR_FREE_BYTE_STRING(salt_auth);
|
||||
if (extensions.present == true) {
|
||||
CBOR_FREE_BYTE_STRING(extensions.credBlob);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CBOR_MAKE_CREDENTIAL_H_
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "file.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
|
||||
|
||||
@@ -3,25 +3,25 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
@@ -237,25 +237,14 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
|
||||
}
|
||||
else if (vendorCmd == 0x02) {
|
||||
if (vendorParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (ef_ee_ea) {
|
||||
file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len);
|
||||
}
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
|
||||
if (vendorCmd == 0x01) {
|
||||
uint16_t opts = 0;
|
||||
if (file_has_data(ef_phy)) {
|
||||
uint8_t *data = file_get_data(ef_phy);
|
||||
opts = get_uint16_t_be(data + PHY_OPTS);
|
||||
uint8_t *pdata = file_get_data(ef_phy);
|
||||
opts = get_uint16_t_be(pdata + PHY_OPTS);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
@@ -26,7 +26,7 @@
|
||||
int cmd_authenticate() {
|
||||
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *) apdu.data;
|
||||
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *) res_APDU;
|
||||
//if (scan_files(true) != PICOKEY_OK)
|
||||
//if (scan_files_fido(true) != PICOKEY_OK)
|
||||
// return SW_EXEC_ERROR();
|
||||
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
|
||||
return SW_WRONG_DATA();
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
@@ -59,7 +59,7 @@ int cmd_register() {
|
||||
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *) res_APDU;
|
||||
resp->registerId = CTAP_REGISTER_ID;
|
||||
resp->keyHandleLen = KEY_HANDLE_LEN;
|
||||
//if (scan_files(true) != PICOKEY_OK)
|
||||
//if (scan_files_fido(true) != PICOKEY_OK)
|
||||
// return SW_EXEC_ERROR();
|
||||
if (apdu.nc != CTAP_APPID_SIZE + CTAP_CHAL_SIZE) {
|
||||
return SW_WRONG_LENGTH();
|
||||
@@ -69,11 +69,7 @@ int cmd_register() {
|
||||
}
|
||||
if (memcmp(req->appId, bogus_firefox,
|
||||
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
|
||||
#ifndef ENABLE_EMULATION
|
||||
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
|
||||
#else
|
||||
{ return SW_DATA_INVALID(); }
|
||||
#endif
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "apdu.h"
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "credential.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -26,7 +27,6 @@
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
#include "files.h"
|
||||
#include "pico_keys.h"
|
||||
#include "otp.h"
|
||||
|
||||
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
|
||||
@@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId,
|
||||
int alg,
|
||||
int curve,
|
||||
uint8_t *cred_id,
|
||||
size_t *cred_id_len) {
|
||||
uint16_t *cred_id_len) {
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
uint8_t rp_id_hash[32];
|
||||
@@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId,
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
|
||||
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
|
||||
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
|
||||
uint8_t key[32] = {0};
|
||||
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
|
||||
uint8_t iv[CRED_IV_LEN] = {0};
|
||||
@@ -314,7 +314,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
|
||||
ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred);
|
||||
if (ret != 0) {
|
||||
credential_free(&rcred);
|
||||
continue;
|
||||
@@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
if (sloti == -1) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(cred_id, cred_id_len, cred_idr);
|
||||
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN);
|
||||
memcpy(data, rp_id_hash, 32);
|
||||
memcpy(data + 32, cred_id, cred_id_len);
|
||||
memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN);
|
||||
memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len);
|
||||
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
|
||||
file_put_data(ef, data, (uint16_t)cred_id_len + 32);
|
||||
file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
|
||||
free(data);
|
||||
|
||||
if (new_record == true) { //increase rps
|
||||
@@ -421,3 +424,33 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len,
|
||||
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
||||
memset(outk, 0, CRED_RESIDENT_LEN);
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN;
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk);
|
||||
memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN);
|
||||
outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00;
|
||||
outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00;
|
||||
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) {
|
||||
if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0;
|
||||
}
|
||||
|
||||
int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) {
|
||||
if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) {
|
||||
return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred);
|
||||
}
|
||||
return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,23 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CREDENTIAL_H_
|
||||
#define _CREDENTIAL_H_
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "file.h"
|
||||
|
||||
typedef struct CredOptions {
|
||||
const bool *rk;
|
||||
@@ -58,6 +59,7 @@ typedef struct Credential {
|
||||
|
||||
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
|
||||
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
|
||||
#define CRED_PROTO_23_S "\xf1\xd0\x02\x03"
|
||||
|
||||
#define CRED_PROTO CRED_PROTO_22_S
|
||||
|
||||
@@ -66,6 +68,11 @@ typedef struct Credential {
|
||||
#define CRED_TAG_LEN 16
|
||||
#define CRED_SILENT_TAG_LEN 16
|
||||
|
||||
#define CRED_PROTO_RESIDENT CRED_PROTO_23_S
|
||||
#define CRED_PROTO_RESIDENT_LEN 4
|
||||
#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 6)
|
||||
#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CRED_PROTO_21 = 0x01,
|
||||
@@ -83,7 +90,7 @@ extern int credential_create(CborCharString *rpId,
|
||||
int alg,
|
||||
int curve,
|
||||
uint8_t *cred_id,
|
||||
size_t *cred_id_len);
|
||||
uint16_t *cred_id_len);
|
||||
extern void credential_free(Credential *cred);
|
||||
extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
|
||||
extern int credential_load(const uint8_t *cred_id,
|
||||
@@ -94,5 +101,8 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len
|
||||
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
|
||||
size_t cred_id_len,
|
||||
uint8_t *outk);
|
||||
extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
|
||||
extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len);
|
||||
extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred);
|
||||
|
||||
#endif // _CREDENTIAL_H_
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CTAP_H_
|
||||
@@ -114,10 +114,14 @@ typedef struct {
|
||||
|
||||
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
||||
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
||||
#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
|
||||
#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897
|
||||
#ifndef ENABLE_EMULATION
|
||||
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
|
||||
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
|
||||
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
|
||||
#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f
|
||||
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
|
||||
#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f
|
||||
#endif
|
||||
|
||||
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
|
||||
|
||||
@@ -134,6 +138,7 @@ typedef struct {
|
||||
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
|
||||
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
|
||||
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
|
||||
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
|
||||
|
||||
typedef struct mse {
|
||||
uint8_t Qpt[65];
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _CTAP2_CBOR_H_
|
||||
|
||||
24
src/fido/defs.c
Normal file
24
src/fido/defs.c
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "version.h"
|
||||
|
||||
uint8_t PICO_PRODUCT = 2; // Pico FIDO
|
||||
uint8_t PICO_VERSION_MAJOR = PICO_FIDO_VERSION_MAJOR;
|
||||
uint8_t PICO_VERSION_MINOR = PICO_FIDO_VERSION_MINOR;
|
||||
151
src/fido/fido.c
151
src/fido/fido.c
@@ -3,21 +3,21 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "kek.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
@@ -25,10 +25,10 @@
|
||||
#include "random.h"
|
||||
#include "mbedtls/x509_crt.h"
|
||||
#include "mbedtls/hkdf.h"
|
||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
||||
#if defined(USB_ITF_CCID)
|
||||
#include "ccid/ccid.h"
|
||||
#endif
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include <math.h>
|
||||
@@ -41,9 +41,8 @@
|
||||
int fido_process_apdu();
|
||||
int fido_unload();
|
||||
|
||||
uint8_t PICO_PRODUCT = 2; // Pico FIDO
|
||||
|
||||
pinUvAuthToken_t paut = { 0 };
|
||||
persistentPinUvAuthToken_t ppaut = { 0 };
|
||||
|
||||
uint8_t keydev_dec[32];
|
||||
bool has_keydev_dec = false;
|
||||
@@ -120,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
||||
return MBEDTLS_ECP_DP_ED448;
|
||||
}
|
||||
#endif
|
||||
else if (curve == FIDO2_CURVE_BP256R1) {
|
||||
return MBEDTLS_ECP_DP_BP256R1;
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_BP384R1) {
|
||||
return MBEDTLS_ECP_DP_BP384R1;
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_BP512R1) {
|
||||
return MBEDTLS_ECP_DP_BP512R1;
|
||||
}
|
||||
return MBEDTLS_ECP_DP_NONE;
|
||||
}
|
||||
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
||||
@@ -160,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
|
||||
uint8_t key_path[KEY_PATH_LEN];
|
||||
memcpy(key_path, cred_id, KEY_PATH_LEN);
|
||||
*(uint32_t *) key_path = 0x80000000 | 10022;
|
||||
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
|
||||
}
|
||||
return derive_key(NULL, false, key_path, mbedtls_curve, key);
|
||||
@@ -196,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
||||
return ret;
|
||||
}
|
||||
|
||||
int load_keydev(uint8_t *key) {
|
||||
int load_keydev(uint8_t key[32]) {
|
||||
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
|
||||
return PICOKEY_ERR_MEMORY_FATAL;
|
||||
}
|
||||
@@ -205,13 +213,39 @@ int load_keydev(uint8_t *key) {
|
||||
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
||||
}
|
||||
else {
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
|
||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
uint16_t fid_size = file_get_size(ef_keydev);
|
||||
if (fid_size == 32) {
|
||||
memcpy(key, file_get_data(ef_keydev), 32);
|
||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
}
|
||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
else if (fid_size == 33 || fid_size == 61) {
|
||||
uint8_t format = *file_get_data(ef_keydev);
|
||||
if (format == 0x01 || format == 0x02) { // Format indicator
|
||||
if (format == 0x02) {
|
||||
uint8_t tmp_key[61];
|
||||
memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
|
||||
int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
memcpy(key, file_get_data(ef_keydev) + 1, 32);
|
||||
}
|
||||
uint8_t kbase[32];
|
||||
derive_kbase(kbase);
|
||||
int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32);
|
||||
if (ret != PICOKEY_OK) {
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +253,7 @@ int load_keydev(uint8_t *key) {
|
||||
}
|
||||
|
||||
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) {
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
|
||||
if (!(k & 0x80000000)) {
|
||||
return -1;
|
||||
@@ -260,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
return r;
|
||||
}
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
if (new_key == true) {
|
||||
uint32_t val = 0;
|
||||
random_gen(NULL, (uint8_t *) &val, sizeof(val));
|
||||
@@ -307,7 +341,24 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
return r;
|
||||
}
|
||||
|
||||
int scan_files() {
|
||||
int encrypt_keydev_f1(const uint8_t keydev[32]) {
|
||||
uint8_t kdata[33] = {0};
|
||||
kdata[0] = 0x01; // Format indicator
|
||||
memcpy(kdata + 1, keydev, 32);
|
||||
uint8_t kbase[32];
|
||||
derive_kbase(kbase);
|
||||
int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32);
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
}
|
||||
ret = file_put_data(ef_keydev, kdata, 33);
|
||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||
low_flash_available();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scan_files_fido() {
|
||||
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
||||
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
||||
ef_mkek = search_by_fid(EF_MKEK, NULL, SPECIFY_EF);
|
||||
@@ -322,17 +373,16 @@ int scan_files() {
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
return ret;
|
||||
}
|
||||
uint8_t kdata[64];
|
||||
uint8_t keydev[32] = {0};
|
||||
size_t key_size = 0;
|
||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
|
||||
if (ret != 0 || key_size != 32) {
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (otp_key_1) {
|
||||
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
|
||||
}
|
||||
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
|
||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||
encrypt_keydev_f1(keydev);
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
@@ -343,21 +393,6 @@ int scan_files() {
|
||||
else {
|
||||
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
|
||||
}
|
||||
if (ef_mkek) { // No encrypted MKEK
|
||||
if (!file_has_data(ef_mkek)) {
|
||||
uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE];
|
||||
random_gen(NULL, mkek, sizeof(mkek));
|
||||
file_put_data(ef_mkek, mkek, sizeof(mkek));
|
||||
int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != 0) {
|
||||
printf("FATAL ERROR: MKEK encryption failed!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: MKEK not found in memory!\r\n");
|
||||
}
|
||||
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
|
||||
if (ef_certdev) {
|
||||
if (!file_has_data(ef_certdev)) {
|
||||
@@ -401,13 +436,6 @@ int scan_files() {
|
||||
printf("FATAL ERROR: Global counter not found in memory!\r\n");
|
||||
}
|
||||
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
|
||||
if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
|
||||
uint8_t pin_data[34] = { 0 }, dhash[32];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 18);
|
||||
double_hash_pin(pin_data + 2, 16, dhash);
|
||||
memcpy(pin_data + 2, dhash, 32);
|
||||
file_put_data(ef_pin, pin_data, 34);
|
||||
}
|
||||
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
|
||||
if (ef_authtoken) {
|
||||
if (!file_has_data(ef_authtoken)) {
|
||||
@@ -421,6 +449,19 @@ int scan_files() {
|
||||
else {
|
||||
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
||||
}
|
||||
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
|
||||
if (ef_pauthtoken) {
|
||||
if (!file_has_data(ef_pauthtoken)) {
|
||||
uint8_t t[32];
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
file_put_data(ef_pauthtoken, t, sizeof(t));
|
||||
}
|
||||
ppaut.data = file_get_data(ef_pauthtoken);
|
||||
ppaut.len = file_get_size(ef_pauthtoken);
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
|
||||
}
|
||||
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_largeblob)) {
|
||||
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
|
||||
@@ -432,18 +473,20 @@ int scan_files() {
|
||||
|
||||
void scan_all() {
|
||||
scan_flash();
|
||||
scan_files();
|
||||
scan_files_fido();
|
||||
}
|
||||
|
||||
extern void init_otp();
|
||||
void init_fido() {
|
||||
scan_all();
|
||||
#ifdef ENABLE_OTP_APP
|
||||
init_otp();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool wait_button_pressed() {
|
||||
uint32_t val = EV_PRESS_BUTTON;
|
||||
#ifndef ENABLE_EMULATION
|
||||
#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
|
||||
queue_try_add(&card_to_usb_q, &val);
|
||||
do {
|
||||
queue_remove_blocking(&usb_to_card_q, &val);
|
||||
@@ -487,11 +530,13 @@ extern int cmd_register();
|
||||
extern int cmd_authenticate();
|
||||
extern int cmd_version();
|
||||
extern int cbor_parse(int, uint8_t *, size_t);
|
||||
extern void driver_init_hid();
|
||||
|
||||
#define CTAP_CBOR 0x10
|
||||
|
||||
int cmd_cbor() {
|
||||
uint8_t *old_buf = res_APDU;
|
||||
driver_init_hid();
|
||||
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _FIDO_H_
|
||||
#define _FIDO_H_
|
||||
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#ifndef ESP_PLATFORM
|
||||
@@ -31,11 +31,7 @@
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
#include "mbedtls/eddsa.h"
|
||||
#endif
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "hid/ctap_hid.h"
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define CTAP_PUBKEY_LEN (65)
|
||||
#define KEY_PATH_LEN (32)
|
||||
@@ -43,7 +39,7 @@
|
||||
#define SHA256_DIGEST_LENGTH (32)
|
||||
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
|
||||
|
||||
extern int scan_files();
|
||||
extern int scan_files_fido();
|
||||
extern int derive_key(const uint8_t *app_id,
|
||||
bool new_key,
|
||||
uint8_t *key_handle,
|
||||
@@ -55,20 +51,28 @@ extern void init_fido();
|
||||
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
|
||||
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
|
||||
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key);
|
||||
extern int load_keydev(uint8_t *key);
|
||||
extern int load_keydev(uint8_t key[32]);
|
||||
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
|
||||
|
||||
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
|
||||
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256
|
||||
#define FIDO2_ALG_EDDSA -8 //EdDSA
|
||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
|
||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
|
||||
#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
|
||||
#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519
|
||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384
|
||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512
|
||||
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
|
||||
#define FIDO2_ALG_ES256K -47
|
||||
#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384
|
||||
#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521
|
||||
#define FIDO2_ALG_ED448 -53 //EDDSA Ed448
|
||||
#define FIDO2_ALG_RS256 -257
|
||||
#define FIDO2_ALG_RS384 -258
|
||||
#define FIDO2_ALG_RS512 -259
|
||||
#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1
|
||||
#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1
|
||||
#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1
|
||||
|
||||
#define FIDO2_CURVE_P256 1
|
||||
#define FIDO2_CURVE_P384 2
|
||||
@@ -78,6 +82,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
||||
#define FIDO2_CURVE_ED25519 6
|
||||
#define FIDO2_CURVE_ED448 7
|
||||
#define FIDO2_CURVE_P256K1 8
|
||||
#define FIDO2_CURVE_BP256R1 9
|
||||
#define FIDO2_CURVE_BP384R1 10
|
||||
#define FIDO2_CURVE_BP512R1 11
|
||||
|
||||
#define FIDO2_AUT_FLAG_UP 0x1
|
||||
#define FIDO2_AUT_FLAG_UV 0x4
|
||||
@@ -129,9 +136,17 @@ typedef struct pinUvAuthToken {
|
||||
bool user_verified;
|
||||
} pinUvAuthToken_t;
|
||||
|
||||
typedef struct persistentPinUvAuthToken {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
uint8_t permissions;
|
||||
} persistentPinUvAuthToken_t;
|
||||
|
||||
extern uint32_t user_present_time_limit;
|
||||
|
||||
extern pinUvAuthToken_t paut;
|
||||
extern persistentPinUvAuthToken_t ppaut;
|
||||
|
||||
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
|
||||
|
||||
extern uint8_t session_pin[32];
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "files.h"
|
||||
@@ -27,6 +27,7 @@ file_t file_entries[] = {
|
||||
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
|
||||
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
|
||||
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
|
||||
{ .fid = EF_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT AUTH TOKEN
|
||||
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
|
||||
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
|
||||
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _FILES_H_
|
||||
@@ -29,7 +29,9 @@
|
||||
#define EF_OPTS 0xC001
|
||||
#define EF_PIN 0x1080
|
||||
#define EF_AUTHTOKEN 0x1090
|
||||
#define EF_PAUTHTOKEN 0x1091
|
||||
#define EF_MINPINLEN 0x1100
|
||||
#define EF_PIN_COMPLEXITY_POLICY 0x1102
|
||||
#define EF_DEV_CONF 0x1122
|
||||
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
||||
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "stdlib.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#include "kek.h"
|
||||
@@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) {
|
||||
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
|
||||
}
|
||||
|
||||
int store_mkek(const uint8_t *mkek) {
|
||||
uint8_t tmp_mkek[MKEK_SIZE];
|
||||
if (mkek == NULL) {
|
||||
const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
}
|
||||
else {
|
||||
memcpy(tmp_mkek, mkek, MKEK_SIZE);
|
||||
}
|
||||
if (otp_key_1) {
|
||||
mkek_masked(tmp_mkek, otp_key_1);
|
||||
}
|
||||
*(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE);
|
||||
uint8_t tmp_mkek_pin[MKEK_SIZE];
|
||||
memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE);
|
||||
file_t *tf = search_file(EF_MKEK);
|
||||
if (!tf) {
|
||||
release_mkek(tmp_mkek);
|
||||
release_mkek(tmp_mkek_pin);
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
|
||||
file_put_data(tf, tmp_mkek_pin, MKEK_SIZE);
|
||||
release_mkek(tmp_mkek_pin);
|
||||
low_flash_available();
|
||||
release_mkek(tmp_mkek);
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
int mkek_encrypt(uint8_t *data, uint16_t len) {
|
||||
int r;
|
||||
uint8_t mkek[MKEK_SIZE + 4];
|
||||
if ((r = load_mkek(mkek)) != PICOKEY_OK) {
|
||||
return r;
|
||||
}
|
||||
r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
|
||||
release_mkek(mkek);
|
||||
return r;
|
||||
}
|
||||
|
||||
int mkek_decrypt(uint8_t *data, uint16_t len) {
|
||||
int r;
|
||||
uint8_t mkek[MKEK_SIZE + 4];
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _KEK_H_
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap2_cbor.h"
|
||||
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "version.h"
|
||||
#include "files.h"
|
||||
@@ -40,7 +40,9 @@ int man_select(app_t *a, uint8_t force) {
|
||||
apdu.ne = res_APDU_size;
|
||||
if (force) {
|
||||
scan_all();
|
||||
#ifdef ENABLE_OTP_APP
|
||||
init_otp();
|
||||
#endif
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -74,17 +76,34 @@ bool cap_supported(uint16_t cap) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t _openpgp_aid[] = {
|
||||
6,
|
||||
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
|
||||
};
|
||||
static uint8_t _piv_aid[] = {
|
||||
5,
|
||||
0xA0, 0x00, 0x00, 0x03, 0x8,
|
||||
};
|
||||
|
||||
int man_get_config() {
|
||||
file_t *ef = search_dynamic_file(EF_DEV_CONF);
|
||||
res_APDU_size = 0;
|
||||
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
|
||||
res_APDU[res_APDU_size++] = TAG_USB_SUPPORTED;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
|
||||
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
|
||||
uint16_t caps = CAP_FIDO2 | CAP_OTP | CAP_U2F | CAP_OATH;
|
||||
if (app_exists(_openpgp_aid + 1, _openpgp_aid[0])) {
|
||||
caps |= CAP_OPENPGP;
|
||||
}
|
||||
if (app_exists(_piv_aid + 1, _piv_aid[0])) {
|
||||
caps |= CAP_PIV;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = caps >> 8;
|
||||
res_APDU[res_APDU_size++] = caps & 0xFF;
|
||||
res_APDU[res_APDU_size++] = TAG_SERIAL;
|
||||
res_APDU[res_APDU_size++] = 4;
|
||||
memcpy(res_APDU + res_APDU_size, pico_serial.id, 4);
|
||||
res_APDU[res_APDU_size] &= ~0xFC; // Force 8-digit serial number
|
||||
res_APDU_size += 4;
|
||||
res_APDU[res_APDU_size++] = TAG_FORM_FACTOR;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
@@ -97,7 +116,7 @@ int man_get_config() {
|
||||
if (!file_has_data(ef)) {
|
||||
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
uint16_t caps = 0;
|
||||
caps = 0;
|
||||
if (cap_supported(CAP_FIDO2)) {
|
||||
caps |= CAP_FIDO2;
|
||||
}
|
||||
@@ -110,6 +129,12 @@ int man_get_config() {
|
||||
if (cap_supported(CAP_OATH)) {
|
||||
caps |= CAP_OATH;
|
||||
}
|
||||
if (cap_supported(CAP_OPENPGP)) {
|
||||
caps |= CAP_OPENPGP;
|
||||
}
|
||||
if (cap_supported(CAP_PIV)) {
|
||||
caps |= CAP_PIV;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = caps >> 8;
|
||||
res_APDU[res_APDU_size++] = caps & 0xFF;
|
||||
res_APDU[res_APDU_size++] = TAG_DEVICE_FLAGS;
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef _MANAGEMENT_H_
|
||||
#define _MANAGEMENT_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "asn1.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "management.h"
|
||||
extern bool is_nk;
|
||||
|
||||
#define MAX_OATH_CRED 255
|
||||
#define CHALLENGE_LEN 8
|
||||
@@ -44,6 +45,10 @@
|
||||
#define TAG_PASSWORD 0x80
|
||||
#define TAG_NEW_PASSWORD 0x81
|
||||
#define TAG_PIN_COUNTER 0x82
|
||||
#define TAG_PWS_LOGIN 0x83
|
||||
#define TAG_PWS_PASSWORD 0x84
|
||||
#define TAG_PWS_METADATA 0x85
|
||||
#define TAG_SERIAL_NUMBER 0x8F
|
||||
|
||||
#define ALG_HMAC_SHA1 0x01
|
||||
#define ALG_HMAC_SHA256 0x02
|
||||
@@ -56,6 +61,7 @@
|
||||
|
||||
#define PROP_INC 0x01
|
||||
#define PROP_TOUCH 0x02
|
||||
#define PROP_PIN 0x03
|
||||
|
||||
int oath_process_apdu();
|
||||
int oath_unload();
|
||||
@@ -99,6 +105,12 @@ int oath_select(app_t *a, uint8_t force) {
|
||||
res_APDU[res_APDU_size++] = TAG_ALGO;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
|
||||
if (is_nk) {
|
||||
res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
|
||||
res_APDU[res_APDU_size++] = 8;
|
||||
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
|
||||
res_APDU_size += 8;
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -270,16 +282,27 @@ int cmd_list() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (file_has_data(ef)) {
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1);
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0));
|
||||
res_APDU[res_APDU_size++] = key.data[0];
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
if (ext) {
|
||||
uint8_t props = 0x0;
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
|
||||
props |= 0x4;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
|
||||
props |= 0x1;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = props;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,7 +638,7 @@ int cmd_rename() {
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
uint8_t *new_data = (uint8_t *) calloc(sizeof(uint8_t), fsize + new_name.len - name.len);
|
||||
uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
|
||||
memcpy(new_data, fdata, name.data - fdata);
|
||||
*(new_data + (name.data - fdata) - 1) = new_name.len;
|
||||
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
|
||||
@@ -626,6 +649,53 @@ int cmd_rename() {
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_get_credential() {
|
||||
asn1_ctx_t ctxi, name = { 0 };
|
||||
if (apdu.nc < 3) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (apdu.data[0] != TAG_NAME) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = find_oath_cred(name.data, name.len);
|
||||
if (file_has_data(ef) == false) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len);
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_LOGIN;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(login.len);
|
||||
memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(pw.len);
|
||||
memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_METADATA;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(meta.len);
|
||||
memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PROPERTY;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(prop.len);
|
||||
memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len;
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_PUT 0x01
|
||||
#define INS_DELETE 0x02
|
||||
#define INS_SET_CODE 0x03
|
||||
@@ -640,6 +710,7 @@ int cmd_rename() {
|
||||
#define INS_VERIFY_PIN 0xb2
|
||||
#define INS_CHANGE_PIN 0xb3
|
||||
#define INS_SET_PIN 0xb4
|
||||
#define INS_GET_CREDENTIAL 0xb5
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_PUT, cmd_put },
|
||||
@@ -656,6 +727,7 @@ static const cmd_t cmds[] = {
|
||||
{ INS_CHANGE_PIN, cmd_change_otp_pin },
|
||||
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
|
||||
{ INS_VERIFY_CODE, cmd_verify_hotp },
|
||||
{ INS_GET_CREDENTIAL, cmd_get_credential },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
@@ -24,14 +24,17 @@
|
||||
#include "asn1.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "usb.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATION
|
||||
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
|
||||
void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
|
||||
#else
|
||||
#include "tusb.h"
|
||||
#endif
|
||||
#include "mbedtls/aes.h"
|
||||
#include "management.h"
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "tusb.h"
|
||||
#endif
|
||||
|
||||
#define FIXED_SIZE 16
|
||||
#define KEY_SIZE 16
|
||||
@@ -116,12 +119,10 @@ uint16_t otp_status(bool is_otp);
|
||||
int otp_process_apdu();
|
||||
int otp_unload();
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
#endif
|
||||
|
||||
const uint8_t otp_aid[] = {
|
||||
7,
|
||||
@@ -200,15 +201,12 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
static uint8_t session_counter[2] = { 0 };
|
||||
#endif
|
||||
int otp_button_pressed(uint8_t slot) {
|
||||
init_otp();
|
||||
if (!cap_supported(CAP_OTP)) {
|
||||
return 3;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
const uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *) data;
|
||||
@@ -218,6 +216,7 @@ int otp_button_pressed(uint8_t slot) {
|
||||
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||
return 2;
|
||||
}
|
||||
#ifdef ENABLE_OATH_APP
|
||||
if (otp_config->tkt_flags & OATH_HOTP) {
|
||||
uint8_t tmp_key[KEY_SIZE + 2];
|
||||
tmp_key[0] = 0x01;
|
||||
@@ -259,6 +258,7 @@ int otp_button_pressed(uint8_t slot) {
|
||||
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
|
||||
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
|
||||
@@ -317,19 +317,15 @@ int otp_button_pressed(uint8_t slot) {
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) slot;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
INITIALIZER( otp_ctor ) {
|
||||
register_app(otp_select, otp_aid);
|
||||
button_pressed_cb = otp_button_pressed;
|
||||
#ifndef ENABLE_EMULATION
|
||||
hid_set_report_cb = otp_hid_set_report_cb;
|
||||
hid_get_report_cb = otp_hid_get_report_cb;
|
||||
#endif
|
||||
}
|
||||
|
||||
int otp_unload() {
|
||||
@@ -476,6 +472,7 @@ int cmd_otp() {
|
||||
}
|
||||
else if (p1 == 0x10) {
|
||||
memcpy(res_APDU, pico_serial.id, 4);
|
||||
res_APDU[0] &= ~0xFC; // Force 8-digit serial number
|
||||
res_APDU_size = 4;
|
||||
}
|
||||
else if (p1 == 0x13) { // Get config
|
||||
@@ -489,20 +486,20 @@ int cmd_otp() {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
int ret = 0;
|
||||
#ifndef ENABLE_EMULATION
|
||||
uint8_t *rdata_bk = apdu.rdata;
|
||||
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
|
||||
status_byte = 0x20;
|
||||
otp_status(_is_otp);
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (wait_button() == true) {
|
||||
status_byte = 0x00;
|
||||
otp_status(_is_otp);
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
#endif
|
||||
status_byte = 0x10;
|
||||
apdu.rdata = rdata_bk;
|
||||
}
|
||||
#endif
|
||||
if (p1 == 0x30 || p1 == 0x38) {
|
||||
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
|
||||
return SW_WRONG_DATA();
|
||||
@@ -567,8 +564,6 @@ int otp_process_apdu() {
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
|
||||
uint8_t otp_frame_rx[70] = {0};
|
||||
uint8_t otp_frame_tx[70] = {0};
|
||||
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
|
||||
@@ -670,5 +665,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
|
||||
|
||||
return reqlen;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -3,22 +3,22 @@
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
* Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __VERSION_H_
|
||||
#define __VERSION_H_
|
||||
|
||||
#define PICO_FIDO_VERSION 0x0606
|
||||
#define PICO_FIDO_VERSION 0x0700
|
||||
|
||||
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
||||
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
|
||||
from http import client
|
||||
from fido2.hid import CtapHidDevice
|
||||
from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError, _Ctap1ClientBackend
|
||||
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector
|
||||
from fido2.attestation import FidoU2FAttestation
|
||||
from fido2.ctap2.pin import ClientPin
|
||||
from fido2.server import Fido2Server
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType
|
||||
from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions
|
||||
from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension
|
||||
from utils import *
|
||||
from fido2.cose import ES256
|
||||
import sys
|
||||
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
|
||||
pass
|
||||
|
||||
class Device():
|
||||
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
|
||||
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
|
||||
self.__user = None
|
||||
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
||||
self.__set_server(rp=rp, attestation=attestation)
|
||||
|
||||
def __verify_rp(rp_id, origin):
|
||||
return True
|
||||
|
||||
def __set_client(self, origin, user_interaction, uv):
|
||||
self.__uv = uv
|
||||
@@ -101,14 +104,23 @@ class Device():
|
||||
sys.exit(1)
|
||||
|
||||
# Set up a FIDO 2 client using the origin https://example.com
|
||||
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
||||
extensions = [
|
||||
HmacSecretExtension(allow_hmac_secret=True),
|
||||
LargeBlobExtension(),
|
||||
CredBlobExtension(),
|
||||
CredProtectExtension(),
|
||||
MinPinLengthExtension(),
|
||||
CredPropsExtension(),
|
||||
ThirdPartyPaymentExtension()
|
||||
]
|
||||
self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions)
|
||||
|
||||
# Prefer UV if supported and configured
|
||||
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
||||
self.__uv = "preferred"
|
||||
print("Authenticator supports User Verification")
|
||||
|
||||
self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
||||
self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction)
|
||||
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
|
||||
self.ctap1 = self.__client1._backend.ctap1
|
||||
|
||||
@@ -117,7 +129,7 @@ class Device():
|
||||
self.__attestation = attestation
|
||||
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
||||
self.__server.allowed_algorithms = [
|
||||
PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
|
||||
PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
|
||||
for p in self.__client._backend.info.algorithms
|
||||
]
|
||||
|
||||
@@ -216,9 +228,7 @@ class Device():
|
||||
'key_params':key_params}}
|
||||
|
||||
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
|
||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
||||
)
|
||||
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||
rp = rp if rp is not Ellipsis else self.__rp
|
||||
user = user if user is not Ellipsis else self.user()
|
||||
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
||||
@@ -226,22 +236,31 @@ class Device():
|
||||
client = self.__client1
|
||||
else:
|
||||
client = self.__client
|
||||
result = client._backend.do_make_credential(
|
||||
client_data=client_data,
|
||||
rp=rp,
|
||||
user=user,
|
||||
key_params=key_params,
|
||||
exclude_list=exclude_list,
|
||||
options=PublicKeyCredentialCreationOptions(
|
||||
rp=PublicKeyCredentialRpEntity.from_dict(rp),
|
||||
user=PublicKeyCredentialUserEntity.from_dict(user),
|
||||
pub_key_cred_params=key_params,
|
||||
exclude_credentials=exclude_list,
|
||||
extensions=extensions,
|
||||
rk=rk,
|
||||
user_verification=user_verification,
|
||||
enterprise_attestation=enterprise_attestation,
|
||||
challenge=os.urandom(32),
|
||||
authenticator_selection=AuthenticatorSelectionCriteria(
|
||||
require_resident_key=rk,
|
||||
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED
|
||||
),
|
||||
attestation=enterprise_attestation
|
||||
)
|
||||
client_data, rp_id = client_data.collect_client_data(options=options)
|
||||
result = client._backend.do_make_credential(
|
||||
options=options,
|
||||
client_data=client_data,
|
||||
rp_id=rp_id,
|
||||
enterprise_rpid_list=None,
|
||||
event=event
|
||||
)
|
||||
return {'res':result,'req':{'client_data':client_data,
|
||||
return {'res':result.response,'req':{'client_data':client_data,
|
||||
'rp':rp,
|
||||
'user':user,
|
||||
'key_params':key_params}}
|
||||
'key_params':key_params},'client_extension_results':result.client_extension_results}
|
||||
|
||||
def try_make_credential(self, options=None):
|
||||
if (options is None):
|
||||
@@ -267,14 +286,14 @@ class Device():
|
||||
|
||||
# Complete registration
|
||||
auth_data = self.__server.register_complete(
|
||||
state, result.client_data, result.attestation_object
|
||||
state=state, response=result
|
||||
)
|
||||
credentials = [auth_data.credential_data]
|
||||
|
||||
print("New credential created!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print("ATTESTATION OBJECT:", result.attestation_object)
|
||||
print("CLIENT DATA:", result.response.client_data)
|
||||
print("ATTESTATION OBJECT:", result.response.attestation_object)
|
||||
print()
|
||||
print("CREDENTIAL DATA:", auth_data.credential_data)
|
||||
|
||||
@@ -294,17 +313,14 @@ class Device():
|
||||
self.__server.authenticate_complete(
|
||||
state,
|
||||
credentials,
|
||||
result.credential_id,
|
||||
result.client_data,
|
||||
result.authenticator_data,
|
||||
result.signature,
|
||||
result
|
||||
)
|
||||
|
||||
print("Credential authenticated!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print("CLIENT DATA:", result.response.client_data)
|
||||
print()
|
||||
print("AUTH DATA:", result.authenticator_data)
|
||||
print("AUTH DATA:", result.response.authenticator_data)
|
||||
|
||||
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
|
||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||
@@ -325,21 +341,31 @@ class Device():
|
||||
return self.__client._backend.ctap2.get_next_assertion()
|
||||
|
||||
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
|
||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
|
||||
)
|
||||
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||
if (ctap1 is True):
|
||||
client = self.__client1
|
||||
else:
|
||||
client = self.__client
|
||||
|
||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||
options=PublicKeyCredentialRequestOptions(
|
||||
challenge=os.urandom(32),
|
||||
rp_id=rp_id,
|
||||
allow_credentials=allow_list,
|
||||
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED,
|
||||
extensions=extensions
|
||||
)
|
||||
client_data, rp_id = client_data.collect_client_data(options=options)
|
||||
|
||||
if (ctap1 is True):
|
||||
client = self.__client1
|
||||
else:
|
||||
client = self.__client
|
||||
try:
|
||||
result = client._backend.do_get_assertion(
|
||||
options=options,
|
||||
client_data=client_data,
|
||||
rp_id=rp_id,
|
||||
allow_list=allow_list,
|
||||
extensions=extensions,
|
||||
user_verification=user_verification,
|
||||
event=event
|
||||
)
|
||||
except ClientError as e:
|
||||
@@ -347,11 +373,9 @@ class Device():
|
||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||
client_pin.set_pin(DEFAULT_PIN)
|
||||
result = client._backend.do_get_assertion(
|
||||
options=options,
|
||||
client_data=client_data,
|
||||
rp_id=rp_id,
|
||||
allow_list=allow_list,
|
||||
extensions=extensions,
|
||||
user_verification=user_verification,
|
||||
event=event
|
||||
)
|
||||
else:
|
||||
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
|
||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], *args)
|
||||
aut_data = res['res'].get_response(0)
|
||||
m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
|
||||
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
|
||||
m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash
|
||||
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
|
||||
return aut_data
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
@@ -442,7 +466,7 @@ def ccid_card():
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def select_oath(ccid_card):
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01]
|
||||
resp = send_apdu(ccid_card, 0xA4, 0x04, 0x00, aid)
|
||||
return ccid_card
|
||||
|
||||
|
||||
33
tests/docker/bookworm/Dockerfile
Normal file
33
tests/docker/bookworm/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM debian:bookworm
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt update && apt upgrade -y
|
||||
RUN apt install -y apt-utils
|
||||
RUN apt install -y libccid \
|
||||
libpcsclite-dev \
|
||||
git \
|
||||
autoconf \
|
||||
pkg-config \
|
||||
libtool \
|
||||
help2man \
|
||||
automake \
|
||||
gcc \
|
||||
make \
|
||||
build-essential \
|
||||
opensc \
|
||||
python3 \
|
||||
python3-pip \
|
||||
swig \
|
||||
cmake \
|
||||
libfuse-dev \
|
||||
python3-pyscard \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography inputimeout fido2==2.0.0 --break-system-packages
|
||||
WORKDIR /
|
||||
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
||||
WORKDIR /vsmartcard/virtualsmartcard
|
||||
RUN autoreconf --verbose --install
|
||||
RUN ./configure --sysconfdir=/etc
|
||||
RUN make && make install
|
||||
WORKDIR /
|
||||
@@ -22,11 +22,7 @@ RUN apt install -y libccid \
|
||||
cmake \
|
||||
libfuse-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
|
||||
RUN git clone https://github.com/polhenarejos/python-fido2.git
|
||||
WORKDIR /python-fido2
|
||||
RUN git checkout development
|
||||
RUN pip3 install .
|
||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
|
||||
WORKDIR /
|
||||
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
||||
WORKDIR /vsmartcard/virtualsmartcard
|
||||
|
||||
@@ -27,16 +27,17 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import HidDescriptor
|
||||
from ..ctap import CtapDevice, CtapError, STATUS
|
||||
from ..utils import LOG_LEVEL_TRAFFIC
|
||||
from threading import Event
|
||||
from enum import IntEnum, IntFlag, unique
|
||||
from typing import Tuple, Optional, Callable, Iterator
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
from enum import IntEnum, IntFlag, unique
|
||||
from threading import Event
|
||||
from typing import Callable, Iterator
|
||||
|
||||
from ..ctap import STATUS, CtapDevice, CtapError
|
||||
from ..utils import LOG_LEVEL_TRAFFIC
|
||||
from .base import HidDescriptor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
|
||||
from . import openbsd as backend
|
||||
else:
|
||||
raise Exception("Unsupported platform")
|
||||
|
||||
from . import emulation as backend
|
||||
|
||||
list_descriptors = backend.list_descriptors
|
||||
@@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor
|
||||
open_connection = backend.open_connection
|
||||
|
||||
|
||||
class ConnectionFailure(Exception):
|
||||
"""The CTAP connection failed or returned an invalid response."""
|
||||
|
||||
|
||||
@unique
|
||||
class CTAPHID(IntEnum):
|
||||
PING = 0x01
|
||||
@@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice):
|
||||
response = self.call(CTAPHID.INIT, nonce)
|
||||
r_nonce, response = response[:8], response[8:]
|
||||
if r_nonce != nonce:
|
||||
raise Exception("Wrong nonce")
|
||||
raise ConnectionFailure("Wrong nonce")
|
||||
(
|
||||
self._channel_id,
|
||||
self._u2fhid_version,
|
||||
@@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice):
|
||||
return self._u2fhid_version
|
||||
|
||||
@property
|
||||
def device_version(self) -> Tuple[int, int, int]:
|
||||
def device_version(self) -> tuple[int, int, int]:
|
||||
"""Device version number."""
|
||||
return self._device_version
|
||||
|
||||
@@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice):
|
||||
return self._capabilities
|
||||
|
||||
@property
|
||||
def product_name(self) -> Optional[str]:
|
||||
def product_name(self) -> str | None:
|
||||
"""Product name of device."""
|
||||
return self.descriptor.product_name
|
||||
|
||||
@property
|
||||
def serial_number(self) -> Optional[str]:
|
||||
def serial_number(self) -> str | None:
|
||||
"""Serial number of device."""
|
||||
return self.descriptor.serial_number
|
||||
|
||||
@@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice):
|
||||
self,
|
||||
cmd: int,
|
||||
data: bytes = b"",
|
||||
event: Optional[Event] = None,
|
||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
||||
event: Event | None = None,
|
||||
on_keepalive: Callable[[STATUS], None] | None = None,
|
||||
) -> bytes:
|
||||
event = event or Event()
|
||||
|
||||
while True:
|
||||
try:
|
||||
return self._do_call(cmd, data, event, on_keepalive)
|
||||
except CtapError as e:
|
||||
if e.code == CtapError.ERR.CHANNEL_BUSY:
|
||||
if not event.wait(0.1):
|
||||
logger.warning("CTAP channel busy, trying again...")
|
||||
continue # Keep retrying on BUSY while not cancelled
|
||||
raise
|
||||
|
||||
def _do_call(self, cmd, data, event, on_keepalive):
|
||||
remaining = data
|
||||
seq = 0
|
||||
|
||||
@@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice):
|
||||
r_channel = struct.unpack_from(">I", recv)[0]
|
||||
recv = recv[4:]
|
||||
if r_channel != self._channel_id:
|
||||
raise Exception("Wrong channel")
|
||||
raise ConnectionFailure("Wrong channel")
|
||||
|
||||
if not response: # Initialization packet
|
||||
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
||||
@@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice):
|
||||
if r_cmd == TYPE_INIT | cmd:
|
||||
pass # first data packet
|
||||
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
||||
ka_status = struct.unpack_from(">B", recv)[0]
|
||||
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
||||
try:
|
||||
ka_status = STATUS(struct.unpack_from(">B", recv)[0])
|
||||
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
||||
except ValueError:
|
||||
raise ConnectionFailure("Invalid keepalive status")
|
||||
if on_keepalive and ka_status != last_ka:
|
||||
try:
|
||||
ka_status = STATUS(ka_status)
|
||||
except ValueError:
|
||||
pass # Unknown status value
|
||||
last_ka = ka_status
|
||||
on_keepalive(ka_status)
|
||||
continue
|
||||
@@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice):
|
||||
r_seq = struct.unpack_from(">B", recv)[0]
|
||||
recv = recv[1:]
|
||||
if r_seq != seq:
|
||||
raise Exception("Wrong sequence number")
|
||||
raise ConnectionFailure("Wrong sequence number")
|
||||
seq += 1
|
||||
|
||||
response += recv
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
|
||||
# default values, can be overridden by the environment
|
||||
: ${MBEDTLS_DOCKER_GUEST:=bullseye}
|
||||
: ${MBEDTLS_DOCKER_GUEST:=bookworm}
|
||||
|
||||
|
||||
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
|
||||
from fido2.client import CtapError
|
||||
from fido2.cose import ES256, ES384, ES512, EdDSA
|
||||
import fido2.features
|
||||
fido2.features.webauthn_json_mapping.enabled = False
|
||||
from utils import ES256K
|
||||
import pytest
|
||||
|
||||
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
|
||||
|
||||
def test_missing_user(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user=None)
|
||||
device.MC(user=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_user_user(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user=b"12345678")
|
||||
device.MC(user=b"12345678")
|
||||
|
||||
def test_missing_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
|
||||
|
||||
def test_missing_pubKeyCredParams(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=None)
|
||||
device.MC(key_params=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
@@ -93,35 +91,23 @@ def test_bad_type_options(device):
|
||||
|
||||
def test_bad_type_rp_name(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
|
||||
device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"})
|
||||
|
||||
def test_bad_type_rp_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
|
||||
|
||||
def test_bad_type_rp_icon(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
|
||||
device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
|
||||
|
||||
def test_bad_type_user_name(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": b"user_id", "name": 8})
|
||||
device.MC(user={"id": b"user_id", "name": 8})
|
||||
|
||||
def test_bad_type_user_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name"})
|
||||
device.MC(user={"id": "user_id", "name": "name"})
|
||||
|
||||
def test_bad_type_user_displayName(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
|
||||
|
||||
def test_bad_type_user_icon(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
|
||||
|
||||
def test_bad_type_pubKeyCredParams(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=["wrong"])
|
||||
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
|
||||
@@ -132,13 +118,13 @@ def test_algorithms(device, info, alg):
|
||||
|
||||
def test_missing_pubKeyCredParams_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
|
||||
device.MC(key_params=[{"alg": ES256.ALGORITHM}])
|
||||
|
||||
assert e.value.code == CtapError.ERR.INVALID_CBOR
|
||||
|
||||
def test_missing_pubKeyCredParams_alg(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"type": "public-key"}])
|
||||
device.MC(key_params=[{"type": "public-key"}])
|
||||
|
||||
assert e.value.code in [
|
||||
CtapError.ERR.INVALID_CBOR,
|
||||
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
|
||||
|
||||
def test_bad_type_pubKeyCredParams_alg(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
|
||||
device.MC(key_params=[{"alg": "7", "type": "public-key"}])
|
||||
|
||||
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
|
||||
|
||||
@@ -158,26 +144,26 @@ def test_unsupported_algorithm(device):
|
||||
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
||||
|
||||
def test_exclude_list(resetdevice):
|
||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
|
||||
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}])
|
||||
|
||||
def test_exclude_list2(resetdevice):
|
||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
|
||||
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
|
||||
|
||||
def test_bad_type_exclude_list(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=["1234"])
|
||||
device.MC(exclude_list=["1234"])
|
||||
|
||||
def test_missing_exclude_list_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"id": b"1234"}])
|
||||
device.MC(exclude_list=[{"id": b"1234"}])
|
||||
|
||||
def test_missing_exclude_list_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"type": "public-key"}])
|
||||
device.MC(exclude_list=[{"type": "public-key"}])
|
||||
|
||||
def test_bad_type_exclude_list_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
|
||||
device.MC(exclude_list=[{"type": "public-key", "id": "1234"}])
|
||||
|
||||
def test_bad_type_exclude_list_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
|
||||
@@ -31,10 +31,10 @@ def test_authenticate(device):
|
||||
AUTRes = device.authenticate(credentials)
|
||||
|
||||
def test_assertion_auth_data(GARes):
|
||||
assert len(GARes['res'].get_response(0).authenticator_data) == 37
|
||||
assert len(GARes['res'].get_response(0).response.authenticator_data) == 37
|
||||
|
||||
def test_Check_that_AT_flag_is_not_set(GARes):
|
||||
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
|
||||
assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0
|
||||
|
||||
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
|
||||
res = device.GA(allow_list=[
|
||||
@@ -63,8 +63,8 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
|
||||
""" Check that authenticator filters and stores items in allow list correctly """
|
||||
allow_list = []
|
||||
|
||||
rp1 = {"id": "rp1.com", "name": "rp1.com"}
|
||||
rp2 = {"id": "rp2.com", "name": "rp2.com"}
|
||||
rp1 = {"id": "example.com", "name": "rp1.com"}
|
||||
rp2 = {"id": "example.com", "name": "rp2.com"}
|
||||
|
||||
rp1_registrations = []
|
||||
rp2_registrations = []
|
||||
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
|
||||
rp_id += ".com"
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(rp_id=rp_id)
|
||||
device.GA(rp_id=rp_id)
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_missing_rp(device):
|
||||
@@ -137,7 +137,7 @@ def test_missing_rp(device):
|
||||
|
||||
def test_bad_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(rp_id={"id": {"type": "wrong"}})
|
||||
device.GA(rp_id={"id": {"type": "wrong"}})
|
||||
|
||||
def test_missing_cdh(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
|
||||
|
||||
def test_bad_allow_list(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list={"type": "wrong"})
|
||||
device.GA(allow_list={"type": "wrong"})
|
||||
|
||||
def test_bad_allow_list_item(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=["wrong"] + [
|
||||
device.GA(allow_list=["wrong"] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
@@ -177,7 +177,7 @@ def test_option_up(device, info, GARes):
|
||||
assert res.auth_data.flags & (1 << 0)
|
||||
|
||||
def test_allow_list_fake_item(device, MCRes):
|
||||
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
|
||||
device.GA(allow_list=[{"type": "rot13", "id": b"1234"}]
|
||||
+ [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],
|
||||
@@ -185,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes):
|
||||
|
||||
def test_allow_list_missing_field(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"id": b"1234"}] + [
|
||||
device.GA(allow_list=[{"id": b"1234"}] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
@@ -200,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
|
||||
|
||||
def test_allow_list_id_wrong_type(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
|
||||
device.GA(allow_list=[{"type": "public-key", "id": 42}]
|
||||
+ [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
@@ -208,7 +208,7 @@ def test_allow_list_id_wrong_type(device, MCRes):
|
||||
|
||||
def test_allow_list_missing_id(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"type": "public-key"}] + [
|
||||
device.GA(allow_list=[{"type": "public-key"}] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
@@ -58,8 +58,9 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
|
||||
device.reset()
|
||||
|
||||
# It returns a silent authentication
|
||||
ga_res = device.doGA(allow_list=allow_list)
|
||||
|
||||
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):
|
||||
@@ -85,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
|
||||
auths = []
|
||||
regs = []
|
||||
# Use unique RP to not collide with other credentials
|
||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
||||
rp = {"id": "example.com", "name": "Example"}
|
||||
for i in range(0, 3):
|
||||
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||
regs.append(res)
|
||||
@@ -116,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
|
||||
auths = resGA.get_assertions()
|
||||
|
||||
user_max_GA = auths[0]
|
||||
print(auths)
|
||||
|
||||
for y in ("name", "displayName", "id"):
|
||||
if (y in user_max_GA):
|
||||
assert user_max_GA.user[y] == user_max[y]
|
||||
@@ -126,7 +127,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
||||
"""
|
||||
Test maximum returned capacity of the RK for the given RP
|
||||
"""
|
||||
|
||||
device.reset()
|
||||
# Try to determine from get_info, or default to 19.
|
||||
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
||||
if not RK_CAPACITY_PER_RP:
|
||||
@@ -140,7 +141,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
||||
return user
|
||||
|
||||
# Use unique RP to not collide with other credentials from other tests.
|
||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
||||
rp = {"id": "example.com", "name": "Example"}
|
||||
|
||||
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
|
||||
# res = device.sendGA(*req.toGA())
|
||||
@@ -183,10 +184,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
||||
"""
|
||||
|
||||
rk_rp = {"id": "rk-cred.org", "name": "Example"}
|
||||
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
|
||||
rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res']
|
||||
|
||||
server_rp = {"id": "server-cred.com", "name": "Example"}
|
||||
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
|
||||
server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res']
|
||||
|
||||
allow_list_with_different_rp_cred = [
|
||||
{
|
||||
@@ -197,7 +198,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
||||
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
|
||||
res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
|
||||
@@ -208,10 +209,10 @@ def test_same_userId_overwrites_rk(resetdevice):
|
||||
rp = {"id": "overwrite.org", "name": "Example"}
|
||||
user = generate_random_user()
|
||||
|
||||
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
||||
mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
|
||||
|
||||
# Should overwrite the first credential.
|
||||
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
||||
mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
|
||||
|
||||
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
|
||||
|
||||
@@ -227,7 +228,7 @@ def test_larger_icon_than_128(device):
|
||||
user = generate_random_user()
|
||||
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
|
||||
|
||||
device.doMC(rp = rp, rk=True, user = user)
|
||||
device.MC(rp = rp, options={"rk":True}, user = user)
|
||||
|
||||
|
||||
def test_returned_credential(device):
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from fido2.utils import websafe_decode
|
||||
from utils import verify
|
||||
import os
|
||||
|
||||
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCLBK(device):
|
||||
res = device.doMC(
|
||||
mc = device.doMC(
|
||||
rk=True,
|
||||
extensions={'largeBlob':{'support':'required'}}
|
||||
)['res']
|
||||
return res
|
||||
)
|
||||
res = mc['res']
|
||||
ext = mc['client_extension_results']
|
||||
return {'res': res, 'ext': ext}
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBRead(device, MCLBK):
|
||||
res = device.doGA(
|
||||
allow_list=[
|
||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],extensions={'largeBlob':{'read': True}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res']
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBReadLB(GALBRead):
|
||||
print(GALBRead.get_response(0))
|
||||
return GALBRead.get_response(0)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBWrite(device, MCLBK):
|
||||
res = device.doGA(
|
||||
allow_list=[
|
||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res'].get_response(0)
|
||||
|
||||
def test_supports_credblob(info):
|
||||
@@ -136,15 +140,17 @@ def test_supports_largeblobs(info):
|
||||
assert info.max_large_blob is None or (info.max_large_blob > 1024)
|
||||
|
||||
def test_get_largeblobkey_mc(MCLBK):
|
||||
assert 'supported' in MCLBK.extension_results
|
||||
assert MCLBK.extension_results['supported'] is True
|
||||
assert 'largeBlob' in MCLBK['ext']
|
||||
assert 'supported' in MCLBK['ext']['largeBlob']
|
||||
assert MCLBK['ext']['largeBlob']['supported'] is True
|
||||
|
||||
def test_get_largeblobkey_ga(GALBReadLBK):
|
||||
assert GALBReadLBK.large_blob_key is not None
|
||||
|
||||
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
||||
assert 'written' in GALBWrite.extension_results
|
||||
assert GALBWrite.extension_results['written'] is True
|
||||
assert 'largeBlob' in GALBWrite.client_extension_results
|
||||
assert 'written' in GALBWrite.client_extension_results['largeBlob']
|
||||
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
|
||||
|
||||
assert 'blob' in GALBReadLB.extension_results
|
||||
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
|
||||
assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
|
||||
assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB
|
||||
|
||||
@@ -22,6 +22,7 @@ import pytest
|
||||
from fido2.ctap2.extensions import CredProtectExtension
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.ctap import CtapError
|
||||
from utils import generate_random_user
|
||||
|
||||
class CredProtect:
|
||||
UserVerificationOptional = 1
|
||||
@@ -30,140 +31,139 @@ class CredProtect:
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectOptional(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
|
||||
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectOptionalList(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
|
||||
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectRequired(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
|
||||
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
|
||||
return res
|
||||
|
||||
class TestCredProtect(object):
|
||||
def test_credprotect_make_credential_1(self, MCCredProtectOptional):
|
||||
assert MCCredProtectOptional.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
|
||||
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
|
||||
|
||||
def test_credprotect_make_credential_1(MCCredProtectOptional):
|
||||
assert MCCredProtectOptional.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
|
||||
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
|
||||
def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
|
||||
assert MCCredProtectOptionalList.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
|
||||
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
|
||||
|
||||
def test_credprotect_make_credential_2(MCCredProtectOptionalList):
|
||||
assert MCCredProtectOptionalList.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
|
||||
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
|
||||
def test_credprotect_make_credential_3(self, MCCredProtectRequired):
|
||||
assert MCCredProtectRequired.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
|
||||
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
|
||||
|
||||
def test_credprotect_make_credential_3(MCCredProtectRequired):
|
||||
assert MCCredProtectRequired.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
|
||||
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
|
||||
def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
|
||||
""" CredProtectOptional Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
def test_credprotect_optional_excluded(device, MCCredProtectOptional):
|
||||
""" CredProtectOptional Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList):
|
||||
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
|
||||
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired):
|
||||
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired):
|
||||
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
# works
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
|
||||
|
||||
# works
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
|
||||
def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
|
||||
|
||||
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
|
||||
# works
|
||||
res = device.doGA()['res'].get_assertions()[0]
|
||||
|
||||
# works
|
||||
res = device.doGA()['res'].get_assertions()[0]
|
||||
# If there's only one credential, this is None
|
||||
assert res.number_of_credentials == None
|
||||
|
||||
# If there's only one credential, this is None
|
||||
assert res.number_of_credentials == None
|
||||
def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
# works
|
||||
res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
|
||||
assert res1.number_of_credentials in (None, 2)
|
||||
|
||||
def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
# works
|
||||
res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
|
||||
assert res1.number_of_credentials in (None, 2)
|
||||
results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
|
||||
|
||||
results = device.doGA(allow_list=allow_list)['res'].get_assertions()
|
||||
# the required credProtect is not returned.
|
||||
for res in results:
|
||||
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
|
||||
|
||||
# the required credProtect is not returned.
|
||||
for res in results:
|
||||
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
|
||||
def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
|
||||
|
||||
def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
|
||||
):
|
||||
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
|
||||
|
||||
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
|
||||
for ext in ["credProtect", "hmac-secret"]:
|
||||
assert res.auth_data.extensions
|
||||
assert ext in res.auth_data.extensions
|
||||
assert res.auth_data.extensions[ext] == True
|
||||
|
||||
for ext in ["credProtect", "hmac-secret"]:
|
||||
assert res.auth_data.extensions
|
||||
assert ext in res.auth_data.extensions
|
||||
assert res.auth_data.extensions[ext] == True
|
||||
class TestCredProtectUv:
|
||||
def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
|
||||
pin = "12345678"
|
||||
|
||||
def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
pin = "12345678"
|
||||
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
|
||||
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
|
||||
|
||||
assert res1.number_of_credentials in (None, 3)
|
||||
assert res1.number_of_credentials in (None, 3)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension
|
||||
from fido2.utils import hmac_sha256
|
||||
from fido2.ctap2.pin import PinProtocolV2
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.client import ClientError
|
||||
from utils import *
|
||||
|
||||
salt1 = b"\xa5" * 32
|
||||
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
|
||||
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
||||
return res['res'].attestation_object
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def hmac(resetdevice):
|
||||
return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
|
||||
|
||||
def test_hmac_secret_make_credential(MCHmacSecret):
|
||||
assert MCHmacSecret.auth_data.extensions
|
||||
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
|
||||
@@ -55,51 +52,51 @@ def test_fake_extension(device):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
|
||||
def test_hmac_secret_entropy(device, MCHmacSecret, salts
|
||||
):
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
|
||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||
ext = auth.extension_results
|
||||
ext = auth.client_extension_results
|
||||
assert ext
|
||||
assert "hmacGetSecret" in ext
|
||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
|
||||
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
|
||||
#print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']))
|
||||
if len(salts) == 1:
|
||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
||||
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
|
||||
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||
if len(salts) == 2:
|
||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
|
||||
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
|
||||
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||
assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5
|
||||
|
||||
def get_output(device, MCHmacSecret, hmac, salts):
|
||||
def get_output(device, MCHmacSecret, salts):
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
|
||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||
|
||||
ext = auth.extension_results
|
||||
ext = auth.client_extension_results
|
||||
assert ext
|
||||
assert "hmacGetSecret" in ext
|
||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
|
||||
if len(salts) == 2:
|
||||
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
|
||||
return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
|
||||
else:
|
||||
return ext["hmacGetSecret"]['output1']
|
||||
return ext.hmac_get_secret.output1
|
||||
|
||||
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
||||
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
|
||||
def test_hmac_secret_sanity(device, MCHmacSecret):
|
||||
output1 = get_output(device, MCHmacSecret, (salt1,))
|
||||
output12 = get_output(
|
||||
device, MCHmacSecret, hmac, (salt1, salt2)
|
||||
device, MCHmacSecret, (salt1, salt2)
|
||||
)
|
||||
output21 = get_output(
|
||||
device, MCHmacSecret, hmac, (salt2, salt1)
|
||||
device, MCHmacSecret, (salt2, salt1)
|
||||
)
|
||||
|
||||
assert output12[0] == output1
|
||||
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
||||
assert output21[0] == output12[1]
|
||||
assert output12[0] != output12[1]
|
||||
|
||||
def test_missing_keyAgreement(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
def test_missing_keyAgreement(device):
|
||||
|
||||
with pytest.raises(CtapError):
|
||||
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
|
||||
device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}})
|
||||
|
||||
def test_missing_saltAuth(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
def test_missing_saltAuth(device):
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
|
||||
device.GA(extensions={"hmac-secret": {2: b'1234'}})
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_missing_saltEnc(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
def test_missing_saltEnc(device,):
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
|
||||
device.GA(extensions={"hmac-secret": { 3: b'1234'}})
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_auth(device, hmac, MCHmacSecret):
|
||||
def test_bad_auth(device, MCHmacSecret):
|
||||
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
bad_auth = list(hout[3][:])
|
||||
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
|
||||
bad_auth = bytes(bad_auth)
|
||||
key_agreement = {
|
||||
1: 2,
|
||||
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
|
||||
-1: 1,
|
||||
-2: b'\x00'*32,
|
||||
-3: b'\x00'*32,
|
||||
}
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
|
||||
device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}})
|
||||
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
||||
def test_invalid_salt_length(device, hmac, salts):
|
||||
with pytest.raises(ValueError) as e:
|
||||
def test_invalid_salt_length(device, salts):
|
||||
with pytest.raises((CtapError,ClientError)) as e:
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
hout = {"salt1":salts[0]}
|
||||
|
||||
device.doGA(extensions={"hmacGetSecret": hout})
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||
def test_get_next_assertion_has_extension(
|
||||
device, hmac, salts
|
||||
device, salts
|
||||
):
|
||||
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
hout = {"salt1":salts[0]}
|
||||
accounts = 3
|
||||
regs = []
|
||||
auths = []
|
||||
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
|
||||
rp = {"id": f"example.com", "name": "ExampleRP_2"}
|
||||
fixed_users = [generate_random_user() for _ in range(accounts)]
|
||||
for i in range(accounts):
|
||||
res = device.doMC(extensions={"hmacCreateSecret": True},
|
||||
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
|
||||
assert "hmac-secret" in ext
|
||||
assert isinstance(ext["hmac-secret"], bytes)
|
||||
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
||||
key = hmac.process_get_output(x)
|
||||
|
||||
|
||||
|
||||
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
||||
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
|
||||
salts = [salt1]
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
hout = {"salt1":salts[0]}
|
||||
|
||||
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
|
||||
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
|
||||
auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||
assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0
|
||||
|
||||
ext_no_uv = auth_no_uv.auth_data.extensions
|
||||
ext_no_uv = auth_no_uv.response.authenticator_data.extensions
|
||||
assert ext_no_uv
|
||||
assert "hmac-secret" in ext_no_uv
|
||||
assert isinstance(ext_no_uv["hmac-secret"], bytes)
|
||||
@@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
||||
hout['salt2'] = salts[1]
|
||||
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
||||
|
||||
assert auth_uv.authenticator_data.flags & (1 << 2)
|
||||
ext_uv = auth_uv.extension_results
|
||||
assert auth_uv.response.authenticator_data.flags & (1 << 2)
|
||||
ext_uv = auth_uv.client_extension_results
|
||||
assert ext_uv
|
||||
assert "hmacGetSecret" in ext_uv
|
||||
assert len(ext_uv["hmacGetSecret"]) == len(salts)
|
||||
assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts)
|
||||
|
||||
# Now see if the hmac-secrets are different
|
||||
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
|
||||
res = device.doGA(ctap1=False, allow_list=[
|
||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
|
||||
|
||||
# Test FIDO2 register works with U2F auth
|
||||
|
||||
@@ -123,7 +123,7 @@ def test_auth(reset_oath):
|
||||
resp = list_apdu(reset_oath)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01]
|
||||
resp = send_apdu(reset_oath, 0xA4, 0x04, 0x00, aid)
|
||||
assert(resp[15] == TAG_CHALLENGE)
|
||||
assert(resp[16] == 8)
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
/usr/sbin/pcscd &
|
||||
sleep 2
|
||||
rm -f memory.flash
|
||||
cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid
|
||||
cp -R tests/docker/fido2/* /usr/local/lib/python3.11/dist-packages/fido2/hid
|
||||
./build_in_docker/pico_fido > /dev/null &
|
||||
pytest tests
|
||||
|
||||
@@ -1,611 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
/*
|
||||
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import platform
|
||||
from binascii import hexlify
|
||||
from threading import Event
|
||||
from typing import Mapping, Any, Optional, Callable
|
||||
import struct
|
||||
import urllib.request
|
||||
import json
|
||||
from enum import IntEnum, unique
|
||||
|
||||
try:
|
||||
from fido2.ctap2.config import Config
|
||||
from fido2.ctap2 import Ctap2, ClientPin, PinProtocolV2
|
||||
from fido2.hid import CtapHidDevice, CTAPHID
|
||||
from fido2.utils import bytes2int, int2bytes
|
||||
from fido2 import cbor
|
||||
from fido2.ctap import CtapDevice, CtapError
|
||||
from fido2.ctap2.pin import PinProtocol, _PinUv
|
||||
from fido2.ctap2.base import args
|
||||
except:
|
||||
print('ERROR: fido2 module not found! Install fido2 package.\nTry with `pip install fido2`')
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography import x509
|
||||
except:
|
||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||
sys.exit(-1)
|
||||
|
||||
from enum import IntEnum
|
||||
from binascii import hexlify
|
||||
|
||||
def get_pki_data(url, data=None, method='GET'):
|
||||
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; '
|
||||
'rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
|
||||
method = 'GET'
|
||||
if (data is not None):
|
||||
method = 'POST'
|
||||
req = urllib.request.Request(f"https://www.picokeys.com/pico/pico-fido/{url}/",
|
||||
method=method,
|
||||
data=data,
|
||||
headers={'User-Agent': user_agent, })
|
||||
response = urllib.request.urlopen(req)
|
||||
resp = response.read().decode('utf-8')
|
||||
j = json.loads(resp)
|
||||
return j
|
||||
|
||||
class VendorConfig(Config):
|
||||
|
||||
class PARAM(IntEnum):
|
||||
VENDOR_COMMAND_ID = 0x01
|
||||
VENDOR_AUT_CT = 0x02
|
||||
VENDOR_PARAM = 0x02
|
||||
|
||||
class CMD(IntEnum):
|
||||
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
|
||||
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
|
||||
CONFIG_VENDOR_PROTOTYPE = 0x7f
|
||||
CONFIG_VENDOR_PHY = 0x1b
|
||||
CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa
|
||||
CONFIG_PHY_OPTS = 0x969f3b09eceb805f
|
||||
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
|
||||
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
|
||||
|
||||
class RESP(IntEnum):
|
||||
KEY_AGREEMENT = 0x01
|
||||
|
||||
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
|
||||
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
|
||||
|
||||
def enable_device_aut(self, ct):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
|
||||
VendorConfig.PARAM.VENDOR_AUT_CT: ct
|
||||
},
|
||||
)
|
||||
|
||||
def disable_device_aut(self):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
|
||||
},
|
||||
)
|
||||
|
||||
def vidpid(self, vid, pid):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PHY,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID,
|
||||
VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid
|
||||
},
|
||||
)
|
||||
|
||||
def led_gpio(self, gpio):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PHY,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO,
|
||||
VendorConfig.PARAM.VENDOR_PARAM: gpio
|
||||
},
|
||||
)
|
||||
|
||||
def led_brightness(self, brightness):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PHY,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS,
|
||||
VendorConfig.PARAM.VENDOR_PARAM: brightness
|
||||
},
|
||||
)
|
||||
|
||||
def phy_opts(self, opts):
|
||||
self._call(
|
||||
VendorConfig.CMD.CONFIG_VENDOR_PHY,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS,
|
||||
VendorConfig.PARAM.VENDOR_PARAM: opts
|
||||
},
|
||||
)
|
||||
|
||||
class Ctap2Vendor(Ctap2):
|
||||
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
|
||||
super().__init__(device=device, strict_cbor=strict_cbor)
|
||||
|
||||
|
||||
def send_vendor(
|
||||
self,
|
||||
cmd: int,
|
||||
data: Optional[Mapping[int, Any]] = None,
|
||||
*,
|
||||
event: Optional[Event] = None,
|
||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
||||
) -> Mapping[int, Any]:
|
||||
"""Sends a VENDOR message to the device, and waits for a response.
|
||||
|
||||
:param cmd: The command byte of the request.
|
||||
:param data: The payload to send (to be CBOR encoded).
|
||||
:param event: Optional threading.Event used to cancel the request.
|
||||
:param on_keepalive: Optional function called when keep-alive is sent by
|
||||
the authenticator.
|
||||
"""
|
||||
request = struct.pack(">B", cmd)
|
||||
if data is not None:
|
||||
request += cbor.encode(data)
|
||||
response = self.device.call(CTAPHID.VENDOR_FIRST + 1, request, event, on_keepalive)
|
||||
status = response[0]
|
||||
if status != 0x00:
|
||||
raise CtapError(status)
|
||||
enc = response[1:]
|
||||
if not enc:
|
||||
return {}
|
||||
decoded = cbor.decode(enc)
|
||||
if self._strict_cbor:
|
||||
expected = cbor.encode(decoded)
|
||||
if expected != enc:
|
||||
raise ValueError(
|
||||
"Non-canonical CBOR from Authenticator.\n"
|
||||
f"Got: {enc.hex()}\nExpected: {expected.hex()}"
|
||||
)
|
||||
if isinstance(decoded, Mapping):
|
||||
return decoded
|
||||
raise TypeError("Decoded value of wrong type")
|
||||
|
||||
def vendor(
|
||||
self,
|
||||
cmd: int,
|
||||
sub_cmd: int,
|
||||
sub_cmd_params: Optional[Mapping[int, Any]] = None,
|
||||
pin_uv_protocol: Optional[int] = None,
|
||||
pin_uv_param: Optional[bytes] = None,
|
||||
) -> Mapping[int, Any]:
|
||||
"""CTAP2 authenticator vendor command.
|
||||
|
||||
This command is used to configure various authenticator features through the
|
||||
use of its subcommands.
|
||||
|
||||
This method is not intended to be called directly. It is intended to be used by
|
||||
an instance of the Config class.
|
||||
|
||||
:param sub_cmd: A Config sub command.
|
||||
:param sub_cmd_params: Sub command specific parameters.
|
||||
:param pin_uv_protocol: PIN/UV auth protocol version used.
|
||||
:param pin_uv_param: PIN/UV Auth parameter.
|
||||
"""
|
||||
return self.send_vendor(
|
||||
cmd,
|
||||
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
|
||||
)
|
||||
|
||||
|
||||
class Vendor:
|
||||
"""Implementation of the CTAP2.1 Authenticator Vendor API. It is vendor implementation.
|
||||
|
||||
:param ctap: An instance of a CTAP2Vendor object.
|
||||
:param pin_uv_protocol: An instance of a PinUvAuthProtocol.
|
||||
:param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session.
|
||||
"""
|
||||
|
||||
@unique
|
||||
class CMD(IntEnum):
|
||||
VENDOR_BACKUP = 0x01
|
||||
VENDOR_MSE = 0x02
|
||||
VENDOR_UNLOCK = 0x03
|
||||
VENDOR_EA = 0x04
|
||||
VENDOR_PHY = 0x05
|
||||
VENDOR_MEMORY = 0x06
|
||||
|
||||
@unique
|
||||
class PARAM(IntEnum):
|
||||
PARAM = 0x01
|
||||
COSE_KEY = 0x02
|
||||
|
||||
class SUBCMD(IntEnum):
|
||||
ENABLE = 0x01
|
||||
DISABLE = 0x02
|
||||
KEY_AGREEMENT = 0x01
|
||||
EA_CSR = 0x01
|
||||
EA_UPLOAD = 0x02
|
||||
|
||||
class RESP(IntEnum):
|
||||
PARAM = 0x01
|
||||
COSE_KEY = 0x02
|
||||
|
||||
class PHY_OPTS(IntEnum):
|
||||
PHY_OPT_WCID = 0x1
|
||||
PHY_OPT_DIMM = 0x2
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ctap: Ctap2Vendor,
|
||||
pin_uv_protocol: Optional[PinProtocol] = None,
|
||||
pin_uv_token: Optional[bytes] = None,
|
||||
):
|
||||
self.ctap = ctap
|
||||
self.pin_uv = (
|
||||
_PinUv(pin_uv_protocol, pin_uv_token)
|
||||
if pin_uv_protocol and pin_uv_token
|
||||
else None
|
||||
)
|
||||
self.__key_enc = None
|
||||
self.__iv = None
|
||||
|
||||
self.vcfg = VendorConfig(ctap, pin_uv_protocol=pin_uv_protocol, pin_uv_token=pin_uv_token)
|
||||
|
||||
def _call(self, cmd, sub_cmd, params=None):
|
||||
if params:
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
else:
|
||||
params = None
|
||||
if self.pin_uv:
|
||||
msg = (
|
||||
b"\xff" * 32
|
||||
+ b"\x0d"
|
||||
+ struct.pack("<b", sub_cmd)
|
||||
+ (cbor.encode(params) if params else b"")
|
||||
)
|
||||
pin_uv_protocol = self.pin_uv.protocol.VERSION
|
||||
pin_uv_param = self.pin_uv.protocol.authenticate(self.pin_uv.token, msg)
|
||||
else:
|
||||
pin_uv_protocol = None
|
||||
pin_uv_param = None
|
||||
return self.ctap.vendor(cmd, sub_cmd, params, pin_uv_protocol, pin_uv_param)
|
||||
|
||||
def backup_save(self, filename):
|
||||
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
|
||||
from secure_key import windows as skey
|
||||
elif (platform.system() == 'Darwin'):
|
||||
from secure_key import macos as skey
|
||||
else:
|
||||
print('ERROR: platform not supported')
|
||||
sys.exit(-1)
|
||||
from words import words
|
||||
ret = self._call(
|
||||
Vendor.CMD.VENDOR_BACKUP,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
)
|
||||
data = ret[Vendor.RESP.PARAM]
|
||||
d = int.from_bytes(skey.get_secure_key(), 'big')
|
||||
with open(filename, 'wb') as fp:
|
||||
fp.write(b'\x01')
|
||||
fp.write(data)
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
fp.write(signature)
|
||||
print('Remember the following words in this order:')
|
||||
for c in range(24):
|
||||
coef = (d//(2048**c))%2048
|
||||
print(f'{(c+1):02d} - {words[coef]}')
|
||||
|
||||
def backup_load(self, filename):
|
||||
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
|
||||
from secure_key import windows as skey
|
||||
elif (platform.system() == 'Darwin'):
|
||||
from secure_key import macos as skey
|
||||
else:
|
||||
print('ERROR: platform not supported')
|
||||
sys.exit(-1)
|
||||
from words import words
|
||||
d = 0
|
||||
if (d == 0):
|
||||
for c in range(24):
|
||||
word = input(f'Introduce word {(c+1):02d}: ')
|
||||
while (word not in words):
|
||||
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
|
||||
coef = words.index(word)
|
||||
d = d+(2048**c)*coef
|
||||
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
pb = pk.public_key()
|
||||
with open(filename, 'rb') as fp:
|
||||
format = fp.read(1)[0]
|
||||
if (format == 0x1):
|
||||
data = fp.read(60)
|
||||
signature = fp.read()
|
||||
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
|
||||
skey.set_secure_key(pk)
|
||||
return self._call(
|
||||
Vendor.CMD.VENDOR_BACKUP,
|
||||
Vendor.SUBCMD.DISABLE,
|
||||
{
|
||||
Vendor.PARAM.PARAM: data
|
||||
},
|
||||
)
|
||||
|
||||
def mse(self):
|
||||
sk = ec.generate_private_key(ec.SECP256R1())
|
||||
pn = sk.public_key().public_numbers()
|
||||
self.__pb = sk.public_key().public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
|
||||
key_agreement = {
|
||||
1: 2,
|
||||
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
|
||||
-1: 1,
|
||||
-2: int2bytes(pn.x, 32),
|
||||
-3: int2bytes(pn.y, 32),
|
||||
}
|
||||
|
||||
ret = self._call(
|
||||
Vendor.CMD.VENDOR_MSE,
|
||||
Vendor.SUBCMD.KEY_AGREEMENT,
|
||||
{
|
||||
Vendor.PARAM.COSE_KEY: key_agreement,
|
||||
},
|
||||
)
|
||||
|
||||
peer_cose_key = ret[VendorConfig.RESP.KEY_AGREEMENT]
|
||||
|
||||
x = bytes2int(peer_cose_key[-2])
|
||||
y = bytes2int(peer_cose_key[-3])
|
||||
pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
|
||||
shared_key = sk.exchange(ec.ECDH(), pk)
|
||||
|
||||
xkdf = HKDF(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=12+32,
|
||||
salt=None,
|
||||
info=self.__pb
|
||||
)
|
||||
kdf_out = xkdf.derive(shared_key)
|
||||
self.__key_enc = kdf_out[12:]
|
||||
self.__iv = kdf_out[:12]
|
||||
|
||||
def encrypt_chacha(self, data):
|
||||
chacha = ChaCha20Poly1305(self.__key_enc)
|
||||
ct = chacha.encrypt(self.__iv, data, self.__pb)
|
||||
return ct
|
||||
|
||||
def unlock_device(self):
|
||||
ct = self.get_skey()
|
||||
self._call(
|
||||
Vendor.CMD.VENDOR_UNLOCK,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
{
|
||||
Vendor.PARAM.PARAM: ct
|
||||
},
|
||||
)
|
||||
|
||||
def _get_key_device(self):
|
||||
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
|
||||
from secure_key import windows as skey
|
||||
elif (platform.system() == 'Darwin'):
|
||||
from secure_key import macos as skey
|
||||
else:
|
||||
print('ERROR: platform not supported')
|
||||
sys.exit(-1)
|
||||
return skey.get_secure_key()
|
||||
|
||||
def get_skey(self):
|
||||
self.mse()
|
||||
ct = self.encrypt_chacha(self._get_key_device())
|
||||
return ct
|
||||
|
||||
def enable_device_aut(self):
|
||||
ct = self.get_skey()
|
||||
self.vcfg.enable_device_aut(ct)
|
||||
|
||||
def disable_device_aut(self):
|
||||
self.vcfg.disable_device_aut()
|
||||
|
||||
def csr(self):
|
||||
return self._call(
|
||||
Vendor.CMD.VENDOR_EA,
|
||||
Vendor.SUBCMD.EA_CSR,
|
||||
)[Vendor.RESP.PARAM]
|
||||
|
||||
def upload_ea(self, der):
|
||||
self._call(
|
||||
Vendor.CMD.VENDOR_EA,
|
||||
Vendor.SUBCMD.EA_UPLOAD,
|
||||
{
|
||||
Vendor.PARAM.PARAM: der
|
||||
}
|
||||
)
|
||||
|
||||
def vidpid(self, vid, pid):
|
||||
return self.vcfg.vidpid(vid, pid)
|
||||
|
||||
def led_gpio(self, gpio):
|
||||
return self.vcfg.led_gpio(gpio)
|
||||
|
||||
def led_brightness(self, brightness):
|
||||
if (brightness > 15):
|
||||
print('ERROR: Brightness must be between 0 and 15')
|
||||
return
|
||||
return self.vcfg.led_brightness(brightness)
|
||||
|
||||
def led_dimmable(self, onoff):
|
||||
opts = self.phy_opts()
|
||||
if (onoff):
|
||||
opts |= Vendor.PHY_OPTS.PHY_OPT_DIMM
|
||||
else:
|
||||
opts &= ~Vendor.PHY_OPTS.PHY_OPT_DIMM
|
||||
print(f'opts: {opts}')
|
||||
return self.vcfg.phy_opts(opts)
|
||||
|
||||
def wcid(self, onoff):
|
||||
opts = self.phy_opts()
|
||||
if (onoff):
|
||||
opts |= Vendor.PHY_OPTS.PHY_OPT_WCID
|
||||
else:
|
||||
opts &= ~Vendor.PHY_OPTS.PHY_OPT_WCID
|
||||
return self.vcfg.phy_opts(opts)
|
||||
|
||||
def phy_opts(self):
|
||||
return self._call(
|
||||
Vendor.CMD.VENDOR_PHY,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
)[Vendor.RESP.PARAM]
|
||||
|
||||
def memory(self):
|
||||
resp = self._call(
|
||||
Vendor.CMD.VENDOR_MEMORY,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
)
|
||||
return { 'free': resp[1], 'used': resp[2], 'total': resp[3], 'files': resp[4], 'size': resp[5] }
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparser = parser.add_subparsers(title="commands", dest="command")
|
||||
parser.add_argument('-p','--pin', help='Specify the PIN of the device.', required=True)
|
||||
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
|
||||
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
|
||||
|
||||
parser_backup = subparser.add_parser('backup', help='Manages the backup of Pico Fido.')
|
||||
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
|
||||
parser_backup.add_argument('filename', help='File to save or load the backup.')
|
||||
|
||||
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
|
||||
parser_attestation.add_argument('subcommand', choices=['csr'])
|
||||
parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.')
|
||||
|
||||
parser_phy = subparser.add_parser('phy', help='Set PHY options.')
|
||||
subparser_phy = parser_phy.add_subparsers(title='commands', dest='subcommand', required=True)
|
||||
parser_phy_vp = subparser_phy.add_parser('vidpid', help='Sets VID/PID. Use VID:PID format (e.g. 1234:5678)')
|
||||
parser_phy_vp.add_argument('value', help='Value of the PHY option.', metavar='VAL', nargs='?')
|
||||
parser_phy_ledn = subparser_phy.add_parser('led_gpio', help='Sets LED GPIO number.')
|
||||
parser_phy_ledn.add_argument('value', help='Value of the PHY option.', metavar='VAL', nargs='?')
|
||||
parser_phy_optwcid = subparser_phy.add_parser('wcid', help='Enable/Disable Web CCID interface.')
|
||||
parser_phy_optwcid.add_argument('value', choices=['enable', 'disable'], help='Enable/Disable Web CCID interface.', nargs='?')
|
||||
parser_phy_ledbtness = subparser_phy.add_parser('led_brightness', help='Sets LED max. brightness.')
|
||||
parser_phy_ledbtness.add_argument('value', help='Value of the max. brightness.', metavar='VAL', nargs='?')
|
||||
parser_phy_optdimm = subparser_phy.add_parser('led_dimmable', help='Enable/Disable LED dimming.')
|
||||
parser_phy_optdimm.add_argument('value', choices=['enable', 'disable'], help='Enable/Disable LED dimming.', nargs='?')
|
||||
|
||||
parser_mem = subparser.add_parser('memory', help='Get current memory usage.')
|
||||
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def secure(vdr, args):
|
||||
if (args.subcommand == 'enable'):
|
||||
vdr.enable_device_aut()
|
||||
elif (args.subcommand == 'unlock'):
|
||||
vdr.unlock_device()
|
||||
elif (args.subcommand == 'disable'):
|
||||
vdr.disable_device_aut()
|
||||
|
||||
def backup(vdr, args):
|
||||
if (args.subcommand == 'save'):
|
||||
vdr.backup_save(args.filename)
|
||||
elif (args.subcommand == 'load'):
|
||||
vdr.backup_load(args.filename)
|
||||
|
||||
def attestation(vdr, args):
|
||||
if (args.subcommand == 'csr'):
|
||||
if (args.filename is None):
|
||||
csr = x509.load_der_x509_csr(vdr.csr())
|
||||
data = urllib.parse.urlencode({'csr': csr.public_bytes(Encoding.PEM)}).encode()
|
||||
j = get_pki_data('csr', data=data)
|
||||
cert = x509.load_pem_x509_certificate(j['x509'].encode())
|
||||
else:
|
||||
with open(args.filename, 'rb') as f:
|
||||
dataf = f.read()
|
||||
try:
|
||||
cert = x509.load_der_x509_certificate(dataf)
|
||||
except ValueError:
|
||||
cert = x509.load_pem_x509_certificate(dataf)
|
||||
vdr.upload_ea(cert.public_bytes(Encoding.DER))
|
||||
|
||||
def phy(vdr, args):
|
||||
val = args.value if 'value' in args else None
|
||||
if (val):
|
||||
if (args.subcommand == 'vidpid'):
|
||||
sp = val.split(':')
|
||||
if (len(sp) != 2):
|
||||
print('ERROR: VID/PID have wrong format. Use VID:PID format (e.g. 1234:5678)')
|
||||
ret = vdr.vidpid(int(sp[0],16), int(sp[1],16))
|
||||
elif (args.subcommand == 'led_gpio'):
|
||||
val = int(val)
|
||||
ret = vdr.led_gpio(val)
|
||||
elif (args.subcommand == 'led_brightness'):
|
||||
val = int(val)
|
||||
ret = vdr.led_brightness(val)
|
||||
elif (args.subcommand == 'led_dimmable'):
|
||||
ret = vdr.led_dimmable(val == 'enable')
|
||||
elif (args.subcommand == 'wcid'):
|
||||
ret = vdr.wcid(val == 'enable')
|
||||
|
||||
if (ret):
|
||||
print(f'Current value: {hexlify(ret)}')
|
||||
else:
|
||||
print('Command executed successfully. Please, restart your Pico Key.')
|
||||
|
||||
def memory(vdr, args):
|
||||
mem = vdr.memory()
|
||||
print(f'Memory usage:')
|
||||
print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)')
|
||||
print(f'\tUsed: {mem["used"]/1024:.2f} kilobytes ({mem["used"]*100/mem["total"]:.2f}%)')
|
||||
print(f'\tTotal: {mem["total"]/1024:.2f} kilobytes')
|
||||
print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
|
||||
print(f'\tFiles: {mem["files"]}')
|
||||
|
||||
def main(args):
|
||||
print('Pico Fido Tool v1.10')
|
||||
print('Author: Pol Henarejos')
|
||||
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
|
||||
print('')
|
||||
print('')
|
||||
|
||||
dev = next(CtapHidDevice.list_devices(), None)
|
||||
ctap = Ctap2Vendor(dev)
|
||||
client_pin = ClientPin(ctap)
|
||||
token = client_pin.get_pin_token(args.pin, permissions=ClientPin.PERMISSION.AUTHENTICATOR_CFG)
|
||||
vdr = Vendor(ctap, pin_uv_protocol=PinProtocolV2(), pin_uv_token=token)
|
||||
|
||||
if (args.command == 'secure'):
|
||||
secure(vdr, args)
|
||||
elif (args.command == 'backup'):
|
||||
backup(vdr, args)
|
||||
elif (args.command == 'attestation'):
|
||||
attestation(vdr, args)
|
||||
elif (args.command == 'phy'):
|
||||
phy(vdr, args)
|
||||
elif (args.command == 'memory'):
|
||||
memory(vdr, args)
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
main(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
@@ -28,6 +28,7 @@ elif [[ $1 == "esp32" ]]; then
|
||||
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
|
||||
git clone --recursive https://github.com/espressif/esp-idf.git
|
||||
cd esp-idf
|
||||
git checkout tags/v5.5
|
||||
./install.sh esp32s3
|
||||
. ./export.sh
|
||||
cd ..
|
||||
|
||||
Reference in New Issue
Block a user