mirror of
https://github.com/polhenarejos/pico-fido
synced 2026-06-04 11:59:06 +02:00
Compare commits
209 Commits
v6.4-eddsa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ffcfb4beb | ||
|
|
3ccd6e827f | ||
|
|
a2044d697d | ||
|
|
659c04c837 | ||
|
|
a0437dbfb2 | ||
|
|
604e7868e2 | ||
|
|
0e3d0d9a7d | ||
|
|
1f4be2a051 | ||
|
|
dc6007d1b4 | ||
|
|
6aa986ca06 | ||
|
|
98145a0ef4 | ||
|
|
342ae90df8 | ||
|
|
39b9dbb8d6 | ||
|
|
b66a80eb01 | ||
|
|
ca15206e77 | ||
|
|
ec10becbef | ||
|
|
b88e52971f | ||
|
|
f658ef6eab | ||
|
|
4e7c40b2bd | ||
|
|
f4487a1ff4 | ||
|
|
edf6a697ba | ||
|
|
24978a5476 | ||
|
|
1795f5c330 | ||
|
|
8d81e2eb24 | ||
|
|
ab3ddf4e23 | ||
|
|
d8ccf9bd28 | ||
|
|
0e1512a135 | ||
|
|
ac0462525a | ||
|
|
bb20a75ef4 | ||
|
|
8d709cf745 | ||
|
|
bbbbcadf4c | ||
|
|
fbbf1feb49 | ||
|
|
22de41bfe0 | ||
|
|
31a6315721 | ||
|
|
9fb8d475b3 | ||
|
|
c0298ece7d | ||
|
|
bea7706d63 | ||
|
|
cfd22c2d2c | ||
|
|
8ea118fe91 | ||
|
|
8e6f571b48 | ||
|
|
3c20800839 | ||
|
|
f2eef5b839 | ||
|
|
bc6ebdd069 | ||
|
|
3f890757ac | ||
|
|
18d68d7e05 | ||
|
|
c8d62de621 | ||
|
|
60165c21ca | ||
|
|
55a60f8875 | ||
|
|
7ed90007ef | ||
|
|
c23cc9ffe1 | ||
|
|
dc4565a8fb | ||
|
|
804ee68e86 | ||
|
|
fe49149d86 | ||
|
|
81d97f1a18 | ||
|
|
d16016cf1e | ||
|
|
2e0333677b | ||
|
|
becdc94339 | ||
|
|
bd499ae1d4 | ||
|
|
5fc84d7097 | ||
|
|
ac7e34522a | ||
|
|
70dec5596a | ||
|
|
2331dcb3ec | ||
|
|
42b8b0af5f | ||
|
|
ceb54d9a08 | ||
|
|
527943fbba | ||
|
|
7dddfd971e | ||
|
|
29f942dab9 | ||
|
|
aa9df892d3 | ||
|
|
7ac2ce30f0 | ||
|
|
e86862033c | ||
|
|
ae36143498 | ||
|
|
d90dbb6c5f | ||
|
|
1d21d93b74 | ||
|
|
8b9be258de | ||
|
|
46720fb387 | ||
|
|
1867f0330f | ||
|
|
bb542e3b83 | ||
|
|
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 | ||
|
|
cfe1321d62 | ||
|
|
2cbea57c86 | ||
|
|
b6bf2e6c66 | ||
|
|
3212f95915 | ||
|
|
21b12a7bff | ||
|
|
c8dbc213a0 | ||
|
|
0a2ee6523f | ||
|
|
c3ea413592 | ||
|
|
64f371e6e5 | ||
|
|
fdd4afb993 | ||
|
|
fef46dc1c5 | ||
|
|
23a45ac297 | ||
|
|
b152ff15a8 | ||
|
|
751fcf0538 | ||
|
|
4e4c28a479 | ||
|
|
23b60beb2e | ||
|
|
37d7d7faeb | ||
|
|
49c0179ccf | ||
|
|
eacb8a040c | ||
|
|
cb99b8f401 | ||
|
|
94f8d5f65f | ||
|
|
38d332f450 | ||
|
|
c67f5e3a1f | ||
|
|
bfb8a4cb20 | ||
|
|
0f5a24c9b6 | ||
|
|
dd207bd031 | ||
|
|
6069cf949b | ||
|
|
297c34914b | ||
|
|
529a12e7a3 | ||
|
|
bdbdd92be8 | ||
|
|
3807e23914 | ||
|
|
ce7d3ea72f | ||
|
|
eb857df3e1 | ||
|
|
2842944d90 | ||
|
|
7be92f5331 | ||
|
|
403b26b60a | ||
|
|
b91ece8ec3 | ||
|
|
d54bc1b0f3 | ||
|
|
e2dbbe2cc3 | ||
|
|
8aa9d1c5a3 | ||
|
|
2d2814cefc | ||
|
|
89a9d013f0 |
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.
|
||||||
11
.github/workflows/nightly.yml
vendored
11
.github/workflows/nightly.yml
vendored
@@ -19,15 +19,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ matrix.refs }}
|
ref: ${{ matrix.refs }}
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
- name: Restore private key
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.PRIVATE_KEY_B64 }}" | base64 -d > private.pem
|
||||||
|
chmod 600 private.pem
|
||||||
- name : Build
|
- name : Build
|
||||||
env:
|
env:
|
||||||
PICO_SDK_PATH: ../pico-sdk
|
PICO_SDK_PATH: ../pico-sdk
|
||||||
|
SECURE_BOOT_PKEY: ../private.pem
|
||||||
run: |
|
run: |
|
||||||
./workflows/autobuild.sh pico
|
./workflows/autobuild.sh pico
|
||||||
./build_pico_fido.sh
|
./build_pico_fido.sh --no-eddsa
|
||||||
./workflows/autobuild.sh esp32
|
./workflows/autobuild.sh esp32
|
||||||
|
- name: Delete private key
|
||||||
|
run: rm private.pem
|
||||||
- name: Update nightly release
|
- name: Update nightly release
|
||||||
uses: pyTooling/Actions/releaser@main
|
uses: pyTooling/Actions/releaser@v6.7.0
|
||||||
with:
|
with:
|
||||||
tag: nightly-${{ matrix.refs }}
|
tag: nightly-${{ matrix.refs }}
|
||||||
rm: true
|
rm: true
|
||||||
|
|||||||
301
CMakeLists.txt
301
CMakeLists.txt
@@ -1,74 +1,84 @@
|
|||||||
#
|
#
|
||||||
# This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
# This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||||
# Copyright (c) 2022 Pol Henarejos.
|
# Copyright (c) 2022 Pol Henarejos.
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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 General Public License as published by
|
||||||
# the Free Software Foundation, version 3.
|
# the Free Software Foundation, version 3.
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful, but
|
# This program is distributed in the hope that it will be useful, but
|
||||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# General Public License for more details.
|
# 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 General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.13)
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
set(USB_VID 0x2E8A)
|
||||||
|
set(USB_PID 0x10FE)
|
||||||
|
|
||||||
if(ESP_PLATFORM)
|
if(ESP_PLATFORM)
|
||||||
set(DEBUG_APDU 1)
|
if(NOT DEFINED ENABLE_POWER_ON_RESET)
|
||||||
set(DENABLE_POWER_ON_RESET 0)
|
set(ENABLE_POWER_ON_RESET 0)
|
||||||
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
|
endif()
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
if(NOT DEFINED ENABLE_PQC)
|
||||||
|
set(ENABLE_PQC 0)
|
||||||
|
endif()
|
||||||
|
set(EXTRA_COMPONENT_DIRS
|
||||||
|
src/fido
|
||||||
|
pico-keys-sdk/config/esp32/components/pico-keys-sdk
|
||||||
|
pico-keys-sdk/config/esp32/components/tinycbor
|
||||||
|
)
|
||||||
|
if(ENABLE_PQC)
|
||||||
|
list(APPEND EXTRA_COMPONENT_DIRS
|
||||||
|
pico-keys-sdk/config/esp32/components/mlkem512
|
||||||
|
pico-keys-sdk/config/esp32/components/mlkem768
|
||||||
|
pico-keys-sdk/config/esp32/components/mlkem1024
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
else()
|
else()
|
||||||
|
if(NOT ENABLE_EMULATION)
|
||||||
|
set(PICO_USE_FASTEST_SUPPORTED_CLOCK 1)
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(ENABLE_EMULATION)
|
project(pico_fido C CXX ASM)
|
||||||
else()
|
|
||||||
set(PICO_USE_FASTEST_SUPPORTED_CLOCK 1)
|
set(CMAKE_C_STANDARD 11)
|
||||||
include(pico_sdk_import.cmake)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
add_executable(pico_fido)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
project(pico_fido C CXX ASM)
|
include(pico-keys-sdk/cmake/options.cmake)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
|
||||||
|
|
||||||
if(ENABLE_EMULATION)
|
|
||||||
else()
|
|
||||||
pico_sdk_init()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(pico_fido)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
|
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
|
||||||
if(ENABLE_POWER_ON_RESET)
|
configure_bool_option(
|
||||||
add_definitions(-DENABLE_POWER_ON_RESET=1)
|
ENABLE_POWER_ON_RESET
|
||||||
message(STATUS "Power cycle on reset: \t enabled")
|
ENABLE_POWER_ON_RESET
|
||||||
else()
|
"Power cycle on reset: \t enabled"
|
||||||
add_definitions(-DENABLE_POWER_ON_RESET=0)
|
"Power cycle on reset: \t disabled"
|
||||||
message(STATUS "Power cycle on reset: \t disabled")
|
)
|
||||||
endif(ENABLE_POWER_ON_RESET)
|
|
||||||
|
|
||||||
option(ENABLE_OATH_APP "Enable/disable OATH application" ON)
|
option(ENABLE_OATH_APP "Enable/disable OATH application" ON)
|
||||||
if(ENABLE_OATH_APP)
|
configure_bool_option(
|
||||||
add_definitions(-DENABLE_OATH_APP=1)
|
ENABLE_OATH_APP
|
||||||
message(STATUS "OATH Application: \t\t enabled")
|
ENABLE_OATH_APP
|
||||||
else()
|
"OATH Application: \t\t enabled"
|
||||||
add_definitions(-DENABLE_OATH_APP=0)
|
"OATH Application: \t\t disabled"
|
||||||
message(STATUS "OATH Application: \t\t disabled")
|
)
|
||||||
endif(ENABLE_OATH_APP)
|
|
||||||
|
|
||||||
option(ENABLE_OTP_APP "Enable/disable OTP application" ON)
|
option(ENABLE_OTP_APP "Enable/disable OTP application" ON)
|
||||||
if(ENABLE_OTP_APP)
|
configure_bool_option(
|
||||||
add_definitions(-DENABLE_OTP_APP=1)
|
ENABLE_OTP_APP
|
||||||
message(STATUS "OTP Application: \t\t enabled")
|
ENABLE_OTP_APP
|
||||||
else()
|
"OTP Application: \t\t enabled"
|
||||||
add_definitions(-DENABLE_OTP_APP=0)
|
"OTP Application: \t\t disabled"
|
||||||
message(STATUS "OTP Application: \t\t disabled")
|
)
|
||||||
endif(ENABLE_OTP_APP)
|
|
||||||
|
|
||||||
if(ENABLE_OTP_APP OR ENABLE_OATH_APP)
|
if(ENABLE_OTP_APP OR ENABLE_OATH_APP)
|
||||||
set(USB_ITF_CCID 1)
|
set(USB_ITF_CCID 1)
|
||||||
@@ -77,87 +87,128 @@ else()
|
|||||||
set(USB_ITF_CCID 0)
|
set(USB_ITF_CCID 0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(USB_ITF_HID 1)
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
|
include(pico-keys-sdk/picokeys_sdk_import.cmake)
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/kek.c
|
if(NOT ESP_PLATFORM)
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_register.c
|
set(SOURCES ${PICOKEYS_SOURCES})
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_authenticate.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_version.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_reset.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_info.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_make_credential.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/known_apps.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_client_pin.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/credential.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_assertion.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c
|
|
||||||
${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
|
|
||||||
)
|
|
||||||
if (${ENABLE_OATH_APP})
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/oath.c
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
if (${ENABLE_OTP_APP})
|
|
||||||
set(SOURCES ${SOURCES}
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/otp.c
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(USB_ITF_HID 1)
|
list(APPEND SOURCES
|
||||||
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
|
||||||
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 1)
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_register.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_authenticate.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_version.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_reset.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_info.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_make_credential.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/known_apps.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_client_pin.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/credential.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_assertion.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c
|
||||||
|
${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)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/oath.c
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_OTP_APP)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/src/fido/otp.c
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h")
|
||||||
if(ESP_PLATFORM)
|
if(ESP_PLATFORM)
|
||||||
project(pico_fido)
|
project(pico_fido)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(INCLUDES ${INCLUDES}
|
set(INCLUDES ${INCLUDES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/src/fido
|
${CMAKE_CURRENT_LIST_DIR}/src/fido
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT ESP_PLATFORM)
|
if(NOT ESP_PLATFORM)
|
||||||
target_sources(pico_fido PUBLIC ${SOURCES})
|
target_sources(pico_fido PUBLIC ${SOURCES})
|
||||||
target_include_directories(pico_fido PUBLIC ${INCLUDES})
|
target_include_directories(pico_fido PUBLIC ${INCLUDES})
|
||||||
|
|
||||||
target_compile_options(pico_fido PUBLIC
|
set(COMMON_COMPILE_OPTIONS
|
||||||
-Wall
|
-Wall
|
||||||
)
|
)
|
||||||
if (NOT MSVC)
|
target_compile_options(pico_fido PRIVATE ${COMMON_COMPILE_OPTIONS})
|
||||||
target_compile_options(pico_fido PUBLIC
|
|
||||||
-Werror
|
picokeys_apply_strict_flags(
|
||||||
|
SOURCES ${SOURCES}
|
||||||
|
FILTER_REGEX "/src/fido/|/pico-keys-sdk/src/|/pico-keys-sdk/config/"
|
||||||
)
|
)
|
||||||
|
|
||||||
string(FIND ${CMAKE_C_COMPILER} ":" COMPILER_COLON)
|
|
||||||
if (${COMPILER_COLON} GREATER_EQUAL 0)
|
|
||||||
target_compile_options(pico_fido PUBLIC
|
|
||||||
-Wno-error=use-after-free
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
endif(NOT MSVC)
|
|
||||||
|
|
||||||
if(ENABLE_EMULATION)
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
target_compile_options(pico_fido PUBLIC
|
string(FIND ${CMAKE_C_COMPILER} ":" COMPILER_COLON)
|
||||||
-fdata-sections
|
if(${COMPILER_COLON} GREATER_EQUAL 0)
|
||||||
-ffunction-sections
|
target_compile_options(pico_fido PRIVATE
|
||||||
|
-Wno-error=use-after-free
|
||||||
)
|
)
|
||||||
endif(NOT MSVC)
|
endif()
|
||||||
if(APPLE)
|
endif()
|
||||||
target_link_options(pico_fido PUBLIC
|
|
||||||
-Wl,-dead_strip
|
if(ENABLE_EMULATION)
|
||||||
|
if(NOT MSVC)
|
||||||
|
set(EMULATION_NON_MSVC_COMPILE_OPTIONS
|
||||||
|
-fdata-sections
|
||||||
|
-ffunction-sections
|
||||||
)
|
)
|
||||||
|
target_compile_options(pico_fido PRIVATE ${EMULATION_NON_MSVC_COMPILE_OPTIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
set(EMULATION_APPLE_LINK_OPTIONS
|
||||||
|
-Wl,-dead_strip
|
||||||
|
)
|
||||||
|
target_link_options(pico_fido PRIVATE ${EMULATION_APPLE_LINK_OPTIONS})
|
||||||
|
|
||||||
|
if(DEBUG_APDU)
|
||||||
|
set(DEBUG_APDU_SANITIZER_OPTIONS
|
||||||
|
-fsanitize=address
|
||||||
|
-g
|
||||||
|
-O1
|
||||||
|
-fno-omit-frame-pointer
|
||||||
|
)
|
||||||
|
target_compile_options(pico_fido PRIVATE ${DEBUG_APDU_SANITIZER_OPTIONS})
|
||||||
|
target_link_options(pico_fido PRIVATE ${DEBUG_APDU_SANITIZER_OPTIONS})
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
set(EMULATION_NON_APPLE_LINK_OPTIONS
|
||||||
|
-Wl,--gc-sections
|
||||||
|
)
|
||||||
|
target_link_options(pico_fido PRIVATE ${EMULATION_NON_APPLE_LINK_OPTIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(pico_fido PRIVATE picokeys_sdk mbedtls pthread m)
|
||||||
else()
|
else()
|
||||||
target_link_options(pico_fido PUBLIC
|
target_link_libraries(
|
||||||
-Wl,--gc-sections
|
pico_fido
|
||||||
)
|
PRIVATE
|
||||||
endif (APPLE)
|
picokeys_sdk
|
||||||
target_link_libraries(pico_fido PRIVATE pthread m)
|
pico_stdlib
|
||||||
else()
|
pico_multicore
|
||||||
target_link_libraries(pico_fido PRIVATE pico_keys_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id pico_aon_timer tinyusb_device tinyusb_board)
|
hardware_flash
|
||||||
pico_add_extra_outputs(${CMAKE_PROJECT_NAME})
|
hardware_sync
|
||||||
endif()
|
hardware_adc
|
||||||
|
pico_unique_id
|
||||||
|
pico_aon_timer
|
||||||
|
tinyusb_device
|
||||||
|
tinyusb_board
|
||||||
|
)
|
||||||
|
pico_add_extra_outputs(${CMAKE_PROJECT_NAME})
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
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.
|
||||||
141
LICENSE
141
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 19 November 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
@@ -7,17 +7,15 @@
|
|||||||
|
|
||||||
Preamble
|
Preamble
|
||||||
|
|
||||||
The GNU General Public License is a free, copyleft license for
|
The GNU Affero General Public License is a free, copyleft license for
|
||||||
software and other kinds of works.
|
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
|
The licenses for most software and other practical works are designed
|
||||||
to take away your freedom to share and change the works. By contrast,
|
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
|
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
|
software for all its users.
|
||||||
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.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom, not
|
When we speak of free software, we are referring to freedom, not
|
||||||
price. Our General Public Licenses are designed to make sure that you
|
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
|
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.
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
To protect your rights, we need to prevent others from denying you
|
Developers that use our General Public Licenses protect your rights
|
||||||
these rights or asking you to surrender the rights. Therefore, you have
|
with two steps: (1) assert copyright on the software, and (2) offer
|
||||||
certain responsibilities if you distribute copies of the software, or if
|
you this License which gives you legal permission to copy, distribute
|
||||||
you modify it: responsibilities to respect the freedom of others.
|
and/or modify the software.
|
||||||
|
|
||||||
For example, if you distribute copies of such a program, whether
|
A secondary benefit of defending all users' freedom is that
|
||||||
gratis or for a fee, you must pass on to the recipients the same
|
improvements made in alternate versions of the program, if they
|
||||||
freedoms that you received. You must make sure that they, too, receive
|
receive widespread use, become available for other developers to
|
||||||
or can get the source code. And you must show them these terms so they
|
incorporate. Many developers of free software are heartened and
|
||||||
know their rights.
|
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:
|
The GNU Affero General Public License is designed specifically to
|
||||||
(1) assert copyright on the software, and (2) offer you this License
|
ensure that, in such cases, the modified source code becomes available
|
||||||
giving you legal permission to copy, distribute and/or modify it.
|
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
|
An older license, called the Affero General Public License and
|
||||||
that there is no warranty for this free software. For both users' and
|
published by Affero, was designed to accomplish similar goals. This is
|
||||||
authors' sake, the GPL requires that modified versions be marked as
|
a different license, not a version of the Affero GPL, but Affero has
|
||||||
changed, so that their problems will not be attributed erroneously to
|
released a new version of the Affero GPL which permits relicensing under
|
||||||
authors of previous versions.
|
this license.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
The precise terms and conditions for copying, distribution and
|
||||||
modification follow.
|
modification follow.
|
||||||
@@ -72,7 +60,7 @@ modification follow.
|
|||||||
|
|
||||||
0. Definitions.
|
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
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
works, such as semiconductor masks.
|
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
|
the Program, the only way you could satisfy both those terms and this
|
||||||
License would be to refrain entirely from conveying the Program.
|
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
|
Notwithstanding any other provision of this License, you have
|
||||||
permission to link or combine any covered work with a work licensed
|
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
|
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,
|
License will continue to apply to the part which is the covered work,
|
||||||
but the special requirements of the GNU Affero General Public License,
|
but the work with which it is combined will remain governed by version
|
||||||
section 13, concerning interaction through a network will apply to the
|
3 of the GNU General Public License.
|
||||||
combination as such.
|
|
||||||
|
|
||||||
14. Revised Versions of this License.
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions of
|
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
|
the GNU Affero General Public License from time to time. Such new versions
|
||||||
be similar in spirit to the present version, but may differ in detail to
|
will be similar in spirit to the present version, but may differ in detail to
|
||||||
address new problems or concerns.
|
address new problems or concerns.
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
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
|
Public License "or any later version" applies to it, you have the
|
||||||
option of following the terms and conditions either of that numbered
|
option of following the terms and conditions either of that numbered
|
||||||
version or of any later version published by the Free Software
|
version or of any later version published by the Free Software
|
||||||
Foundation. If the Program does not specify a version number of the
|
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.
|
by the Free Software Foundation.
|
||||||
|
|
||||||
If the Program specifies that a proxy can decide which future
|
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
|
public statement of acceptance of a version permanently authorizes you
|
||||||
to choose that version for the Program.
|
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>
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
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/>.
|
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.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short
|
If your software can interact with users remotely through a computer
|
||||||
notice like this when it starts in an interactive mode:
|
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
|
||||||
<program> Copyright (C) <year> <name of author>
|
interface could display a "Source" link that leads users to an archive
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
of the code. There are many ways you could offer source, and different
|
||||||
This is free software, and you are welcome to redistribute it
|
solutions will be better for different programs; see section 13 for the
|
||||||
under certain conditions; type `show c' for details.
|
specific requirements.
|
||||||
|
|
||||||
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".
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or school,
|
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.
|
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
|
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
<https://www.gnu.org/licenses/>.
|
<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>.
|
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,6 +1,8 @@
|
|||||||
# Pico FIDO
|
# 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.
|
This project transforms your Raspberry Pi Pico or ESP32 microcontroller into an integrated FIDO Passkey, functioning like a standard USB Passkey for authentication.
|
||||||
|
|
||||||
|
If you are looking for a OpenPGP + Fido, see: https://github.com/polhenarejos/pico-fido2. Available through [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App").
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
Pico FIDO includes the following features:
|
Pico FIDO includes the following features:
|
||||||
|
|
||||||
@@ -34,12 +36,13 @@ Pico FIDO includes the following features:
|
|||||||
- Challenge-response generation
|
- Challenge-response generation
|
||||||
- Emulated keyboard interface
|
- Emulated keyboard interface
|
||||||
- Button press generates an OTP that is directly typed
|
- Button press generates an OTP that is directly typed
|
||||||
|
- Yubico Authenticator app compatible
|
||||||
- Yubico YKMAN compatible
|
- Yubico YKMAN compatible
|
||||||
- Nitrokey nitropy and nitroapp compatible
|
- Nitrokey nitropy and nitroapp compatible
|
||||||
- Secure Boot and Secure Lock in RP2350 and ESP32-S3 MCUs
|
- Secure Boot and Secure Lock in RP2350 and ESP32-S3 MCUs
|
||||||
- One Time Programming to store the master key that encrypts all resident keys and seeds.
|
- One Time Programming to store the master key that encrypts all resident keys and seeds.
|
||||||
- Rescue interface to allow recovery of the device if it becomes unresponsive or undetectable.
|
- Rescue interface to allow recovery of the device if it becomes unresponsive or undetectable.
|
||||||
- LED customization with Pico Commissioner.
|
- LED customization with PicoKey App.
|
||||||
|
|
||||||
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.
|
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.
|
||||||
|
|
||||||
@@ -53,11 +56,11 @@ Microcontrollers RP2350 and ESP32-S3 are designed to support secure environments
|
|||||||
|
|
||||||
If you own a Raspberry Pico (RP2040 or RP2350), go to [Download page](https://www.picokeys.com/getting-started/), select your vendor and model and download the proper firmware; or go to [Release page](https://www.github.com/polhenarejos/pico-fido/releases/) and download the UF2 file for your board.
|
If you own a Raspberry Pico (RP2040 or RP2350), go to [Download page](https://www.picokeys.com/getting-started/), select your vendor and model and download the proper firmware; or go to [Release page](https://www.github.com/polhenarejos/pico-fido/releases/) and download the UF2 file for your board.
|
||||||
|
|
||||||
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you plan to use it with other proprietary tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner").
|
UF2 files are shiped with a VID/PID granted by RaspberryPi (2E8A:10FE). If you plan to use it with OpenSC or similar tools, you should modify Info.plist of CCID driver to add these VID/PID or use the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App").
|
||||||
|
|
||||||
You can use whatever VID/PID (i.e., 234b:0000 from FISJ), but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
|
You can use whatever VID/PID for internal purposes, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
|
||||||
|
|
||||||
Note that the pure-browser option [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner") is the most recommended.
|
Note that the [PicoKey App](https://www.picokeys.com/picokeyapp/ "PicoKey App") is the most recommended.
|
||||||
|
|
||||||
## Build for Raspberry Pico
|
## Build for Raspberry Pico
|
||||||
Before building, ensure you have installed the toolchain for the Pico and that the Pico SDK is properly located on your drive.
|
Before building, ensure you have installed the toolchain for the Pico and that the Pico SDK is properly located on your drive.
|
||||||
@@ -135,6 +138,53 @@ To run a subset of tests, use the `-k <test>` flag:
|
|||||||
pytest -k test_credprotect
|
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
|
## Credits
|
||||||
Pico FIDO uses the following libraries or portion of code:
|
Pico FIDO uses the following libraries or portion of code:
|
||||||
- MbedTLS for cryptographic operations.
|
- MbedTLS for cryptographic operations.
|
||||||
|
|||||||
@@ -1,23 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
VERSION_MAJOR="6"
|
VERSION_MAJOR="7"
|
||||||
VERSION_MINOR="4-eddsa1"
|
VERSION_MINOR="6"
|
||||||
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
|
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
|
||||||
#if ! [[ -z "${GITHUB_SHA}" ]]; then
|
#if ! [[ -z "${GITHUB_SHA}" ]]; then
|
||||||
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
|
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
|
||||||
#fi
|
#fi
|
||||||
|
|
||||||
rm -rf release/*
|
|
||||||
mkdir -p build_release
|
mkdir -p build_release
|
||||||
mkdir -p release
|
mkdir -p release
|
||||||
|
rm -rf -- release/*
|
||||||
cd build_release
|
cd build_release
|
||||||
|
|
||||||
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
|
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}"
|
||||||
board_dir=${PICO_SDK_PATH}/src/boards/include/boards
|
SECURE_BOOT_PKEY="${SECURE_BOOT_PKEY:-../../ec_private_key.pem}"
|
||||||
for board in "$board_dir"/*
|
boards=("pico" "pico2")
|
||||||
|
|
||||||
|
for board_name in "${boards[@]}"
|
||||||
do
|
do
|
||||||
board_name="$(basename -- $board .h)"
|
rm -rf -- ./*
|
||||||
rm -rf *
|
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name -DSECURE_BOOT_PKEY=${SECURE_BOOT_PKEY}
|
||||||
PICO_SDK_PATH="${PICO_SDK_PATH}" cmake .. -DPICO_BOARD=$board_name
|
|
||||||
make -j`nproc`
|
make -j`nproc`
|
||||||
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX.uf2
|
mv pico_fido.uf2 ../release/pico_fido_$board_name-$SUFFIX.uf2
|
||||||
done
|
done
|
||||||
|
|||||||
Submodule pico-keys-sdk updated: 4120a8c1a6...13d2e84595
@@ -10,6 +10,8 @@ CONFIG_PARTITION_TABLE_CUSTOM=y
|
|||||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
||||||
CONFIG_PARTITION_TABLE_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_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_SIZE_512=y
|
||||||
CONFIG_WL_SECTOR_MODE_PERF=y
|
CONFIG_WL_SECTOR_MODE_PERF=y
|
||||||
COMPILER_OPTIMIZATION="Performance"
|
COMPILER_OPTIMIZATION="Performance"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
idf_component_register(
|
idf_component_register(
|
||||||
SRCS ${SOURCES}
|
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
|
INCLUDE_DIRS .
|
||||||
REQUIRES bootloader_support esp_partition esp_tinyusb zorxx__neopixel mbedtls efuse
|
REQUIRES esp_tinyusb mbedtls efuse pico-keys-sdk
|
||||||
)
|
)
|
||||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "pico_keys.h"
|
#include "picokeys.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#endif
|
#endif
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
@@ -30,25 +30,13 @@
|
|||||||
|
|
||||||
const bool _btrue = true, _bfalse = false;
|
const bool _btrue = true, _bfalse = false;
|
||||||
|
|
||||||
int cbor_reset();
|
|
||||||
int cbor_get_info();
|
|
||||||
int cbor_make_credential(const uint8_t *data, size_t len);
|
|
||||||
int cbor_client_pin(const uint8_t *data, size_t len);
|
|
||||||
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||||
int cbor_get_next_assertion(const uint8_t *data, size_t len);
|
|
||||||
int cbor_selection();
|
|
||||||
int cbor_cred_mgmt(const uint8_t *data, size_t len);
|
|
||||||
int cbor_config(const uint8_t *data, size_t len);
|
|
||||||
int cbor_vendor(const uint8_t *data, size_t len);
|
|
||||||
int cbor_large_blobs(const uint8_t *data, size_t len);
|
|
||||||
|
|
||||||
extern int cmd_read_config();
|
|
||||||
|
|
||||||
const uint8_t aaguid[16] = { 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45 }; // First 16 bytes of SHA256("Pico FIDO2")
|
const uint8_t aaguid[16] = { 0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45 }; // First 16 bytes of SHA256("Pico FIDO2")
|
||||||
|
|
||||||
const uint8_t *cbor_data = NULL;
|
static const uint8_t *volatile cbor_data = NULL;
|
||||||
size_t cbor_len = 0;
|
static volatile size_t cbor_len = 0;
|
||||||
uint8_t cbor_cmd = 0;
|
static volatile uint8_t cbor_cmd = 0;
|
||||||
|
|
||||||
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||||
if (len == 0 && cmd == CTAPHID_CBOR) {
|
if (len == 0 && cmd == CTAPHID_CBOR) {
|
||||||
@@ -59,6 +47,9 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
if (cap_supported(CAP_FIDO2)) {
|
if (cap_supported(CAP_FIDO2)) {
|
||||||
if (cmd == CTAPHID_CBOR) {
|
if (cmd == CTAPHID_CBOR) {
|
||||||
|
if (data[0] != CTAP_GET_NEXT_ASSERTION) {
|
||||||
|
reset_gna_state();
|
||||||
|
}
|
||||||
if (data[0] == CTAP_MAKE_CREDENTIAL) {
|
if (data[0] == CTAP_MAKE_CREDENTIAL) {
|
||||||
return cbor_make_credential(data + 1, len - 1);
|
return cbor_make_credential(data + 1, len - 1);
|
||||||
}
|
}
|
||||||
@@ -94,7 +85,7 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
return cbor_vendor(data, len);
|
return cbor_vendor(data, len);
|
||||||
}
|
}
|
||||||
else if (cmd == 0xC2) {
|
else if (cmd == 0xC2) {
|
||||||
if (cmd_read_config() == 0x9000) {
|
if (man_get_config() == 0) {
|
||||||
memmove(res_APDU-1, res_APDU, res_APDU_size);
|
memmove(res_APDU-1, res_APDU, res_APDU_size);
|
||||||
res_APDU_size -= 1;
|
res_APDU_size -= 1;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -104,7 +95,9 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
return CTAP1_ERR_INVALID_CMD;
|
return CTAP1_ERR_INVALID_CMD;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cbor_thread(void) {
|
void *cbor_thread(void *arg) __attribute__((unused));
|
||||||
|
void *cbor_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
card_init_core1();
|
card_init_core1();
|
||||||
while (1) {
|
while (1) {
|
||||||
uint32_t m;
|
uint32_t m;
|
||||||
@@ -115,17 +108,20 @@ void cbor_thread(void) {
|
|||||||
if (m == EV_EXIT) {
|
if (m == EV_EXIT) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len);
|
const uint8_t *data = (const uint8_t *)cbor_data;
|
||||||
|
size_t len = cbor_len;
|
||||||
|
uint8_t cmd = cbor_cmd;
|
||||||
|
apdu.sw = (uint16_t)cbor_parse(cmd, data, len);
|
||||||
if (apdu.sw == 0) {
|
if (apdu.sw == 0) {
|
||||||
DEBUG_DATA(res_APDU + 1, res_APDU_size);
|
DEBUG_DATA(res_APDU, res_APDU_size);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
|
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
|
||||||
res_APDU[-1] = apdu.sw;
|
res_APDU[-1] = (uint8_t)apdu.sw;
|
||||||
apdu.sw = 0;
|
apdu.sw = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
res_APDU[0] = apdu.sw;
|
res_APDU[0] = (uint8_t)apdu.sw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,9 +130,7 @@ void cbor_thread(void) {
|
|||||||
flag = EV_EXEC_FINISHED;
|
flag = EV_EXEC_FINISHED;
|
||||||
queue_add_blocking(&card_to_usb_q, &flag);
|
queue_add_blocking(&card_to_usb_q, &flag);
|
||||||
}
|
}
|
||||||
#ifdef ESP_PLATFORM
|
return NULL;
|
||||||
vTaskDelete(NULL);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
||||||
@@ -149,7 +143,7 @@ int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
|||||||
return 2; // CBOR processing
|
return 2; // CBOR processing
|
||||||
}
|
}
|
||||||
|
|
||||||
CborError COSE_key_params(int crv, int alg, mbedtls_ecp_group *grp, mbedtls_ecp_point *Q, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
|
static CborError COSE_key_params(int crv, int alg, mbedtls_ecp_group *grp, mbedtls_ecp_point *Q, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
int kty = 1;
|
int kty = 1;
|
||||||
if (crv == FIDO2_CURVE_P256 || crv == FIDO2_CURVE_P384 || crv == FIDO2_CURVE_P521 ||
|
if (crv == FIDO2_CURVE_P256 || crv == FIDO2_CURVE_P384 || crv == FIDO2_CURVE_P521 ||
|
||||||
@@ -210,9 +204,14 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
|
|||||||
else if (key->grp.id == MBEDTLS_ECP_DP_CURVE25519) {
|
else if (key->grp.id == MBEDTLS_ECP_DP_CURVE25519) {
|
||||||
alg = FIDO2_ALG_ECDH_ES_HKDF_256;
|
alg = FIDO2_ALG_ECDH_ES_HKDF_256;
|
||||||
}
|
}
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
|
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
|
||||||
alg = FIDO2_ALG_EDDSA;
|
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);
|
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
|
||||||
}
|
}
|
||||||
CborError COSE_key_shared(mbedtls_ecdh_context *key,
|
CborError COSE_key_shared(mbedtls_ecdh_context *key,
|
||||||
|
|||||||
@@ -3,31 +3,28 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ESP_PLATFORM
|
#include "picokeys.h"
|
||||||
#include "common.h"
|
|
||||||
#else
|
|
||||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
|
||||||
#endif
|
|
||||||
#include "mbedtls/ecp.h"
|
#include "mbedtls/ecp.h"
|
||||||
#include "mbedtls/ecdh.h"
|
#include "mbedtls/ecdh.h"
|
||||||
#include "mbedtls/sha256.h"
|
#include "mbedtls/sha256.h"
|
||||||
#include "mbedtls/hkdf.h"
|
#include "mbedtls/hkdf.h"
|
||||||
|
#include "mbedtls/constant_time.h"
|
||||||
#include "cbor.h"
|
#include "cbor.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
@@ -35,9 +32,7 @@
|
|||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "crypto_utils.h"
|
#include "crypto_utils.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "kek.h"
|
|
||||||
|
|
||||||
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
|
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
|
||||||
uint32_t max_usage_time_period = 600 * 1000;
|
uint32_t max_usage_time_period = 600 * 1000;
|
||||||
@@ -45,7 +40,7 @@ bool needs_power_cycle = false;
|
|||||||
static mbedtls_ecdh_context hkey;
|
static mbedtls_ecdh_context hkey;
|
||||||
static bool hkey_init = false;
|
static bool hkey_init = false;
|
||||||
|
|
||||||
int beginUsingPinUvAuthToken(bool userIsPresent) {
|
static int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||||
paut.user_present = userIsPresent;
|
paut.user_present = userIsPresent;
|
||||||
paut.user_verified = true;
|
paut.user_verified = true;
|
||||||
initial_usage_time_limit = board_millis();
|
initial_usage_time_limit = board_millis();
|
||||||
@@ -54,25 +49,25 @@ int beginUsingPinUvAuthToken(bool userIsPresent) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearUserPresentFlag() {
|
void clearUserPresentFlag(void) {
|
||||||
if (paut.in_use == true) {
|
if (paut.in_use == true) {
|
||||||
paut.user_present = false;
|
paut.user_present = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearUserVerifiedFlag() {
|
void clearUserVerifiedFlag(void) {
|
||||||
if (paut.in_use == true) {
|
if (paut.in_use == true) {
|
||||||
paut.user_verified = false;
|
paut.user_verified = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearPinUvAuthTokenPermissionsExceptLbw() {
|
void clearPinUvAuthTokenPermissionsExceptLbw(void) {
|
||||||
if (paut.in_use == true) {
|
if (paut.in_use == true) {
|
||||||
paut.permissions = CTAP_PERMISSION_LBW;
|
paut.permissions = CTAP_PERMISSION_LBW;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopUsingPinUvAuthToken() {
|
static void stopUsingPinUvAuthToken(void) {
|
||||||
paut.permissions = 0;
|
paut.permissions = 0;
|
||||||
usage_timer = 0;
|
usage_timer = 0;
|
||||||
paut.in_use = false;
|
paut.in_use = false;
|
||||||
@@ -83,21 +78,21 @@ void stopUsingPinUvAuthToken() {
|
|||||||
user_present_time_limit = 0;
|
user_present_time_limit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getUserPresentFlagValue() {
|
bool getUserPresentFlagValue(void) {
|
||||||
if (paut.in_use != true) {
|
if (paut.in_use != true) {
|
||||||
paut.user_present = false;
|
paut.user_present = false;
|
||||||
}
|
}
|
||||||
return paut.user_present;
|
return paut.user_present;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getUserVerifiedFlagValue() {
|
bool getUserVerifiedFlagValue(void) {
|
||||||
if (paut.in_use != true) {
|
if (paut.in_use != true) {
|
||||||
paut.user_verified = false;
|
paut.user_verified = false;
|
||||||
}
|
}
|
||||||
return paut.user_verified;
|
return paut.user_verified;
|
||||||
}
|
}
|
||||||
|
|
||||||
int regenerate() {
|
static int regenerate(void) {
|
||||||
if (hkey_init == true) {
|
if (hkey_init == true) {
|
||||||
mbedtls_ecdh_free(&hkey);
|
mbedtls_ecdh_free(&hkey);
|
||||||
}
|
}
|
||||||
@@ -105,11 +100,7 @@ int regenerate() {
|
|||||||
mbedtls_ecdh_init(&hkey);
|
mbedtls_ecdh_init(&hkey);
|
||||||
hkey_init = true;
|
hkey_init = true;
|
||||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
|
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_fill_iterator, NULL);
|
||||||
&hkey.ctx.mbed_ecdh.d,
|
|
||||||
&hkey.ctx.mbed_ecdh.Q,
|
|
||||||
random_gen,
|
|
||||||
NULL);
|
|
||||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return ret;
|
return ret;
|
||||||
@@ -117,7 +108,7 @@ int regenerate() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
static int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
uint8_t buf[32];
|
uint8_t buf[32];
|
||||||
ret = mbedtls_mpi_write_binary(z, buf, sizeof(buf));
|
ret = mbedtls_mpi_write_binary(z, buf, sizeof(buf));
|
||||||
@@ -125,34 +116,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (protocol == 1) {
|
if (protocol == 1) {
|
||||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
|
||||||
buf,
|
|
||||||
sizeof(buf),
|
|
||||||
sharedSecret);
|
|
||||||
}
|
}
|
||||||
else if (protocol == 2) {
|
else if (protocol == 2) {
|
||||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||||
ret = mbedtls_hkdf(md_info,
|
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
buf,
|
|
||||||
sizeof(buf),
|
|
||||||
(uint8_t *) "CTAP2 HMAC key",
|
|
||||||
14,
|
|
||||||
sharedSecret,
|
|
||||||
32);
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
return mbedtls_hkdf(md_info,
|
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
buf,
|
|
||||||
sizeof(buf),
|
|
||||||
(uint8_t *) "CTAP2 AES key",
|
|
||||||
13,
|
|
||||||
sharedSecret + 32,
|
|
||||||
32);
|
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -160,38 +132,50 @@ 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) {
|
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
|
||||||
mbedtls_mpi z;
|
mbedtls_mpi z;
|
||||||
mbedtls_mpi_init(&z);
|
mbedtls_mpi_init(&z);
|
||||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
|
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_fill_iterator, NULL);
|
||||||
&z,
|
|
||||||
Q,
|
|
||||||
&hkey.ctx.mbed_ecdh.d,
|
|
||||||
random_gen,
|
|
||||||
NULL);
|
|
||||||
ret = kdf(protocol, &z, sharedSecret);
|
ret = kdf(protocol, &z, sharedSecret);
|
||||||
mbedtls_mpi_free(&z);
|
mbedtls_mpi_free(&z);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int resetPinUvAuthToken() {
|
static void resetAuthToken(bool persistent) {
|
||||||
|
uint16_t fid = EF_AUTHTOKEN;
|
||||||
|
if (persistent) {
|
||||||
|
fid = EF_PAUTHTOKEN;
|
||||||
|
}
|
||||||
|
file_t *ef = file_search_by_fid(fid, NULL, SPECIFY_EF);
|
||||||
uint8_t t[32];
|
uint8_t t[32];
|
||||||
random_gen(NULL, t, sizeof(t));
|
random_fill_buffer(t, sizeof(t));
|
||||||
file_put_data(ef_authtoken, t, sizeof(t));
|
file_put_data(ef, t, sizeof(t));
|
||||||
|
flash_commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
int resetPinUvAuthToken(void) {
|
||||||
|
resetAuthToken(false);
|
||||||
paut.permissions = 0;
|
paut.permissions = 0;
|
||||||
paut.data = file_get_data(ef_authtoken);
|
paut.data = file_get_data(ef_authtoken);
|
||||||
paut.len = file_get_size(ef_authtoken);
|
paut.len = file_get_size(ef_authtoken);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
low_flash_available();
|
int resetPersistentPinUvAuthToken(void) {
|
||||||
|
resetAuthToken(true);
|
||||||
|
file_t *ef_pauthtoken = file_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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
|
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
|
||||||
if (protocol == 1) {
|
if (protocol == 1) {
|
||||||
memcpy(out, in, in_len);
|
memcpy(out, in, in_len);
|
||||||
return aes_encrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
|
return aes_encrypt(key, NULL, 32 * 8, PICOKEYS_AES_MODE_CBC, out, in_len);
|
||||||
}
|
}
|
||||||
else if (protocol == 2) {
|
else if (protocol == 2) {
|
||||||
random_gen(NULL, out, IV_SIZE);
|
random_fill_buffer(out, IV_SIZE);
|
||||||
memcpy(out + IV_SIZE, in, in_len);
|
memcpy(out + IV_SIZE, in, in_len);
|
||||||
return aes_encrypt(key + 32, out, 32 * 8, PICO_KEYS_AES_MODE_CBC, out + IV_SIZE, in_len);
|
return aes_encrypt(key + 32, out, 32 * 8, PICOKEYS_AES_MODE_CBC, out + IV_SIZE, in_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
@@ -200,21 +184,17 @@ int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
|
|||||||
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
|
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
|
||||||
if (protocol == 1) {
|
if (protocol == 1) {
|
||||||
memcpy(out, in, in_len);
|
memcpy(out, in, in_len);
|
||||||
return aes_decrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
|
return aes_decrypt(key, NULL, 32 * 8, PICOKEYS_AES_MODE_CBC, out, in_len);
|
||||||
}
|
}
|
||||||
else if (protocol == 2) {
|
else if (protocol == 2) {
|
||||||
memcpy(out, in + IV_SIZE, in_len - IV_SIZE);
|
memcpy(out, in + IV_SIZE, in_len - IV_SIZE);
|
||||||
return aes_decrypt(key + 32, in, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len - IV_SIZE);
|
return aes_decrypt(key + 32, in, 32 * 8, PICOKEYS_AES_MODE_CBC, out, in_len - IV_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int authenticate(uint8_t protocol,
|
static int __attribute__((unused)) authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
|
||||||
const uint8_t *key,
|
|
||||||
const uint8_t *data,
|
|
||||||
size_t len,
|
|
||||||
uint8_t *sign) {
|
|
||||||
uint8_t hmac[32];
|
uint8_t hmac[32];
|
||||||
int ret =
|
int ret =
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||||
@@ -242,24 +222,24 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
if (protocol == 1) {
|
if (protocol == 1) {
|
||||||
return memcmp(sign, hmac, 16);
|
return mbedtls_ct_memcmp(sign, hmac, 16);
|
||||||
}
|
}
|
||||||
else if (protocol == 2) {
|
else if (protocol == 2) {
|
||||||
return memcmp(sign, hmac, 32);
|
return mbedtls_ct_memcmp(sign, hmac, 32);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int initialize() {
|
static int initialize(void) {
|
||||||
regenerate();
|
regenerate();
|
||||||
return resetPinUvAuthToken();
|
return resetPinUvAuthToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPublicKey() {
|
static int __attribute__((unused)) getPublicKey(void) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int pinUvAuthTokenUsageTimerObserver() {
|
static int __attribute__((unused)) pinUvAuthTokenUsageTimerObserver(void) {
|
||||||
if (usage_timer == 0) {
|
if (usage_timer == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -280,19 +260,16 @@ int pinUvAuthTokenUsageTimerObserver() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int check_mkek_encrypted(const uint8_t *dhash) {
|
static int check_keydev_encrypted(const uint8_t pin_token[32]) {
|
||||||
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
|
if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
|
||||||
hash_multi(dhash, 16, session_pin); // Only for storing MKEK
|
uint8_t tmp_keydev[61];
|
||||||
uint8_t mkek[MKEK_SIZE] = {0};
|
tmp_keydev[0] = 0x03; // Change format to encrypted
|
||||||
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, 2, tmp_keydev + 1);
|
||||||
int ret = store_mkek(mkek);
|
file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
|
||||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
|
||||||
mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
|
flash_commit();
|
||||||
if (ret != PICOKEY_OK) {
|
|
||||||
return CTAP2_ERR_PIN_AUTH_INVALID;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t new_pin_mismatches = 0;
|
uint8_t new_pin_mismatches = 0;
|
||||||
@@ -305,11 +282,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
CborEncoder encoder, mapEncoder;
|
CborEncoder encoder, mapEncoder;
|
||||||
CborValue map;
|
CborValue map;
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
|
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
|
||||||
kay = { 0 };
|
|
||||||
CborCharString rpId = { 0 };
|
CborCharString rpId = { 0 };
|
||||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||||
uint64_t val_c = 1;
|
uint64_t val_c = 1;
|
||||||
|
uint8_t keydev[32] = {0};
|
||||||
if (hkey_init == false) {
|
if (hkey_init == false) {
|
||||||
initialize();
|
initialize();
|
||||||
}
|
}
|
||||||
@@ -424,27 +401,32 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
pin_len++;
|
pin_len++;
|
||||||
}
|
}
|
||||||
uint8_t minPin = 4;
|
uint8_t minPin = 4;
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_minpin)) {
|
if (file_has_data(ef_minpin)) {
|
||||||
minPin = *file_get_data(ef_minpin);
|
minPin = *file_get_data(ef_minpin);
|
||||||
}
|
}
|
||||||
if (pin_len < minPin) {
|
if (pin_len < minPin) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
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[0] = MAX_PIN_RETRIES;
|
||||||
hsh[1] = pin_len;
|
hsh[1] = pin_len;
|
||||||
|
hsh[2] = 1; // New format indicator
|
||||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||||
double_hash_pin(dhash, 16, hsh + 2);
|
mbedtls_platform_zeroize(paddedNewPin, sizeof(paddedNewPin));
|
||||||
file_put_data(ef_pin, hsh, 2 + 32);
|
pin_derive_verifier(dhash, 16, hsh + 3);
|
||||||
low_flash_available();
|
file_put_data(ef_pin, hsh, sizeof(hsh));
|
||||||
|
flash_commit();
|
||||||
|
|
||||||
ret = check_mkek_encrypted(dhash);
|
pin_derive_session(dhash, 16, session_pin);
|
||||||
if (ret != PICOKEY_OK) {
|
ret = check_keydev_encrypted(session_pin);
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
CBOR_ERROR(ret);
|
CBOR_ERROR(ret);
|
||||||
}
|
}
|
||||||
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
||||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||||
|
needs_power_cycle = false;
|
||||||
|
|
||||||
goto err; //No return
|
goto err; //No return
|
||||||
}
|
}
|
||||||
else if (subcommand == 0x4) { //changePIN
|
else if (subcommand == 0x4) { //changePIN
|
||||||
@@ -473,6 +455,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
if (needs_power_cycle) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||||
|
}
|
||||||
uint8_t sharedSecret[64];
|
uint8_t sharedSecret[64];
|
||||||
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@@ -486,11 +471,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
uint8_t pin_data[34];
|
uint8_t pin_data[35];
|
||||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||||
pin_data[0] -= 1;
|
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();
|
flash_commit();
|
||||||
uint8_t retries = pin_data[0];
|
uint8_t retries = pin_data[0];
|
||||||
uint8_t paddedNewPin[64];
|
uint8_t paddedNewPin[64];
|
||||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
||||||
@@ -498,9 +483,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
uint8_t dhash[32];
|
uint8_t dhash[32], off = 3;
|
||||||
double_hash_pin(paddedNewPin, 16, dhash);
|
if (file_get_size(ef_pin) == 34) {
|
||||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
off = 2;
|
||||||
|
double_hash_pin(paddedNewPin, 16, dhash);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mbedtls_ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||||
regenerate();
|
regenerate();
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
if (retries == 0) {
|
if (retries == 0) {
|
||||||
@@ -514,10 +506,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
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 != PICOKEYS_OK) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||||
|
}
|
||||||
|
encrypt_keydev_f1(keydev);
|
||||||
|
}
|
||||||
|
pin_derive_session(paddedNewPin, 16, session_pin);
|
||||||
pin_data[0] = MAX_PIN_RETRIES;
|
pin_data[0] = MAX_PIN_RETRIES;
|
||||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
|
|
||||||
|
ret = check_keydev_encrypted(session_pin);
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
|
CBOR_ERROR(ret);
|
||||||
|
}
|
||||||
|
|
||||||
new_pin_mismatches = 0;
|
new_pin_mismatches = 0;
|
||||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
@@ -532,42 +542,40 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
pin_len++;
|
pin_len++;
|
||||||
}
|
}
|
||||||
uint8_t minPin = 4;
|
uint8_t minPin = 4;
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_minpin)) {
|
if (file_has_data(ef_minpin)) {
|
||||||
minPin = *file_get_data(ef_minpin);
|
minPin = *file_get_data(ef_minpin);
|
||||||
}
|
}
|
||||||
if (pin_len < minPin) {
|
if (pin_len < minPin) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||||
}
|
}
|
||||||
uint8_t hsh[34];
|
|
||||||
hsh[0] = MAX_PIN_RETRIES;
|
// New PIN is valid and verified
|
||||||
hsh[1] = pin_len;
|
ret = load_keydev(keydev);
|
||||||
|
if (ret != PICOKEYS_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);
|
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||||
double_hash_pin(dhash, 16, hsh + 2);
|
pin_derive_session(dhash, 16, session_pin);
|
||||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
|
ret = check_keydev_encrypted(session_pin);
|
||||||
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
|
if (ret != PICOKEYS_OK) {
|
||||||
|
CBOR_ERROR(ret);
|
||||||
|
}
|
||||||
|
flash_commit();
|
||||||
|
|
||||||
|
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 && mbedtls_ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||||
}
|
}
|
||||||
|
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||||
|
|
||||||
uint8_t mkek[MKEK_SIZE] = {0};
|
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||||
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(dhash, sizeof(dhash));
|
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
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));
|
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
|
||||||
@@ -576,8 +584,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
file_put_data(ef_minpin, tmpf, file_get_size(ef_minpin));
|
file_put_data(ef_minpin, tmpf, file_get_size(ef_minpin));
|
||||||
free(tmpf);
|
free(tmpf);
|
||||||
}
|
}
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
resetPinUvAuthToken();
|
resetPinUvAuthToken();
|
||||||
|
resetPersistentPinUvAuthToken();
|
||||||
|
needs_power_cycle = false;
|
||||||
goto err; // No return
|
goto err; // No return
|
||||||
}
|
}
|
||||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
||||||
@@ -598,7 +608,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
|
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
|
||||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
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)) {
|
if (!file_has_data(ef_pin)) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||||
@@ -612,17 +624,20 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
if (needs_power_cycle) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||||
|
}
|
||||||
uint8_t sharedSecret[64];
|
uint8_t sharedSecret[64];
|
||||||
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
uint8_t pin_data[34];
|
uint8_t pin_data[35];
|
||||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||||
pin_data[0] -= 1;
|
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();
|
flash_commit();
|
||||||
uint8_t retries = pin_data[0];
|
uint8_t retries = pin_data[0];
|
||||||
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
|
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
|
||||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
||||||
@@ -630,11 +645,18 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
uint8_t dhash[32];
|
uint8_t dhash[32], off = 3;
|
||||||
double_hash_pin(paddedNewPin, 16, dhash);
|
if (file_get_size(ef_pin) == 34) {
|
||||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
off = 2;
|
||||||
|
double_hash_pin(paddedNewPin, 16, dhash);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||||
|
}
|
||||||
|
if (mbedtls_ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||||
regenerate();
|
regenerate();
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
|
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||||
if (retries == 0) {
|
if (retries == 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||||
}
|
}
|
||||||
@@ -646,42 +668,63 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||||
|
|
||||||
ret = check_mkek_encrypted(paddedNewPin);
|
if (off == 2) {
|
||||||
if (ret != PICOKEY_OK) {
|
// 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 != PICOKEYS_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 != PICOKEYS_OK) {
|
||||||
CBOR_ERROR(ret);
|
CBOR_ERROR(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
hash_multi(paddedNewPin, 16, session_pin);
|
|
||||||
pin_data[0] = MAX_PIN_RETRIES;
|
pin_data[0] = MAX_PIN_RETRIES;
|
||||||
new_pin_mismatches = 0;
|
new_pin_mismatches = 0;
|
||||||
|
|
||||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
|
||||||
|
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||||
}
|
}
|
||||||
resetPinUvAuthToken();
|
uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
|
||||||
beginUsingPinUvAuthToken(false);
|
if (permissions & CTAP_PERMISSION_PCMR) {
|
||||||
if (subcommand == 0x05) {
|
ppaut.permissions = CTAP_PERMISSION_PCMR;
|
||||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
pdata = ppaut.data;
|
||||||
}
|
|
||||||
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 {
|
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, pdata, 32, pinUvAuthToken_enc);
|
||||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
|
||||||
|
needs_power_cycle = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||||
@@ -695,6 +738,7 @@ err:
|
|||||||
CBOR_FREE_BYTE_STRING(kax);
|
CBOR_FREE_BYTE_STRING(kax);
|
||||||
CBOR_FREE_BYTE_STRING(kay);
|
CBOR_FREE_BYTE_STRING(kay);
|
||||||
CBOR_FREE_BYTE_STRING(rpId);
|
CBOR_FREE_BYTE_STRING(rpId);
|
||||||
|
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||||
if (error != CborNoError) {
|
if (error != CborNoError) {
|
||||||
if (error == CborErrorImproperValue) {
|
if (error == CborErrorImproperValue) {
|
||||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "mbedtls/ecdh.h"
|
#include "mbedtls/ecdh.h"
|
||||||
#include "mbedtls/chachapoly.h"
|
#include "mbedtls/chachapoly.h"
|
||||||
@@ -31,14 +31,13 @@
|
|||||||
|
|
||||||
extern uint8_t keydev_dec[32];
|
extern uint8_t keydev_dec[32];
|
||||||
extern bool has_keydev_dec;
|
extern bool has_keydev_dec;
|
||||||
|
|
||||||
int cbor_config(const uint8_t *data, size_t len) {
|
int cbor_config(const uint8_t *data, size_t len) {
|
||||||
CborParser parser;
|
CborParser parser;
|
||||||
CborValue map;
|
CborValue map;
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0;
|
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
|
||||||
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
|
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
|
||||||
CborCharString minPinLengthRPIDs[32] = { 0 };
|
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
|
||||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||||
CborEncoder encoder;
|
CborEncoder encoder;
|
||||||
//CborEncoder mapEncoder;
|
//CborEncoder mapEncoder;
|
||||||
@@ -66,13 +65,19 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
||||||
CBOR_PARSE_MAP_START(_f1, 2)
|
CBOR_PARSE_MAP_START(_f1, 2)
|
||||||
{
|
{
|
||||||
if (subcommand == 0x7f) { // Config Aut
|
if (subcommand == 0xFF) { // Vendor
|
||||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||||
if (subpara == 0x01) {
|
if (subpara == 0x01) {
|
||||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||||
}
|
}
|
||||||
else if (subpara == 0x02) {
|
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
|
else if (subcommand == 0x03) { // Extensions
|
||||||
@@ -95,15 +100,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
|
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);
|
CBOR_PARSE_MAP_END(_f1, 2);
|
||||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||||
@@ -122,9 +118,12 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
if (pinUvAuthParam.present == false) {
|
if (pinUvAuthParam.present == false) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||||
}
|
}
|
||||||
if (pinUvAuthProtocol == 0) {
|
if (pinUvAuthProtocol == 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
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);
|
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
|
||||||
memset(verify_payload, 0xff, 32);
|
memset(verify_payload, 0xff, 32);
|
||||||
@@ -141,8 +140,8 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subcommand == 0x7f) {
|
if (subcommand == 0xFF) {
|
||||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
|
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
|
||||||
if (!file_has_data(ef_keydev_enc)) {
|
if (!file_has_data(ef_keydev_enc)) {
|
||||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
@@ -152,7 +151,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
file_put_data(ef_keydev, keydev_dec, sizeof(keydev_dec));
|
file_put_data(ef_keydev, keydev_dec, sizeof(keydev_dec));
|
||||||
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
|
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
|
||||||
file_put_data(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
|
file_put_data(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
|
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
|
||||||
if (!file_has_data(ef_keydev)) {
|
if (!file_has_data(ef_keydev)) {
|
||||||
@@ -163,15 +162,15 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_chachapoly_context chatx;
|
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) {
|
if (ret != 0) {
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t key_dev_enc[12 + 32 + 16];
|
uint8_t key_dev_enc[12 + 32 + 16];
|
||||||
random_gen(NULL, key_dev_enc, 12);
|
random_fill_buffer(key_dev_enc, 12);
|
||||||
mbedtls_chachapoly_init(&chatx);
|
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));
|
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);
|
mbedtls_chachapoly_free(&chatx);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@@ -182,7 +181,32 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
|
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
|
||||||
file_put_data(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
|
file_put_data(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||||
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
|
}
|
||||||
|
else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
|
||||||
|
if (vendorParamByteString.present == false) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||||
|
}
|
||||||
|
file_t *ef_ee_ea = file_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);
|
||||||
|
}
|
||||||
|
flash_commit();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flash_commit();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||||
@@ -191,7 +215,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
else if (subcommand == 0x03) {
|
else if (subcommand == 0x03) {
|
||||||
uint8_t currentMinPinLen = 4;
|
uint8_t currentMinPinLen = 4;
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_minpin)) {
|
if (file_has_data(ef_minpin)) {
|
||||||
currentMinPinLen = *file_get_data(ef_minpin);
|
currentMinPinLen = *file_get_data(ef_minpin);
|
||||||
}
|
}
|
||||||
@@ -207,6 +231,10 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
|
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
|
||||||
forceChangePin = ptrue;
|
forceChangePin = ptrue;
|
||||||
}
|
}
|
||||||
|
if (forceChangePin) {
|
||||||
|
resetPersistentPinUvAuthToken();
|
||||||
|
resetPinUvAuthToken();
|
||||||
|
}
|
||||||
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
||||||
dataf[0] = (uint8_t)newMinPinLength;
|
dataf[0] = (uint8_t)newMinPinLength;
|
||||||
dataf[1] = forceChangePin == ptrue ? 1 : 0;
|
dataf[1] = forceChangePin == ptrue ? 1 : 0;
|
||||||
@@ -214,7 +242,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
mbedtls_sha256((uint8_t *) minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, dataf + 2 + m * 32, 0);
|
mbedtls_sha256((uint8_t *) minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, dataf + 2 + m * 32, 0);
|
||||||
}
|
}
|
||||||
file_put_data(ef_minpin, dataf, (uint16_t)(2 + minPinLengthRPIDs_len * 32));
|
file_put_data(ef_minpin, dataf, (uint16_t)(2 + minPinLengthRPIDs_len * 32));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
free(dataf);
|
free(dataf);
|
||||||
goto err; //No return
|
goto err; //No return
|
||||||
}
|
}
|
||||||
@@ -222,35 +250,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
set_opts(get_opts() | FIDO2_OPT_EA);
|
set_opts(get_opts() | FIDO2_OPT_EA);
|
||||||
goto err;
|
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 {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||||
}
|
}
|
||||||
@@ -259,7 +258,8 @@ int cbor_config(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
err:
|
err:
|
||||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
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++) {
|
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
@@ -22,7 +23,6 @@
|
|||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
|
|
||||||
uint8_t rp_counter = 1;
|
uint8_t rp_counter = 1;
|
||||||
uint8_t rp_total = 0;
|
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) {
|
if (strcmp(_fd3, "transports") == 0) {
|
||||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||||
{
|
{
|
||||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
|
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
|
||||||
transports_len], 4);
|
|
||||||
credentialId.transports_len++;
|
credentialId.transports_len++;
|
||||||
}
|
}
|
||||||
CBOR_PARSE_ARRAY_END(_f3, 4);
|
CBOR_PARSE_ARRAY_END(_f3, 4);
|
||||||
@@ -122,16 +121,23 @@ 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);
|
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||||
if (subcommand == 0x01) {
|
if (subcommand == 0x01) {
|
||||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (is_preview == false &&
|
else {
|
||||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
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;
|
uint8_t existing = 0;
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
if (file_has_data(search_dynamic_file((uint16_t)(EF_CRED + i)))) {
|
if (file_has_data(file_search((uint16_t)(EF_CRED + i)))) {
|
||||||
existing++;
|
existing++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
else if (subcommand == 0x02 || subcommand == 0x03) {
|
else if (subcommand == 0x02 || subcommand == 0x03) {
|
||||||
file_t *rp_ef = NULL;
|
file_t *rp_ef = NULL;
|
||||||
if (subcommand == 0x02) {
|
if (subcommand == 0x02) {
|
||||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
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)) {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
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_counter = 1;
|
||||||
rp_total = 0;
|
rp_total = 0;
|
||||||
@@ -160,7 +173,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
uint8_t skip = 0;
|
uint8_t skip = 0;
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
file_t *tef = search_dynamic_file((uint16_t)(EF_RP + i));
|
file_t *tef = file_search((uint16_t)(EF_RP + i));
|
||||||
if (file_has_data(tef) && *file_get_data(tef) > 0) {
|
if (file_has_data(tef) && *file_get_data(tef) > 0) {
|
||||||
if (++skip == rp_counter) {
|
if (++skip == rp_counter) {
|
||||||
if (rp_ef == NULL) {
|
if (rp_ef == NULL) {
|
||||||
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
if (subcommand == 0x04) {
|
if (subcommand == 0x04) {
|
||||||
*(raw_subpara - 1) = 0x04;
|
*(raw_subpara - 1) = 0x04;
|
||||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||||
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (is_preview == false &&
|
else {
|
||||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
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_counter = 1;
|
||||||
cred_total = 0;
|
cred_total = 0;
|
||||||
@@ -219,7 +239,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
file_t *cred_ef = NULL;
|
file_t *cred_ef = NULL;
|
||||||
uint8_t skip = 0;
|
uint8_t skip = 0;
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
file_t *tef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
file_t *tef = file_search((uint16_t)(EF_CRED + i));
|
||||||
if (file_has_data(tef) && memcmp(file_get_data(tef), rpIdHash.data, 32) == 0) {
|
if (file_has_data(tef) && memcmp(file_get_data(tef), rpIdHash.data, 32) == 0) {
|
||||||
if (++skip == cred_counter) {
|
if (++skip == cred_counter) {
|
||||||
if (cred_ef == NULL) {
|
if (cred_ef == NULL) {
|
||||||
@@ -239,15 +259,15 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Credential cred = { 0 };
|
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);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_ecdsa_context key;
|
mbedtls_ecp_keypair key;
|
||||||
mbedtls_ecdsa_init(&key);
|
mbedtls_ecp_keypair_init(&key);
|
||||||
if (fido_load_key((int)cred.curve, cred.id.data, &key) != 0) {
|
if (fido_load_key((int)cred.curve, cred.id.data, &key) != 0) {
|
||||||
credential_free(&cred);
|
credential_free(&cred);
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
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_encode_uint(&mapEncoder, 0x07));
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
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, "type"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||||
@@ -335,7 +357,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||||
}
|
}
|
||||||
credential_free(&cred);
|
credential_free(&cred);
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
}
|
}
|
||||||
else if (subcommand == 0x06) {
|
else if (subcommand == 0x06) {
|
||||||
if (credentialId.id.present == false) {
|
if (credentialId.id.present == false) {
|
||||||
@@ -351,20 +373,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
file_t *ef = file_search((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);
|
uint8_t *rp_id_hash = file_get_data(ef);
|
||||||
if (delete_file(ef) != 0) {
|
if (file_delete(ef) != 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
for (int j = 0; j < MAX_RESIDENT_CREDENTIALS; j++) {
|
for (int j = 0; j < MAX_RESIDENT_CREDENTIALS; j++) {
|
||||||
file_t *rp_ef = search_dynamic_file((uint16_t)(EF_RP + j));
|
file_t *rp_ef = file_search((uint16_t)(EF_RP + j));
|
||||||
if (file_has_data(rp_ef) && memcmp(file_get_data(rp_ef) + 1, rp_id_hash, 32) == 0) {
|
if (file_has_data(rp_ef) && memcmp(file_get_data(rp_ef) + 1, rp_id_hash, 32) == 0) {
|
||||||
uint8_t *rp_data = (uint8_t *) calloc(1, file_get_size(rp_ef));
|
uint8_t *rp_data = (uint8_t *) calloc(1, file_get_size(rp_ef));
|
||||||
memcpy(rp_data, file_get_data(rp_ef), file_get_size(rp_ef));
|
memcpy(rp_data, file_get_data(rp_ef), file_get_size(rp_ef));
|
||||||
rp_data[0] -= 1;
|
rp_data[0] -= 1;
|
||||||
if (rp_data[0] == 0) {
|
if (rp_data[0] == 0) {
|
||||||
delete_file(rp_ef);
|
file_delete(rp_ef);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
file_put_data(rp_ef, rp_data, file_get_size(rp_ef));
|
file_put_data(rp_ef, rp_data, file_get_size(rp_ef));
|
||||||
@@ -373,7 +395,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
goto err; //no error
|
goto err; //no error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,11 +415,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
file_t *ef = file_search((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 };
|
Credential cred = { 0 };
|
||||||
uint8_t *rp_id_hash = file_get_data(ef);
|
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);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
|
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
|
||||||
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
uint8_t newcred[MAX_CRED_ID_LENGTH];
|
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,
|
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
|
||||||
&user.displayName, &cred.opts, &cred.extensions,
|
&user.displayName, &cred.opts, &cred.extensions,
|
||||||
cred.use_sign_count, (int)cred.alg,
|
cred.use_sign_count, (int)cred.alg,
|
||||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||||
credential_free(&cred);
|
credential_free(&cred);
|
||||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
@@ -417,7 +439,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
|||||||
if (credential_store(newcred, newcred_len, rp_id_hash) != 0) {
|
if (credential_store(newcred, newcred_len, rp_id_hash) != 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
goto err; //no error
|
goto err; //no error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,28 +3,28 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "cbor.h"
|
#include "cbor.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "crypto_utils.h"
|
#include "crypto_utils.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "cbor_make_credential.h"
|
#include "cbor_make_credential.h"
|
||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "random.h"
|
#include "random.h"
|
||||||
|
|
||||||
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||||
|
extern char *rp_id, *user_name, *display_name;
|
||||||
|
|
||||||
bool residentx = false;
|
bool residentx = false;
|
||||||
Credential credsx[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
Credential credsx[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||||
@@ -42,6 +43,22 @@ uint32_t timerx = 0;
|
|||||||
uint8_t *datax = NULL;
|
uint8_t *datax = NULL;
|
||||||
size_t lenx = 0;
|
size_t lenx = 0;
|
||||||
|
|
||||||
|
void reset_gna_state(void) {
|
||||||
|
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||||
|
credential_free(&credsx[i]);
|
||||||
|
}
|
||||||
|
if (datax) {
|
||||||
|
free(datax);
|
||||||
|
datax = NULL;
|
||||||
|
}
|
||||||
|
lenx = 0;
|
||||||
|
residentx = false;
|
||||||
|
timerx = 0;
|
||||||
|
flagsx = 0;
|
||||||
|
credentialCounter = 0;
|
||||||
|
numberOfCredentialsx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
|
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
|
||||||
(void) data;
|
(void) data;
|
||||||
(void) len;
|
(void) len;
|
||||||
@@ -57,19 +74,7 @@ int cbor_get_next_assertion(const uint8_t *data, size_t len) {
|
|||||||
credentialCounter++;
|
credentialCounter++;
|
||||||
err:
|
err:
|
||||||
if (error != CborNoError || credentialCounter == numberOfCredentialsx) {
|
if (error != CborNoError || credentialCounter == numberOfCredentialsx) {
|
||||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
reset_gna_state();
|
||||||
credential_free(&credsx[i]);
|
|
||||||
}
|
|
||||||
if (datax) {
|
|
||||||
free(datax);
|
|
||||||
datax = NULL;
|
|
||||||
}
|
|
||||||
lenx = 0;
|
|
||||||
residentx = false;
|
|
||||||
timerx = 0;
|
|
||||||
flagsx = 0;
|
|
||||||
credentialCounter = 0;
|
|
||||||
numberOfCredentialsx = 0;
|
|
||||||
if (error == CborErrorImproperValue) {
|
if (error == CborErrorImproperValue) {
|
||||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||||
}
|
}
|
||||||
@@ -93,7 +98,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||||
size_t allowList_len = 0, creds_len = 0;
|
size_t allowList_len = 0, creds_len = 0;
|
||||||
uint8_t *aut_data = NULL;
|
uint8_t *aut_data = NULL;
|
||||||
bool asserted = false, up = true, uv = false;
|
bool asserted = false, up = false, uv = false;
|
||||||
int64_t kty = 2, alg = 0, crv = 0;
|
int64_t kty = 2, alg = 0, crv = 0;
|
||||||
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
||||||
const bool *credBlob = NULL;
|
const bool *credBlob = NULL;
|
||||||
@@ -201,6 +206,9 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (rpId.present == false || clientDataHash.present == false) {
|
if (rpId.present == false || clientDataHash.present == false) {
|
||||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||||
}
|
}
|
||||||
|
rp_id = rpId.data;
|
||||||
|
user_name = NULL;
|
||||||
|
display_name = NULL;
|
||||||
|
|
||||||
uint8_t flags = 0;
|
uint8_t flags = 0;
|
||||||
uint8_t rp_id_hash[32] = {0};
|
uint8_t rp_id_hash[32] = {0};
|
||||||
@@ -235,6 +243,12 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (options.uv == ptrue) { //4.3
|
if (options.uv == ptrue) { //4.3
|
||||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||||
}
|
}
|
||||||
|
if (options.uv == NULL || pinUvAuthParam.present == true) {
|
||||||
|
uv = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
uv = *options.uv;
|
||||||
|
}
|
||||||
//if (options.up != NULL) { //4.5
|
//if (options.up != NULL) { //4.5
|
||||||
// CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
// CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||||
//}
|
//}
|
||||||
@@ -243,12 +257,12 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
}
|
}
|
||||||
//else if (options.up == NULL) //5.7
|
//else if (options.up == NULL) //5.7
|
||||||
//rup = ptrue;
|
//rup = ptrue;
|
||||||
if (options.uv != NULL) {
|
|
||||||
uv = *options.uv;
|
|
||||||
}
|
|
||||||
if (options.up != NULL) {
|
if (options.up != NULL) {
|
||||||
up = *options.up;
|
up = *options.up;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
up = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pinUvAuthParam.present == true) { //6.1
|
if (pinUvAuthParam.present == true) { //6.1
|
||||||
@@ -289,26 +303,65 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||||
credential_free(&creds[creds_len]);
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||||
|
file_t *ef = file_search((uint16_t)(EF_CRED + i));
|
||||||
|
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||||
|
if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
|
||||||
|
// Should never happen
|
||||||
|
credential_free(&creds[creds_len]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
resident = true;
|
||||||
|
creds_len++;
|
||||||
|
silent = false; // If we are able to load a credential, we are not silent
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
creds_len++;
|
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||||
|
credential_free(&creds[creds_len]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
creds_len++;
|
||||||
|
silent = false; // If we are able to load a credential, we are not silent
|
||||||
|
// Even we provide allowList, we need to check if the credential is resident
|
||||||
|
if (!resident) {
|
||||||
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||||
|
file_t *ef = file_search((uint16_t)(EF_CRED + i));
|
||||||
|
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
|
||||||
|
resident = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resident) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
file_t *ef = file_search((uint16_t)(EF_CRED + i));
|
||||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||||
continue;
|
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) {
|
if (ret != 0) {
|
||||||
credential_free(&creds[creds_len]);
|
credential_free(&creds[creds_len]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
creds_len++;
|
creds_len++;
|
||||||
|
silent = false; // If we are able to load a credential, we are not silent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resident = true;
|
resident = true;
|
||||||
@@ -319,8 +372,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)) {
|
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||||
credential_free(&creds[i]);
|
credential_free(&creds[i]);
|
||||||
}
|
}
|
||||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
|
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||||
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
|
|
||||||
credential_free(&creds[i]);
|
credential_free(&creds[i]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -351,8 +403,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
|
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||||
numberOfCredentials++;
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||||
|
file_t *ef = file_search((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++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,7 +432,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (!silent) {
|
if (!silent) {
|
||||||
for (int i = 0; i < numberOfCredentials; i++) {
|
for (int i = 0; i < numberOfCredentials; i++) {
|
||||||
for (int j = i + 1; j < numberOfCredentials; j++) {
|
for (int j = i + 1; j < numberOfCredentials; j++) {
|
||||||
if (creds[j].creation > creds[i].creation) {
|
if (creds[j].board_creation > creds[i].board_creation) {
|
||||||
Credential tmp = creds[j];
|
Credential tmp = creds[j];
|
||||||
creds[j] = creds[i];
|
creds[j] = creds[i];
|
||||||
creds[i] = tmp;
|
creds[i] = tmp;
|
||||||
@@ -403,10 +471,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
selcred = &creds[0];
|
selcred = &creds[0];
|
||||||
|
if (resident && allowList_len > 1) {
|
||||||
|
numberOfCredentials = 1;
|
||||||
|
}
|
||||||
if (numberOfCredentials > 1) {
|
if (numberOfCredentials > 1) {
|
||||||
asserted = true;
|
asserted = true;
|
||||||
residentx = resident;
|
residentx = resident;
|
||||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
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];
|
credsx[i] = creds[i];
|
||||||
}
|
}
|
||||||
numberOfCredentialsx = numberOfCredentials;
|
numberOfCredentialsx = numberOfCredentials;
|
||||||
@@ -445,91 +519,93 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (options.up == pfalse) {
|
if (options.up == pfalse) {
|
||||||
extensions.hmac_secret = NULL;
|
extensions.hmac_secret = NULL;
|
||||||
}
|
}
|
||||||
if (extensions.hmac_secret != NULL) {
|
if (extensions.hmac_secret == ptrue) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
if (credBlob == ptrue) {
|
if (credBlob == ptrue) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
if (extensions.thirdPartyPayment != NULL) {
|
if (extensions.thirdPartyPayment == ptrue) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
if (l > 0) {
|
||||||
if (credBlob == ptrue) {
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
if (credBlob == ptrue) {
|
||||||
if (selcred->extensions.credBlob.present == true) {
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data,
|
if (selcred->extensions.credBlob.present == true) {
|
||||||
selcred->extensions.credBlob.len));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data,
|
||||||
|
selcred->extensions.credBlob.len));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
if (extensions.hmac_secret == ptrue) {
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (extensions.hmac_secret != NULL) {
|
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||||
|
|
||||||
uint8_t sharedSecret[64] = {0};
|
uint8_t sharedSecret[64] = {0};
|
||||||
mbedtls_ecp_point Qp;
|
mbedtls_ecp_point Qp;
|
||||||
mbedtls_ecp_point_init(&Qp);
|
mbedtls_ecp_point_init(&Qp);
|
||||||
mbedtls_mpi_lset(&Qp.Z, 1);
|
mbedtls_mpi_lset(&Qp.Z, 1);
|
||||||
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
|
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
|
||||||
|
mbedtls_ecp_point_free(&Qp);
|
||||||
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
|
||||||
|
mbedtls_ecp_point_free(&Qp);
|
||||||
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
|
||||||
mbedtls_ecp_point_free(&Qp);
|
mbedtls_ecp_point_free(&Qp);
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
if (ret != 0) {
|
||||||
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
|
||||||
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
|
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
|
||||||
|
}
|
||||||
|
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
|
||||||
|
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
|
||||||
|
if (ret != 0) {
|
||||||
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
uint8_t cred_random[64] = {0}, *crd = NULL;
|
||||||
|
ret = credential_derive_hmac_key(selcred->id.data, selcred->id.len, cred_random);
|
||||||
|
if (ret != 0) {
|
||||||
|
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||||
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
|
}
|
||||||
|
if (flags & FIDO2_AUT_FLAG_UV) {
|
||||||
|
crd = cred_random + 32;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
crd = cred_random;
|
||||||
|
}
|
||||||
|
uint8_t out1[64] = {0}, hmac_res[80] = {0};
|
||||||
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
|
||||||
|
if ((uint8_t)salt_enc.len == 64 + poff) {
|
||||||
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
|
||||||
|
}
|
||||||
|
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
||||||
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
||||||
}
|
}
|
||||||
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
|
if (extensions.thirdPartyPayment == ptrue) {
|
||||||
mbedtls_ecp_point_free(&Qp);
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "thirdPartyPayment"));
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
if (selcred->extensions.thirdPartyPayment == ptrue) {
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
|
|
||||||
mbedtls_ecp_point_free(&Qp);
|
|
||||||
if (ret != 0) {
|
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
|
||||||
}
|
|
||||||
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
|
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
|
||||||
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
|
|
||||||
}
|
|
||||||
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
|
|
||||||
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
|
|
||||||
if (ret != 0) {
|
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
|
||||||
}
|
|
||||||
uint8_t cred_random[64] = {0}, *crd = NULL;
|
|
||||||
ret = credential_derive_hmac_key(selcred->id.data, selcred->id.len, cred_random);
|
|
||||||
if (ret != 0) {
|
|
||||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
|
||||||
}
|
|
||||||
if (flags & FIDO2_AUT_FLAG_UV) {
|
|
||||||
crd = cred_random + 32;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
crd = cred_random;
|
|
||||||
}
|
|
||||||
uint8_t out1[64] = {0}, hmac_res[80] = {0};
|
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
|
|
||||||
if ((uint8_t)salt_enc.len == 64 + poff) {
|
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
|
|
||||||
}
|
|
||||||
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
|
||||||
}
|
|
||||||
if (extensions.thirdPartyPayment != NULL) {
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "thirdPartyPayment"));
|
|
||||||
if (selcred->extensions.thirdPartyPayment == ptrue) {
|
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||||
flags |= FIDO2_AUT_FLAG_ED;
|
flags |= FIDO2_AUT_FLAG_ED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ctr = get_sign_counter();
|
uint32_t ctr = get_sign_counter();
|
||||||
@@ -539,7 +615,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
uint8_t *pa = aut_data;
|
uint8_t *pa = aut_data;
|
||||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||||
*pa++ = flags;
|
*pa++ = flags;
|
||||||
pa += put_uint32_t_be(ctr, pa);
|
pa += put_uint32_be(ctr, pa);
|
||||||
memcpy(pa, ext, ext_len); pa += ext_len;
|
memcpy(pa, ext, ext_len); pa += ext_len;
|
||||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||||
@@ -548,14 +624,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
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);
|
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||||
mbedtls_ecdsa_context ekey;
|
mbedtls_ecp_keypair ekey;
|
||||||
mbedtls_ecdsa_init(&ekey);
|
mbedtls_ecp_keypair_init(&ekey);
|
||||||
size_t olen = 0;
|
size_t olen = 0;
|
||||||
if (selcred) {
|
if (selcred) {
|
||||||
ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey);
|
ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
|
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
|
||||||
mbedtls_ecdsa_free(&ekey);
|
mbedtls_ecp_keypair_free(&ekey);
|
||||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,17 +641,20 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
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) {
|
||||||
md = NULL;
|
md = NULL;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (md != NULL) {
|
if (md != NULL) {
|
||||||
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
||||||
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
|
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_fill_iterator, NULL);
|
||||||
}
|
}
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
else {
|
else {
|
||||||
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL);
|
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_fill_iterator, NULL);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Bogus signature
|
// Bogus signature
|
||||||
@@ -591,7 +670,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) {
|
if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) {
|
||||||
lfields++;
|
lfields++;
|
||||||
}
|
}
|
||||||
if (numberOfCredentials > 1 && next == false) {
|
if (numberOfCredentials > 1 && next == false && (!resident || allowList_len <= 1)) {
|
||||||
lfields++;
|
lfields++;
|
||||||
}
|
}
|
||||||
if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||||
@@ -604,7 +683,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_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||||
if (selcred) {
|
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 {
|
else {
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
|
||||||
@@ -631,8 +717,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_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
|
||||||
selcred->userId.len));
|
|
||||||
if (numberOfCredentials > 1 && allowList_len == 0) {
|
if (numberOfCredentials > 1 && allowList_len == 0) {
|
||||||
if (selcred->userName.present == true) {
|
if (selcred->userName.present == true) {
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
||||||
@@ -645,7 +730,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
}
|
}
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||||
}
|
}
|
||||||
if (numberOfCredentials > 1 && next == false) {
|
if (numberOfCredentials > 1 && next == false && (!resident || allowList_len <= 1)) {
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
|
||||||
}
|
}
|
||||||
@@ -658,7 +743,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
|||||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||||
ctr++;
|
ctr++;
|
||||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
err:
|
err:
|
||||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
@@ -23,26 +24,40 @@
|
|||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
int cbor_get_info() {
|
int cbor_get_info(void) {
|
||||||
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
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 = file_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_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, "U2F_V2"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
|
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_1"));
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
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, "credBlob"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
|
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_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||||
|
|
||||||
@@ -50,11 +65,18 @@ int cbor_get_info() {
|
|||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 8));
|
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 9));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
|
||||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
|
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
|
||||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "alwaysUv"));
|
||||||
|
if (file_has_data(ef_pin) && (get_opts() & FIDO2_OPT_AUV || !getUserVerifiedFlagValue())) {
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
|
||||||
|
}
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
|
||||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "authnrCfg"));
|
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "authnrCfg"));
|
||||||
@@ -91,19 +113,38 @@ int cbor_get_info() {
|
|||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5));
|
uint8_t curves = 3;
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
|
curves++;
|
||||||
|
#endif
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
if (phy_data.enabled_curves & PHY_CURVE_SECP256K1) {
|
||||||
|
#endif
|
||||||
|
curves++;
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, curves));
|
||||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256, &arrayEncoder, &mapEncoder2));
|
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256, &arrayEncoder, &mapEncoder2));
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_EDDSA, &arrayEncoder, &mapEncoder2));
|
CBOR_CHECK(COSE_public_key(FIDO2_ALG_EDDSA, &arrayEncoder, &mapEncoder2));
|
||||||
|
#endif
|
||||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES384, &arrayEncoder, &mapEncoder2));
|
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES384, &arrayEncoder, &mapEncoder2));
|
||||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES512, &arrayEncoder, &mapEncoder2));
|
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES512, &arrayEncoder, &mapEncoder2));
|
||||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256K, &arrayEncoder, &mapEncoder2));
|
#ifndef ENABLE_EMULATION
|
||||||
|
if (phy_data.enabled_curves & PHY_CURVE_SECP256K1) {
|
||||||
|
#endif
|
||||||
|
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256K, &arrayEncoder, &mapEncoder2));
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_LARGE_BLOB_SIZE)); // maxSerializedLargeBlobArray
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_LARGE_BLOB_SIZE)); // maxSerializedLargeBlobArray
|
||||||
|
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
@@ -124,13 +165,36 @@ int cbor_get_info() {
|
|||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
|
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));
|
#endif
|
||||||
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));
|
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
err:
|
err:
|
||||||
if (error != CborNoError) {
|
if (error != CborNoError) {
|
||||||
|
|||||||
@@ -3,25 +3,25 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "mbedtls/sha256.h"
|
#include "mbedtls/sha256.h"
|
||||||
|
|
||||||
static uint64_t expectedLength = 0, expectedNextOffset = 0;
|
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 };
|
uint8_t verify_data[70] = { 0 };
|
||||||
memset(verify_data, 0xff, 32);
|
memset(verify_data, 0xff, 32);
|
||||||
verify_data[32] = 0x0C;
|
verify_data[32] = 0x0C;
|
||||||
put_uint32_t_le(offset, verify_data + 34);
|
put_uint32_le((uint32_t)offset, verify_data + 34);
|
||||||
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
|
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) {
|
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||||
@@ -152,7 +152,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
||||||
}
|
}
|
||||||
file_put_data(ef_largeblob, temp_lba, (uint16_t)expectedLength);
|
file_put_data(ef_largeblob, temp_lba, (uint16_t)expectedLength);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "cbor_make_credential.h"
|
#include "cbor_make_credential.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
@@ -25,7 +26,9 @@
|
|||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
#include "mbedtls/sha256.h"
|
#include "mbedtls/sha256.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "pico_keys.h"
|
#include "crypto_utils.h"
|
||||||
|
|
||||||
|
char *rp_id = NULL, *user_name = NULL, *display_name = NULL;
|
||||||
|
|
||||||
int cbor_make_credential(const uint8_t *data, size_t len) {
|
int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||||
CborParser parser;
|
CborParser parser;
|
||||||
@@ -39,7 +42,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||||
size_t excludeList_len = 0;
|
size_t excludeList_len = 0;
|
||||||
CredOptions options = { 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;
|
uint8_t *aut_data = NULL;
|
||||||
size_t resp_size = 0;
|
size_t resp_size = 0;
|
||||||
CredExtensions extensions = { 0 };
|
CredExtensions extensions = { 0 };
|
||||||
@@ -127,12 +134,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
CBOR_PARSE_MAP_START(_f1, 2)
|
CBOR_PARSE_MAP_START(_f1, 2)
|
||||||
{
|
{
|
||||||
CBOR_FIELD_GET_KEY_TEXT(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_BOOL(2, "hmac-secret", extensions.hmac_secret);
|
||||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
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_BOOL(2, "minPinLength", extensions.minPinLength);
|
||||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
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, "largeBlobKey", extensions.largeBlobKey);
|
||||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
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_ADVANCE(2);
|
||||||
}
|
}
|
||||||
CBOR_PARSE_MAP_END(_f1, 2);
|
CBOR_PARSE_MAP_END(_f1, 2);
|
||||||
@@ -160,6 +194,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
CBOR_PARSE_MAP_END(map, 1);
|
CBOR_PARSE_MAP_END(map, 1);
|
||||||
|
rp_id = rp.id.data;
|
||||||
|
user_name = user.parent.name.data;
|
||||||
|
display_name = user.displayName.data;
|
||||||
|
|
||||||
uint8_t flags = FIDO2_AUT_FLAG_AT;
|
uint8_t flags = FIDO2_AUT_FLAG_AT;
|
||||||
uint8_t rp_id_hash[32] = {0};
|
uint8_t rp_id_hash[32] = {0};
|
||||||
@@ -202,31 +239,57 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
|
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
|
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) {
|
if (curve <= 0) {
|
||||||
curve = FIDO2_CURVE_P256;
|
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) {
|
if (curve <= 0) {
|
||||||
curve = FIDO2_CURVE_P384;
|
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) {
|
if (curve <= 0) {
|
||||||
curve = FIDO2_CURVE_P521;
|
curve = FIDO2_CURVE_P521;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K) {
|
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
|
||||||
|
if (curve <= 0) {
|
||||||
|
curve = FIDO2_CURVE_BP256R1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
|
||||||
|
if (curve <= 0) {
|
||||||
|
curve = FIDO2_CURVE_BP384R1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
|
||||||
|
if (curve <= 0) {
|
||||||
|
curve = FIDO2_CURVE_BP512R1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
if (curve <= 0) {
|
if (curve <= 0) {
|
||||||
curve = FIDO2_CURVE_P256K1;
|
curve = FIDO2_CURVE_P256K1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
|
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
|
||||||
if (curve <= 0) {
|
if (curve <= 0) {
|
||||||
curve = FIDO2_CURVE_ED25519;
|
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) {
|
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
|
||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
@@ -292,12 +355,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Credential ecred = {0};
|
Credential ecred = {0};
|
||||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
|
if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
|
||||||
&ecred) == 0 &&
|
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
|
file_t *ef_cred = file_search((uint16_t)(EF_CRED + i));
|
||||||
(flags & FIDO2_AUT_FLAG_UV))) {
|
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);
|
credential_free(&ecred);
|
||||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
credential_free(&ecred);
|
credential_free(&ecred);
|
||||||
}
|
}
|
||||||
@@ -307,6 +384,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
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 (options.up == ptrue || options.up == NULL) { //14.1
|
||||||
if (pinUvAuthParam.present == true) {
|
if (pinUvAuthParam.present == true) {
|
||||||
if (getUserPresentFlagValue() == false) {
|
if (getUserPresentFlagValue() == false) {
|
||||||
@@ -326,11 +407,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);
|
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
|
||||||
|
|
||||||
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
|
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,
|
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));
|
||||||
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
|
|
||||||
cred_id, &cred_id_len));
|
|
||||||
|
|
||||||
if (getUserVerifiedFlagValue()) {
|
if (getUserVerifiedFlagValue()) {
|
||||||
flags |= FIDO2_AUT_FLAG_UV;
|
flags |= FIDO2_AUT_FLAG_UV;
|
||||||
@@ -342,14 +421,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
|
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
|
||||||
int l = 0;
|
int l = 0;
|
||||||
uint8_t minPinLen = 0;
|
uint8_t minPinLen = 0;
|
||||||
if (extensions.hmac_secret != NULL) {
|
if (extensions.hmac_secret == ptrue) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
if (extensions.credProtect != 0) {
|
if (extensions.credProtect != 0) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
if (extensions.minPinLength != NULL) {
|
if (extensions.minPinLength == ptrue) {
|
||||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
file_t *ef_minpin = file_search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_minpin)) {
|
if (file_has_data(ef_minpin)) {
|
||||||
uint8_t *minpin_data = file_get_data(ef_minpin);
|
uint8_t *minpin_data = file_get_data(ef_minpin);
|
||||||
for (int o = 2; o < file_get_size(ef_minpin); o += 32) {
|
for (int o = 2; o < file_get_size(ef_minpin); o += 32) {
|
||||||
@@ -366,29 +445,91 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
if (extensions.credBlob.present == true) {
|
if (extensions.credBlob.present == true) {
|
||||||
l++;
|
l++;
|
||||||
}
|
}
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
if (hmac_secret_mc) {
|
||||||
if (extensions.credBlob.present == true) {
|
l++;
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
|
|
||||||
}
|
}
|
||||||
if (extensions.credProtect != 0) {
|
if (pin_complexity_policy == ptrue) {
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
l++;
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
|
||||||
}
|
}
|
||||||
if (extensions.hmac_secret != NULL) {
|
if (l > 0) {
|
||||||
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||||
|
if (extensions.credBlob.present == true) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
|
||||||
|
}
|
||||||
|
if (extensions.credProtect != 0) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||||
|
}
|
||||||
|
if (extensions.hmac_secret == ptrue) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
|
}
|
||||||
|
if (minPinLen > 0) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||||
|
}
|
||||||
|
if (hmac_secret_mc) {
|
||||||
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
uint8_t sharedSecret[64] = {0};
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
|
mbedtls_ecp_point Qp;
|
||||||
}
|
mbedtls_ecp_point_init(&Qp);
|
||||||
if (minPinLen > 0) {
|
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 = file_search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
|
||||||
|
}
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||||
|
flags |= FIDO2_AUT_FLAG_ED;
|
||||||
}
|
}
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
|
||||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
|
||||||
flags |= FIDO2_AUT_FLAG_ED;
|
|
||||||
}
|
}
|
||||||
mbedtls_ecp_keypair ekey;
|
mbedtls_ecp_keypair ekey;
|
||||||
mbedtls_ecp_keypair_init(&ekey);
|
mbedtls_ecp_keypair_init(&ekey);
|
||||||
@@ -409,15 +550,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
|
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
|
||||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
|
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);
|
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
|
||||||
uint8_t *pa = aut_data;
|
uint8_t *pa = aut_data;
|
||||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||||
*pa++ = flags;
|
*pa++ = flags;
|
||||||
pa += put_uint32_t_be(ctr, pa);
|
pa += put_uint32_be(ctr, pa);
|
||||||
memcpy(pa, aaguid, 16); pa += 16;
|
memcpy(pa, aaguid, 16); pa += 16;
|
||||||
pa += put_uint16_t_be(cred_id_len, pa);
|
if (options.rk == ptrue) {
|
||||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||||
|
pa += put_uint16_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_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, cbor_buf, rs); pa += (uint16_t)rs;
|
||||||
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
|
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
|
||||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||||
@@ -428,15 +577,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
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);
|
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);
|
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);
|
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||||
}
|
}
|
||||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
|
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
|
||||||
md = NULL;
|
md = NULL;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (md != NULL) {
|
if (md != NULL) {
|
||||||
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
||||||
}
|
}
|
||||||
@@ -455,34 +606,18 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
self_attestation = false;
|
self_attestation = false;
|
||||||
}
|
}
|
||||||
if (md != NULL) {
|
if (md != NULL) {
|
||||||
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
|
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_fill_iterator, NULL);
|
||||||
}
|
}
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
else {
|
else {
|
||||||
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_gen, NULL);
|
ret = mbedtls_eddsa_write_signature(&ekey, aut_data, aut_data_len + clientDataHash.len, sig, sizeof(sig), &olen, MBEDTLS_EDDSA_PURE, NULL, 0, random_fill_iterator, NULL);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
mbedtls_ecp_keypair_free(&ekey);
|
mbedtls_ecp_keypair_free(&ekey);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.id.len > 0 && user.parent.name.len > 0 && user.displayName.len > 0) {
|
|
||||||
if (memcmp(user.parent.name.data, "+pico", 5) == 0) {
|
|
||||||
options.rk = pfalse;
|
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
|
|
||||||
if (memcmp(p, "CommissionProfile", 17) == 0) {
|
|
||||||
ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data);
|
|
||||||
if (ret == PICOKEY_OK) {
|
|
||||||
ret = phy_save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (ret != PICOKEY_OK) {
|
|
||||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t largeBlobKey[32] = {0};
|
uint8_t largeBlobKey[32] = {0};
|
||||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||||
ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey);
|
ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey);
|
||||||
@@ -492,7 +627,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, extensions.largeBlobKey == ptrue && options.rk == ptrue ? 5 : 4));
|
uint8_t lparams = 3;
|
||||||
|
if (enterpriseAttestation == 2) {
|
||||||
|
lparams++;
|
||||||
|
}
|
||||||
|
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||||
|
lparams++;
|
||||||
|
}
|
||||||
|
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lparams));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
|
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
|
||||||
@@ -500,16 +642,16 @@ 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_byte_string(&mapEncoder, aut_data, aut_data_len));
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
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_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_text_stringz(&mapEncoder2, "sig"));
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
||||||
if (self_attestation == false || is_nitrokey) {
|
if (self_attestation == false || is_nk) {
|
||||||
CborEncoder arrEncoder;
|
CborEncoder arrEncoder;
|
||||||
file_t *ef_cert = NULL;
|
file_t *ef_cert = NULL;
|
||||||
if (enterpriseAttestation == 2) {
|
if (enterpriseAttestation == 2) {
|
||||||
ef_cert = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
ef_cert = file_search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||||
}
|
}
|
||||||
if (!file_has_data(ef_cert)) {
|
if (!file_has_data(ef_cert)) {
|
||||||
ef_cert = ef_certdev;
|
ef_cert = ef_certdev;
|
||||||
@@ -521,8 +663,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||||
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
if (enterpriseAttestation == 2) {
|
||||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||||
|
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||||
|
}
|
||||||
|
|
||||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||||
@@ -539,7 +683,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
ctr++;
|
ctr++;
|
||||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
err:
|
err:
|
||||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||||
@@ -548,6 +692,10 @@ err:
|
|||||||
CBOR_FREE_BYTE_STRING(user.id);
|
CBOR_FREE_BYTE_STRING(user.id);
|
||||||
CBOR_FREE_BYTE_STRING(user.displayName);
|
CBOR_FREE_BYTE_STRING(user.displayName);
|
||||||
CBOR_FREE_BYTE_STRING(user.parent.name);
|
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) {
|
if (extensions.present == true) {
|
||||||
CBOR_FREE_BYTE_STRING(extensions.credBlob);
|
CBOR_FREE_BYTE_STRING(extensions.credBlob);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _CBOR_MAKE_CREDENTIAL_H_
|
#ifndef _CBOR_MAKE_CREDENTIAL_H_
|
||||||
|
|||||||
@@ -3,22 +3,24 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
|
#include "ctap2_cbor.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
@@ -26,20 +28,18 @@
|
|||||||
#endif
|
#endif
|
||||||
#include "fs/phy.h"
|
#include "fs/phy.h"
|
||||||
|
|
||||||
extern void scan_all();
|
int cbor_reset(void) {
|
||||||
|
|
||||||
int cbor_reset() {
|
|
||||||
#ifndef ENABLE_EMULATION
|
#ifndef ENABLE_EMULATION
|
||||||
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET == 1
|
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET == 1
|
||||||
if (!(phy_data.opts & PHY_OPT_DISABLE_POWER_RESET) && board_millis() > 10000) {
|
if (!(phy_data.opts & PHY_OPT_DISABLE_POWER_RESET) && board_millis() > 10000) {
|
||||||
return CTAP2_ERR_NOT_ALLOWED;
|
return CTAP2_ERR_NOT_ALLOWED;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (wait_button_pressed() == true) {
|
if (wait_button_pressed() > 0) {
|
||||||
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
initialize_flash(true);
|
file_initialize_flash(true);
|
||||||
init_fido();
|
init_fido();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,33 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
|
#include "ctap2_cbor.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
|
|
||||||
int cbor_selection() {
|
extern char *rp_id, *user_name, *display_name;
|
||||||
if (wait_button_pressed() == true) {
|
|
||||||
|
int cbor_selection(void) {
|
||||||
|
rp_id = user_name = display_name = NULL;
|
||||||
|
int ret = wait_button_pressed() ;
|
||||||
|
if (ret == 1) {
|
||||||
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
else if (ret == 2) {
|
||||||
|
return CTAP2_ERR_OPERATION_DENIED;
|
||||||
|
}
|
||||||
return CTAP2_OK;
|
return CTAP2_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,26 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
|
#include "serial.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "mbedtls/ecdh.h"
|
#include "mbedtls/ecdh.h"
|
||||||
#include "mbedtls/chachapoly.h"
|
#include "mbedtls/chachapoly.h"
|
||||||
@@ -42,7 +43,7 @@ int mse_decrypt_ct(uint8_t *data, size_t len) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||||
CborParser parser;
|
CborParser parser;
|
||||||
CborValue map;
|
CborValue map;
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
@@ -116,7 +117,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
file_put_data(ef_keydev_enc, vendorParam.data, (uint16_t)vendorParam.len);
|
file_put_data(ef_keydev_enc, vendorParam.data, (uint16_t)vendorParam.len);
|
||||||
file_put_data(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
|
file_put_data(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||||
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
goto err;
|
goto err;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -132,7 +133,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
mbedtls_ecdh_context hkey;
|
mbedtls_ecdh_context hkey;
|
||||||
mbedtls_ecdh_init(&hkey);
|
mbedtls_ecdh_init(&hkey);
|
||||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
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_fill_iterator, NULL);
|
||||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
@@ -154,7 +155,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
|
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_fill_iterator, NULL);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdh_free(&hkey);
|
mbedtls_ecdh_free(&hkey);
|
||||||
mbedtls_platform_zeroize(buf, sizeof(buf));
|
mbedtls_platform_zeroize(buf, sizeof(buf));
|
||||||
@@ -206,12 +207,17 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
mbedtls_ecdsa_context ekey;
|
mbedtls_ecdsa_context ekey;
|
||||||
mbedtls_ecdsa_init(&ekey);
|
mbedtls_ecdsa_init(&ekey);
|
||||||
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
uint8_t keydev[32] = {0};
|
||||||
|
if (load_keydev(keydev) != 0) {
|
||||||
|
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||||
|
}
|
||||||
|
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, keydev, 32);
|
||||||
|
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdsa_free(&ekey);
|
mbedtls_ecdsa_free(&ekey);
|
||||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
}
|
}
|
||||||
ret = mbedtls_ecp_mul(&ekey.grp, &ekey.Q, &ekey.d, &ekey.grp.G, random_gen, NULL);
|
ret = mbedtls_ecp_keypair_calc_public(&ekey, random_fill_iterator, NULL);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdsa_free(&ekey);
|
mbedtls_ecdsa_free(&ekey);
|
||||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||||
@@ -227,7 +233,7 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
mbedtls_x509write_csr_set_key(&ctx, &key);
|
mbedtls_x509write_csr_set_key(&ctx, &key);
|
||||||
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
|
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
|
||||||
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
|
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
|
||||||
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL);
|
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_fill_iterator, NULL);
|
||||||
mbedtls_ecdsa_free(&ekey);
|
mbedtls_ecdsa_free(&ekey);
|
||||||
if (ret <= 0) {
|
if (ret <= 0) {
|
||||||
mbedtls_x509write_csr_free(&ctx);
|
mbedtls_x509write_csr_free(&ctx);
|
||||||
@@ -237,52 +243,6 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
|||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
|
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);
|
|
||||||
}
|
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, opts));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else if (cmd == CTAP_VENDOR_MEMORY) {
|
|
||||||
if (vendorCmd == 0x01) {
|
|
||||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_free_space()));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_used_space()));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_total_space()));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_num_files()));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
|
||||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, flash_size()));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||||
|
|||||||
@@ -3,30 +3,30 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
|
|
||||||
int cmd_authenticate() {
|
int cmd_authenticate(void) {
|
||||||
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *) apdu.data;
|
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *) apdu.data;
|
||||||
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *) res_APDU;
|
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *) res_APDU;
|
||||||
//if (scan_files(true) != PICOKEY_OK)
|
//if (scan_files_fido(true) != PICOKEYS_OK)
|
||||||
// return SW_EXEC_ERROR();
|
// return SW_EXEC_ERROR();
|
||||||
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
|
if (apdu.nc < CTAP_CHAL_SIZE + CTAP_APPID_SIZE + 1 + 1) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
@@ -34,12 +34,12 @@ int cmd_authenticate() {
|
|||||||
if (req->keyHandleLen < KEY_HANDLE_LEN) {
|
if (req->keyHandleLen < KEY_HANDLE_LEN) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (P1(apdu) == CTAP_AUTH_ENFORCE && wait_button_pressed() == true) {
|
if (P1(apdu) == CTAP_AUTH_ENFORCE && wait_button_pressed() > 0) {
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_ecdsa_context key;
|
mbedtls_ecp_keypair key;
|
||||||
mbedtls_ecdsa_init(&key);
|
mbedtls_ecp_keypair_init(&key);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
|
uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen);
|
||||||
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
|
memcpy(tmp_kh, req->keyHandle, req->keyHandleLen);
|
||||||
@@ -49,24 +49,24 @@ int cmd_authenticate() {
|
|||||||
else {
|
else {
|
||||||
ret = derive_key(req->appId, false, req->keyHandle, MBEDTLS_ECP_DP_SECP256R1, &key);
|
ret = derive_key(req->appId, false, req->keyHandle, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||||
if (verify_key(req->appId, req->keyHandle, &key) != 0) {
|
if (verify_key(req->appId, req->keyHandle, &key) != 0) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
free(tmp_kh);
|
free(tmp_kh);
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
free(tmp_kh);
|
free(tmp_kh);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
if (P1(apdu) == CTAP_AUTH_CHECK_ONLY) {
|
if (P1(apdu) == CTAP_AUTH_CHECK_ONLY) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
resp->flags = 0;
|
resp->flags = 0;
|
||||||
resp->flags |= P1(apdu) == CTAP_AUTH_ENFORCE ? CTAP_AUTH_FLAG_TUP : 0x0;
|
resp->flags |= P1(apdu) == CTAP_AUTH_ENFORCE ? CTAP_AUTH_FLAG_TUP : 0x0;
|
||||||
uint32_t ctr = get_sign_counter();
|
uint32_t ctr = get_sign_counter();
|
||||||
put_uint32_t_be(ctr, resp->ctr);
|
put_uint32_be(ctr, resp->ctr);
|
||||||
uint8_t hash[32], sig_base[CTAP_APPID_SIZE + 1 + 4 + CTAP_CHAL_SIZE];
|
uint8_t hash[32], sig_base[CTAP_APPID_SIZE + 1 + 4 + CTAP_CHAL_SIZE];
|
||||||
memcpy(sig_base, req->appId, CTAP_APPID_SIZE);
|
memcpy(sig_base, req->appId, CTAP_APPID_SIZE);
|
||||||
memcpy(sig_base + CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
|
memcpy(sig_base + CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
|
||||||
@@ -74,12 +74,12 @@ int cmd_authenticate() {
|
|||||||
memcpy(sig_base + CTAP_APPID_SIZE + 1 + 4, req->chal, CTAP_CHAL_SIZE);
|
memcpy(sig_base + CTAP_APPID_SIZE + 1 + 4, req->chal, CTAP_CHAL_SIZE);
|
||||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
|
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
size_t olen = 0;
|
size_t olen = 0;
|
||||||
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->sig, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->sig, CTAP_MAX_EC_SIG_SIZE, &olen, random_fill_iterator, NULL);
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecp_keypair_free(&key);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
@@ -87,6 +87,6 @@ int cmd_authenticate() {
|
|||||||
|
|
||||||
ctr++;
|
ctr++;
|
||||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,20 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
@@ -29,55 +29,50 @@ const uint8_t u2f_aid[] = {
|
|||||||
0xA0, 0x00, 0x00, 0x05, 0x27, 0x10, 0x02
|
0xA0, 0x00, 0x00, 0x05, 0x27, 0x10, 0x02
|
||||||
};
|
};
|
||||||
|
|
||||||
int u2f_unload();
|
static int u2f_unload(void);
|
||||||
int u2f_process_apdu();
|
static int u2f_process_apdu(void);
|
||||||
|
|
||||||
int u2f_select(app_t *a, uint8_t force) {
|
static int u2f_select(app_t *a, uint8_t force) {
|
||||||
(void) force;
|
(void) force;
|
||||||
if (cap_supported(CAP_U2F)) {
|
if (cap_supported(CAP_U2F)) {
|
||||||
a->process_apdu = u2f_process_apdu;
|
a->process_apdu = u2f_process_apdu;
|
||||||
a->unload = u2f_unload;
|
a->unload = u2f_unload;
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
INITIALIZER ( u2f_ctor ) {
|
INITIALIZER ( u2f_ctor ) {
|
||||||
register_app(u2f_select, u2f_aid);
|
register_app(u2f_select, u2f_aid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int u2f_unload() {
|
int u2f_unload(void) {
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint8_t *bogus_firefox = (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
const uint8_t *bogus_firefox = (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||||
const uint8_t *bogus_chrome = (const uint8_t *) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
const uint8_t *bogus_chrome = (const uint8_t *) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||||
|
|
||||||
extern int ctap_error(uint8_t error);
|
int cmd_register(void) {
|
||||||
int cmd_register() {
|
|
||||||
CTAP_REGISTER_REQ *req = (CTAP_REGISTER_REQ *) apdu.data;
|
CTAP_REGISTER_REQ *req = (CTAP_REGISTER_REQ *) apdu.data;
|
||||||
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *) res_APDU;
|
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *) res_APDU;
|
||||||
resp->registerId = CTAP_REGISTER_ID;
|
resp->registerId = CTAP_REGISTER_ID;
|
||||||
resp->keyHandleLen = KEY_HANDLE_LEN;
|
resp->keyHandleLen = KEY_HANDLE_LEN;
|
||||||
//if (scan_files(true) != PICOKEY_OK)
|
//if (scan_files_fido(true) != PICOKEYS_OK)
|
||||||
// return SW_EXEC_ERROR();
|
// return SW_EXEC_ERROR();
|
||||||
if (apdu.nc != CTAP_APPID_SIZE + CTAP_CHAL_SIZE) {
|
if (apdu.nc != CTAP_APPID_SIZE + CTAP_CHAL_SIZE) {
|
||||||
return SW_WRONG_LENGTH();
|
return SW_WRONG_LENGTH();
|
||||||
}
|
}
|
||||||
if (wait_button_pressed() == true) {
|
if (wait_button_pressed() > 0) {
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
if (memcmp(req->appId, bogus_firefox,
|
if (memcmp(req->appId, bogus_firefox,
|
||||||
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
|
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
|
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
|
||||||
#else
|
|
||||||
{ return SW_DATA_INVALID(); }
|
|
||||||
#endif
|
|
||||||
mbedtls_ecdsa_context key;
|
mbedtls_ecdsa_context key;
|
||||||
mbedtls_ecdsa_init(&key);
|
mbedtls_ecdsa_init(&key);
|
||||||
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
|
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecdsa_free(&key);
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
@@ -102,16 +97,16 @@ int cmd_register() {
|
|||||||
mbedtls_ecdsa_init(&key);
|
mbedtls_ecdsa_init(&key);
|
||||||
uint8_t key_dev[32] = {0};
|
uint8_t key_dev[32] = {0};
|
||||||
ret = load_keydev(key_dev);
|
ret = load_keydev(key_dev);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, key_dev, 32);
|
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, key_dev, 32);
|
||||||
mbedtls_platform_zeroize(key_dev, sizeof(key_dev));
|
mbedtls_platform_zeroize(key_dev, sizeof(key_dev));
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecdsa_free(&key);
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
ret = mbedtls_ecdsa_write_signature(&key,MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
ret = mbedtls_ecdsa_write_signature(&key,MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_fill_iterator, NULL);
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecdsa_free(&key);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
@@ -120,10 +115,6 @@ int cmd_register() {
|
|||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern int cmd_register();
|
|
||||||
extern int cmd_authenticate();
|
|
||||||
extern int cmd_version();
|
|
||||||
|
|
||||||
static const cmd_t cmds[] = {
|
static const cmd_t cmds[] = {
|
||||||
{ CTAP_REGISTER, cmd_register },
|
{ CTAP_REGISTER, cmd_register },
|
||||||
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
||||||
@@ -131,7 +122,7 @@ static const cmd_t cmds[] = {
|
|||||||
{ 0x00, 0x0 }
|
{ 0x00, 0x0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
int u2f_process_apdu() {
|
int u2f_process_apdu(void) {
|
||||||
if (CLA(apdu) != 0x00) {
|
if (CLA(apdu) != 0x00) {
|
||||||
return SW_CLA_NOT_SUPPORTED();
|
return SW_CLA_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,22 +3,23 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "pico_keys.h"
|
#include "picokeys.h"
|
||||||
|
#include "fido.h"
|
||||||
|
|
||||||
int cmd_version() {
|
int cmd_version(void) {
|
||||||
memcpy(res_APDU, "U2F_V2", strlen("U2F_V2"));
|
memcpy(res_APDU, "U2F_V2", strlen("U2F_V2"));
|
||||||
res_APDU_size = (uint16_t)strlen("U2F_V2");
|
res_APDU_size = (uint16_t)strlen("U2F_V2");
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
|
|||||||
@@ -3,22 +3,25 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
|
#include "serial.h"
|
||||||
|
#include "pico_time.h"
|
||||||
#include "mbedtls/chachapoly.h"
|
#include "mbedtls/chachapoly.h"
|
||||||
#include "mbedtls/sha256.h"
|
#include "mbedtls/sha256.h"
|
||||||
#include "credential.h"
|
#include "credential.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
@@ -26,18 +29,24 @@
|
|||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "otp.h"
|
#include "otp.h"
|
||||||
|
|
||||||
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
|
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
|
||||||
|
|
||||||
static int credential_silent_tag(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
static int credential_silent_tag(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, uint8_t *outk) {
|
||||||
|
mbedtls_sha256_context ctx;
|
||||||
|
mbedtls_sha256_init(&ctx);
|
||||||
|
mbedtls_sha256_starts(&ctx, 0);
|
||||||
if (otp_key_1) {
|
if (otp_key_1) {
|
||||||
memcpy(outk, otp_key_1, 32);
|
mbedtls_sha256_update(&ctx, otp_key_1, 32);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
mbedtls_sha256(pico_serial.id, PICO_UNIQUE_BOARD_ID_SIZE_BYTES, outk, 0);
|
mbedtls_sha256_update(&ctx, pico_serial.id, sizeof(pico_serial.id));
|
||||||
}
|
}
|
||||||
|
mbedtls_sha256_update(&ctx, rp_id_hash, 32);
|
||||||
|
mbedtls_sha256_finish(&ctx, outk);
|
||||||
|
mbedtls_sha256_free(&ctx);
|
||||||
|
|
||||||
return mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, cred_id, cred_id_len - CRED_SILENT_TAG_LEN, outk);
|
return mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, cred_id, cred_id_len - CRED_SILENT_TAG_LEN, outk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +79,7 @@ int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
uint8_t outk[32];
|
uint8_t outk[32];
|
||||||
ret = credential_silent_tag(cred_id, cred_id_len, outk);
|
ret = credential_silent_tag(cred_id, cred_id_len, rp_id_hash, outk);
|
||||||
ret = memcmp(outk, cred_id + cred_id_len - CRED_SILENT_TAG_LEN, CRED_SILENT_TAG_LEN);
|
ret = memcmp(outk, cred_id + cred_id_len - CRED_SILENT_TAG_LEN, CRED_SILENT_TAG_LEN);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -86,7 +95,7 @@ int credential_create(CborCharString *rpId,
|
|||||||
int alg,
|
int alg,
|
||||||
int curve,
|
int curve,
|
||||||
uint8_t *cred_id,
|
uint8_t *cred_id,
|
||||||
size_t *cred_id_len) {
|
uint16_t *cred_id_len) {
|
||||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||||
CborError error = CborNoError;
|
CborError error = CborNoError;
|
||||||
uint8_t rp_id_hash[32];
|
uint8_t rp_id_hash[32];
|
||||||
@@ -141,13 +150,17 @@ int credential_create(CborCharString *rpId,
|
|||||||
}
|
}
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||||
}
|
}
|
||||||
|
if (has_set_rtc()) {
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||||
|
CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t) get_rtc_time()));
|
||||||
|
}
|
||||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
|
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};
|
uint8_t key[32] = {0};
|
||||||
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
|
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
|
||||||
uint8_t iv[CRED_IV_LEN] = {0};
|
uint8_t iv[CRED_IV_LEN] = {0};
|
||||||
random_gen(NULL, iv, sizeof(iv));
|
random_fill_buffer(iv, sizeof(iv));
|
||||||
mbedtls_chachapoly_context chatx;
|
mbedtls_chachapoly_context chatx;
|
||||||
mbedtls_chachapoly_init(&chatx);
|
mbedtls_chachapoly_init(&chatx);
|
||||||
mbedtls_chachapoly_setkey(&chatx, key);
|
mbedtls_chachapoly_setkey(&chatx, key);
|
||||||
@@ -161,7 +174,7 @@ int credential_create(CborCharString *rpId,
|
|||||||
}
|
}
|
||||||
memcpy(cred_id, CRED_PROTO, CRED_PROTO_LEN);
|
memcpy(cred_id, CRED_PROTO, CRED_PROTO_LEN);
|
||||||
memcpy(cred_id + CRED_PROTO_LEN, iv, CRED_IV_LEN);
|
memcpy(cred_id + CRED_PROTO_LEN, iv, CRED_IV_LEN);
|
||||||
credential_silent_tag(cred_id, *cred_id_len, cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN);
|
credential_silent_tag(cred_id, *cred_id_len, rp_id_hash, cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN);
|
||||||
|
|
||||||
err:
|
err:
|
||||||
if (error != CborNoError) {
|
if (error != CborNoError) {
|
||||||
@@ -213,7 +226,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
|||||||
CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1);
|
CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1);
|
||||||
}
|
}
|
||||||
else if (val_u == 0x06) {
|
else if (val_u == 0x06) {
|
||||||
CBOR_FIELD_GET_UINT(cred->creation, 1);
|
CBOR_FIELD_GET_UINT(cred->board_creation, 1);
|
||||||
}
|
}
|
||||||
else if (val_u == 0x07) {
|
else if (val_u == 0x07) {
|
||||||
cred->extensions.present = true;
|
cred->extensions.present = true;
|
||||||
@@ -248,6 +261,9 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
|||||||
}
|
}
|
||||||
CBOR_PARSE_MAP_END(_f1, 2);
|
CBOR_PARSE_MAP_END(_f1, 2);
|
||||||
}
|
}
|
||||||
|
else if (val_u == 0x0C) {
|
||||||
|
CBOR_FIELD_GET_UINT(cred->rtc_creation, 1);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
CBOR_ADVANCE(1);
|
CBOR_ADVANCE(1);
|
||||||
}
|
}
|
||||||
@@ -296,7 +312,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
file_t *ef = search_dynamic_file(EF_CRED + i);
|
file_t *ef = file_search(EF_CRED + i);
|
||||||
Credential rcred = { 0 };
|
Credential rcred = { 0 };
|
||||||
if (!file_has_data(ef)) {
|
if (!file_has_data(ef)) {
|
||||||
if (sloti == -1) {
|
if (sloti == -1) {
|
||||||
@@ -307,12 +323,12 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
|||||||
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||||
continue;
|
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) {
|
if (ret != 0) {
|
||||||
credential_free(&rcred);
|
credential_free(&rcred);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (memcmp(rcred.userId.data, cred.userId.data, MIN(rcred.userId.len, cred.userId.len)) == 0) {
|
if (rcred.userId.len == cred.userId.len && memcmp(rcred.userId.data, cred.userId.data, rcred.userId.len) == 0) {
|
||||||
sloti = i;
|
sloti = i;
|
||||||
credential_free(&rcred);
|
credential_free(&rcred);
|
||||||
new_record = false;
|
new_record = false;
|
||||||
@@ -323,17 +339,20 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
|||||||
if (sloti == -1) {
|
if (sloti == -1) {
|
||||||
return -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, 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_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);
|
free(data);
|
||||||
|
|
||||||
if (new_record == true) { //increase rps
|
if (new_record == true) { //increase rps
|
||||||
sloti = -1;
|
sloti = -1;
|
||||||
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||||
ef = search_dynamic_file(EF_RP + i);
|
ef = file_search(EF_RP + i);
|
||||||
if (!file_has_data(ef)) {
|
if (!file_has_data(ef)) {
|
||||||
if (sloti == -1) {
|
if (sloti == -1) {
|
||||||
sloti = i;
|
sloti = i;
|
||||||
@@ -348,7 +367,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
|||||||
if (sloti == -1) {
|
if (sloti == -1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
ef = search_dynamic_file((uint16_t)(EF_RP + sloti));
|
ef = file_search((uint16_t)(EF_RP + sloti));
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
data = (uint8_t *) calloc(1, file_get_size(ef));
|
data = (uint8_t *) calloc(1, file_get_size(ef));
|
||||||
memcpy(data, file_get_data(ef), file_get_size(ef));
|
memcpy(data, file_get_data(ef), file_get_size(ef));
|
||||||
@@ -367,7 +386,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
credential_free(&cred);
|
credential_free(&cred);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,3 +433,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);
|
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||||
return 0;
|
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.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _CREDENTIAL_H_
|
#ifndef _CREDENTIAL_H_
|
||||||
#define _CREDENTIAL_H_
|
#define _CREDENTIAL_H_
|
||||||
|
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
|
#include "file.h"
|
||||||
|
|
||||||
typedef struct CredOptions {
|
typedef struct CredOptions {
|
||||||
const bool *rk;
|
const bool *rk;
|
||||||
@@ -42,7 +43,7 @@ typedef struct Credential {
|
|||||||
CborByteString userId;
|
CborByteString userId;
|
||||||
CborCharString userName;
|
CborCharString userName;
|
||||||
CborCharString userDisplayName;
|
CborCharString userDisplayName;
|
||||||
uint64_t creation;
|
uint64_t board_creation;
|
||||||
CredExtensions extensions;
|
CredExtensions extensions;
|
||||||
const bool *use_sign_count;
|
const bool *use_sign_count;
|
||||||
int64_t alg;
|
int64_t alg;
|
||||||
@@ -50,6 +51,7 @@ typedef struct Credential {
|
|||||||
CborByteString id;
|
CborByteString id;
|
||||||
CredOptions opts;
|
CredOptions opts;
|
||||||
bool present;
|
bool present;
|
||||||
|
uint64_t rtc_creation;
|
||||||
} Credential;
|
} Credential;
|
||||||
|
|
||||||
#define CRED_PROT_UV_OPTIONAL 0x01
|
#define CRED_PROT_UV_OPTIONAL 0x01
|
||||||
@@ -58,6 +60,7 @@ typedef struct Credential {
|
|||||||
|
|
||||||
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
|
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
|
||||||
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
|
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
|
||||||
|
#define CRED_PROTO_23_S "\xf1\xd0\x02\x03"
|
||||||
|
|
||||||
#define CRED_PROTO CRED_PROTO_22_S
|
#define CRED_PROTO CRED_PROTO_22_S
|
||||||
|
|
||||||
@@ -66,6 +69,11 @@ typedef struct Credential {
|
|||||||
#define CRED_TAG_LEN 16
|
#define CRED_TAG_LEN 16
|
||||||
#define CRED_SILENT_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
|
typedef enum
|
||||||
{
|
{
|
||||||
CRED_PROTO_21 = 0x01,
|
CRED_PROTO_21 = 0x01,
|
||||||
@@ -83,7 +91,7 @@ extern int credential_create(CborCharString *rpId,
|
|||||||
int alg,
|
int alg,
|
||||||
int curve,
|
int curve,
|
||||||
uint8_t *cred_id,
|
uint8_t *cred_id,
|
||||||
size_t *cred_id_len);
|
uint16_t *cred_id_len);
|
||||||
extern void credential_free(Credential *cred);
|
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_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,
|
extern int credential_load(const uint8_t *cred_id,
|
||||||
@@ -94,5 +102,8 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len
|
|||||||
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
|
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
|
||||||
size_t cred_id_len,
|
size_t cred_id_len,
|
||||||
uint8_t *outk);
|
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_
|
#endif // _CREDENTIAL_H_
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _CTAP_H_
|
#ifndef _CTAP_H_
|
||||||
@@ -114,10 +114,14 @@ typedef struct {
|
|||||||
|
|
||||||
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
||||||
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
#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_VIDPID 0x6fcb19b0cbe3acfa
|
||||||
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
|
|
||||||
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
|
#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)
|
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
|
||||||
|
|
||||||
@@ -125,8 +129,7 @@ typedef struct {
|
|||||||
#define CTAP_VENDOR_MSE 0x02
|
#define CTAP_VENDOR_MSE 0x02
|
||||||
#define CTAP_VENDOR_UNLOCK 0x03
|
#define CTAP_VENDOR_UNLOCK 0x03
|
||||||
#define CTAP_VENDOR_EA 0x04
|
#define CTAP_VENDOR_EA 0x04
|
||||||
#define CTAP_VENDOR_PHY_OPTS 0x05
|
#define CTAP_VENDOR_ADMIN_PIN 0x08
|
||||||
#define CTAP_VENDOR_MEMORY 0x06
|
|
||||||
|
|
||||||
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
||||||
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
||||||
@@ -134,6 +137,7 @@ typedef struct {
|
|||||||
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
|
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
|
||||||
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
|
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
|
||||||
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
|
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
|
||||||
|
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
|
||||||
|
|
||||||
typedef struct mse {
|
typedef struct mse {
|
||||||
uint8_t Qpt[65];
|
uint8_t Qpt[65];
|
||||||
|
|||||||
@@ -3,32 +3,39 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _CTAP2_CBOR_H_
|
#ifndef _CTAP2_CBOR_H_
|
||||||
#define _CTAP2_CBOR_H_
|
#define _CTAP2_CBOR_H_
|
||||||
|
|
||||||
#include "cbor.h"
|
#include "cbor.h"
|
||||||
#ifndef ESP_PLATFORM
|
|
||||||
#include "common.h"
|
|
||||||
#else
|
|
||||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
|
||||||
#endif
|
|
||||||
#include "mbedtls/ecp.h"
|
#include "mbedtls/ecp.h"
|
||||||
#include "mbedtls/ecdh.h"
|
#include "mbedtls/ecdh.h"
|
||||||
|
|
||||||
extern uint8_t *driver_prepare_response();
|
extern uint8_t *driver_prepare_response(void);
|
||||||
extern void driver_exec_finished(size_t size_next);
|
extern void driver_exec_finished(size_t size_next);
|
||||||
|
extern int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_get_info(void);
|
||||||
|
extern int cbor_reset(void);
|
||||||
|
extern int cbor_make_credential(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_client_pin(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_selection(void);
|
||||||
|
extern int cbor_get_next_assertion(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_cred_mgmt(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_config(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_large_blobs(const uint8_t *data, size_t len);
|
||||||
|
extern int cbor_vendor(const uint8_t *data, size_t len);
|
||||||
|
extern void reset_gna_state(void);
|
||||||
extern int cbor_process(uint8_t, const uint8_t *data, size_t len);
|
extern int cbor_process(uint8_t, const uint8_t *data, size_t len);
|
||||||
extern const uint8_t aaguid[16];
|
extern const uint8_t aaguid[16];
|
||||||
|
|
||||||
|
|||||||
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 "picokeys.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;
|
||||||
323
src/fido/fido.c
323
src/fido/fido.c
@@ -3,21 +3,21 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "kek.h"
|
#include "serial.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "ctap.h"
|
#include "ctap.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
@@ -25,25 +25,24 @@
|
|||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "mbedtls/x509_crt.h"
|
#include "mbedtls/x509_crt.h"
|
||||||
#include "mbedtls/hkdf.h"
|
#include "mbedtls/hkdf.h"
|
||||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
#if defined(USB_ITF_CCID)
|
||||||
#include "ccid/ccid.h"
|
#include "ccid/ccid.h"
|
||||||
#endif
|
#endif
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "management.h"
|
#include "management.h"
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
|
#include "ctap2_cbor.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "crypto_utils.h"
|
#include "crypto_utils.h"
|
||||||
#include "otp.h"
|
#include "otp.h"
|
||||||
|
|
||||||
int fido_process_apdu();
|
static int fido_unload(void);
|
||||||
int fido_unload();
|
|
||||||
|
|
||||||
uint8_t PICO_PRODUCT = 2; // Pico FIDO
|
|
||||||
|
|
||||||
pinUvAuthToken_t paut = { 0 };
|
pinUvAuthToken_t paut = { 0 };
|
||||||
|
persistentPinUvAuthToken_t ppaut = { 0 };
|
||||||
|
|
||||||
uint8_t keydev_dec[32];
|
uint8_t keydev_dec[32];
|
||||||
bool has_keydev_dec = false;
|
bool has_keydev_dec = false;
|
||||||
@@ -54,31 +53,36 @@ const uint8_t fido_aid[] = {
|
|||||||
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
|
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const uint8_t fido_aid_backup[] = {
|
||||||
|
8,
|
||||||
|
0xB0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
|
||||||
|
};
|
||||||
|
|
||||||
const uint8_t atr_fido[] = {
|
const uint8_t atr_fido[] = {
|
||||||
23,
|
23,
|
||||||
0x3b, 0xfd, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe, 0x15, 0x80, 0x73, 0xc0, 0x21, 0xc0, 0x57, 0x59,
|
0x3b, 0xfd, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe, 0x15, 0x80, 0x73, 0xc0, 0x21, 0xc0, 0x57, 0x59,
|
||||||
0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x40
|
0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x40
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t fido_get_version_major() {
|
static uint8_t fido_get_version_major(void) {
|
||||||
return PICO_FIDO_VERSION_MAJOR;
|
return PICO_FIDO_VERSION_MAJOR;
|
||||||
}
|
}
|
||||||
uint8_t fido_get_version_minor() {
|
static uint8_t fido_get_version_minor(void) {
|
||||||
return PICO_FIDO_VERSION_MINOR;
|
return PICO_FIDO_VERSION_MINOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
int fido_select(app_t *a, uint8_t force) {
|
static int fido_select(app_t *a, uint8_t force) {
|
||||||
(void) force;
|
(void) force;
|
||||||
if (cap_supported(CAP_FIDO2)) {
|
if (cap_supported(CAP_FIDO2)) {
|
||||||
a->process_apdu = fido_process_apdu;
|
a->process_apdu = fido_process_apdu;
|
||||||
a->unload = fido_unload;
|
a->unload = fido_unload;
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern uint8_t (*get_version_major)();
|
extern uint8_t (*get_version_major)(void);
|
||||||
extern uint8_t (*get_version_minor)();
|
extern uint8_t (*get_version_minor)(void);
|
||||||
|
|
||||||
INITIALIZER ( fido_ctor ) {
|
INITIALIZER ( fido_ctor ) {
|
||||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
||||||
@@ -87,10 +91,11 @@ INITIALIZER ( fido_ctor ) {
|
|||||||
get_version_major = fido_get_version_major;
|
get_version_major = fido_get_version_major;
|
||||||
get_version_minor = fido_get_version_minor;
|
get_version_minor = fido_get_version_minor;
|
||||||
register_app(fido_select, fido_aid);
|
register_app(fido_select, fido_aid);
|
||||||
|
register_app(fido_select, fido_aid_backup);
|
||||||
}
|
}
|
||||||
|
|
||||||
int fido_unload() {
|
static int fido_unload(void) {
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
||||||
@@ -112,12 +117,23 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
|||||||
else if (curve == FIDO2_CURVE_X448) {
|
else if (curve == FIDO2_CURVE_X448) {
|
||||||
return MBEDTLS_ECP_DP_CURVE448;
|
return MBEDTLS_ECP_DP_CURVE448;
|
||||||
}
|
}
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
else if (curve == FIDO2_CURVE_ED25519) {
|
else if (curve == FIDO2_CURVE_ED25519) {
|
||||||
return MBEDTLS_ECP_DP_ED25519;
|
return MBEDTLS_ECP_DP_ED25519;
|
||||||
}
|
}
|
||||||
else if (curve == FIDO2_CURVE_ED448) {
|
else if (curve == FIDO2_CURVE_ED448) {
|
||||||
return MBEDTLS_ECP_DP_ED448;
|
return MBEDTLS_ECP_DP_ED448;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
else if (curve == FIDO2_CURVE_BP256R1) {
|
||||||
|
return MBEDTLS_ECP_DP_BP256R1;
|
||||||
|
}
|
||||||
|
else if (curve == FIDO2_CURVE_BP384R1) {
|
||||||
|
return MBEDTLS_ECP_DP_BP384R1;
|
||||||
|
}
|
||||||
|
else if (curve == FIDO2_CURVE_BP512R1) {
|
||||||
|
return MBEDTLS_ECP_DP_BP512R1;
|
||||||
|
}
|
||||||
return MBEDTLS_ECP_DP_NONE;
|
return MBEDTLS_ECP_DP_NONE;
|
||||||
}
|
}
|
||||||
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
||||||
@@ -134,17 +150,19 @@ int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
|||||||
return FIDO2_CURVE_P256K1;
|
return FIDO2_CURVE_P256K1;
|
||||||
}
|
}
|
||||||
else if (id == MBEDTLS_ECP_DP_CURVE25519) {
|
else if (id == MBEDTLS_ECP_DP_CURVE25519) {
|
||||||
return MBEDTLS_ECP_DP_CURVE25519;
|
return FIDO2_CURVE_X25519;
|
||||||
}
|
}
|
||||||
else if (id == MBEDTLS_ECP_DP_CURVE448) {
|
else if (id == MBEDTLS_ECP_DP_CURVE448) {
|
||||||
return FIDO2_CURVE_X448;
|
return FIDO2_CURVE_X448;
|
||||||
}
|
}
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
else if (id == MBEDTLS_ECP_DP_ED25519) {
|
else if (id == MBEDTLS_ECP_DP_ED25519) {
|
||||||
return FIDO2_CURVE_ED25519;
|
return FIDO2_CURVE_ED25519;
|
||||||
}
|
}
|
||||||
else if (id == MBEDTLS_ECP_DP_ED448) {
|
else if (id == MBEDTLS_ECP_DP_ED448) {
|
||||||
return FIDO2_CURVE_ED448;
|
return FIDO2_CURVE_ED448;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,14 +173,18 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
|
|||||||
}
|
}
|
||||||
uint8_t key_path[KEY_PATH_LEN];
|
uint8_t key_path[KEY_PATH_LEN];
|
||||||
memcpy(key_path, cred_id, KEY_PATH_LEN);
|
memcpy(key_path, cred_id, KEY_PATH_LEN);
|
||||||
*(uint32_t *) key_path = 0x80000000 | 10022;
|
uint32_t key_path_first = 0x80000000u | 10022u;
|
||||||
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
|
memcpy(key_path, &key_path_first, sizeof(key_path_first));
|
||||||
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
|
for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||||
|
uint32_t part = 0;
|
||||||
|
memcpy(&part, key_path + i * sizeof(uint32_t), sizeof(part));
|
||||||
|
part |= 0x80000000u;
|
||||||
|
memcpy(key_path + i * sizeof(uint32_t), &part, sizeof(part));
|
||||||
}
|
}
|
||||||
return derive_key(NULL, false, key_path, mbedtls_curve, key);
|
return derive_key(NULL, false, key_path, mbedtls_curve, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size) {
|
static int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size) {
|
||||||
mbedtls_x509write_cert ctx;
|
mbedtls_x509write_cert ctx;
|
||||||
mbedtls_x509write_crt_init(&ctx);
|
mbedtls_x509write_crt_init(&ctx);
|
||||||
mbedtls_x509write_crt_set_version(&ctx, MBEDTLS_X509_CRT_VERSION_3);
|
mbedtls_x509write_crt_set_version(&ctx, MBEDTLS_X509_CRT_VERSION_3);
|
||||||
@@ -170,7 +192,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
|||||||
mbedtls_x509write_crt_set_issuer_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
mbedtls_x509write_crt_set_issuer_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
||||||
mbedtls_x509write_crt_set_subject_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
mbedtls_x509write_crt_set_subject_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
||||||
uint8_t serial[16];
|
uint8_t serial[16];
|
||||||
random_gen(NULL, serial, sizeof(serial));
|
random_fill_buffer(serial, sizeof(serial));
|
||||||
mbedtls_x509write_crt_set_serial_raw(&ctx, serial, sizeof(serial));
|
mbedtls_x509write_crt_set_serial_raw(&ctx, serial, sizeof(serial));
|
||||||
mbedtls_pk_context key;
|
mbedtls_pk_context key;
|
||||||
mbedtls_pk_init(&key);
|
mbedtls_pk_init(&key);
|
||||||
@@ -185,38 +207,73 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
|||||||
mbedtls_x509write_crt_set_key_usage(&ctx,
|
mbedtls_x509write_crt_set_key_usage(&ctx,
|
||||||
MBEDTLS_X509_KU_DIGITAL_SIGNATURE |
|
MBEDTLS_X509_KU_DIGITAL_SIGNATURE |
|
||||||
MBEDTLS_X509_KU_KEY_CERT_SIGN);
|
MBEDTLS_X509_KU_KEY_CERT_SIGN);
|
||||||
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, random_gen, NULL);
|
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, random_fill_iterator, NULL);
|
||||||
mbedtls_x509write_crt_free(&ctx);
|
mbedtls_x509write_crt_free(&ctx);
|
||||||
/* pk cannot be freed, as it is freed later */
|
/* pk cannot be freed, as it is freed later */
|
||||||
//mbedtls_pk_free(&key);
|
//mbedtls_pk_free(&key);
|
||||||
return ret;
|
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)) {
|
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
|
||||||
return PICOKEY_ERR_MEMORY_FATAL;
|
return PICOKEYS_ERR_MEMORY_FATAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_keydev_dec == true) {
|
if (has_keydev_dec == true) {
|
||||||
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
uint16_t fid_size = file_get_size(ef_keydev);
|
||||||
|
if (fid_size == 32) {
|
||||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
memcpy(key, file_get_data(ef_keydev), 32);
|
||||||
return PICOKEY_EXEC_ERROR;
|
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICOKEYS_AES_MODE_CBC, key, 32) != PICOKEYS_OK) {
|
||||||
|
return PICOKEYS_EXEC_ERROR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
else if (fid_size == 33 || fid_size == 61) {
|
||||||
return PICOKEY_EXEC_ERROR;
|
uint8_t format = *file_get_data(ef_keydev);
|
||||||
|
if (format == 0x01 || format == 0x02 || format == 0x03) { // Format indicator
|
||||||
|
if (format == 0x02 || format == 0x03) {
|
||||||
|
uint8_t tmp_key[61], version = format == 0x03 ? 2 : 1;
|
||||||
|
memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
|
||||||
|
int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, version, key);
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
|
return PICOKEYS_EXEC_ERROR;
|
||||||
|
}
|
||||||
|
if (format == 0x02) {
|
||||||
|
tmp_key[0] = 0x03;
|
||||||
|
ret = encrypt_with_aad(session_pin, key, 32, 2, tmp_key + 1);
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
|
mbedtls_platform_zeroize(tmp_key, sizeof(tmp_key));
|
||||||
|
return PICOKEYS_EXEC_ERROR;
|
||||||
|
}
|
||||||
|
file_put_data(ef_keydev, tmp_key, sizeof(tmp_key));
|
||||||
|
flash_commit();
|
||||||
|
}
|
||||||
|
mbedtls_platform_zeroize(tmp_key, sizeof(tmp_key));
|
||||||
|
}
|
||||||
|
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, PICOKEYS_AES_MODE_CBC, key, 32);
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
|
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||||
|
return PICOKEYS_EXEC_ERROR;
|
||||||
|
}
|
||||||
|
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *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)];
|
uint32_t k = 0;
|
||||||
|
memcpy(&k, &keyHandle[i * sizeof(uint32_t)], sizeof(k));
|
||||||
if (!(k & 0x80000000)) {
|
if (!(k & 0x80000000)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -251,15 +308,15 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
|||||||
uint8_t outk[67] = { 0 }; //SECP521R1 key is 66 bytes length
|
uint8_t outk[67] = { 0 }; //SECP521R1 key is 66 bytes length
|
||||||
int r = 0;
|
int r = 0;
|
||||||
memset(outk, 0, sizeof(outk));
|
memset(outk, 0, sizeof(outk));
|
||||||
if ((r = load_keydev(outk)) != PICOKEY_OK) {
|
if ((r = load_keydev(outk)) != PICOKEYS_OK) {
|
||||||
printf("Error loading keydev: %d\n", r);
|
printf("Error loading keydev: %d\n", r);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
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) {
|
if (new_key == true) {
|
||||||
uint32_t val = 0;
|
uint32_t val = 0;
|
||||||
random_gen(NULL, (uint8_t *) &val, sizeof(val));
|
random_fill_buffer((uint8_t *) &val, sizeof(val));
|
||||||
val |= 0x80000000;
|
val |= 0x80000000;
|
||||||
memcpy(&key_handle[i * sizeof(uint32_t)], &val, sizeof(uint32_t));
|
memcpy(&key_handle[i * sizeof(uint32_t)], &val, sizeof(uint32_t));
|
||||||
}
|
}
|
||||||
@@ -287,48 +344,59 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
|||||||
if (cinfo->bit_size % 8 != 0) {
|
if (cinfo->bit_size % 8 != 0) {
|
||||||
outk[0] >>= 8 - (cinfo->bit_size % 8);
|
outk[0] >>= 8 - (cinfo->bit_size % 8);
|
||||||
}
|
}
|
||||||
r = mbedtls_ecp_read_key(curve, key, outk, (size_t)ceil((float) cinfo->bit_size / 8));
|
r = mbedtls_ecp_read_key(curve, key, outk, (size_t)((cinfo->bit_size + 7) / 8));
|
||||||
mbedtls_platform_zeroize(outk, sizeof(outk));
|
mbedtls_platform_zeroize(outk, sizeof(outk));
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
if (curve == MBEDTLS_ECP_DP_ED25519) {
|
return mbedtls_ecp_keypair_calc_public(key, random_fill_iterator, NULL);
|
||||||
return mbedtls_ecp_point_edwards(&key->grp, &key->Q, &key->d, random_gen, NULL);
|
|
||||||
}
|
|
||||||
return mbedtls_ecp_mul(&key->grp, &key->Q, &key->d, &key->grp.G, random_gen, NULL);
|
|
||||||
}
|
}
|
||||||
mbedtls_platform_zeroize(outk, sizeof(outk));
|
mbedtls_platform_zeroize(outk, sizeof(outk));
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
int scan_files() {
|
int encrypt_keydev_f1(const uint8_t keydev[32]) {
|
||||||
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
uint8_t kdata[33] = {0};
|
||||||
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
kdata[0] = 0x01; // Format indicator
|
||||||
ef_mkek = search_by_fid(EF_MKEK, NULL, SPECIFY_EF);
|
memcpy(kdata + 1, keydev, 32);
|
||||||
|
uint8_t kbase[32];
|
||||||
|
derive_kbase(kbase);
|
||||||
|
int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICOKEYS_AES_MODE_CBC, kdata + 1, 32);
|
||||||
|
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||||
|
if (ret != PICOKEYS_OK) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = file_put_data(ef_keydev, kdata, 33);
|
||||||
|
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||||
|
flash_commit();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int scan_files_fido(void) {
|
||||||
|
ef_keydev = file_search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
||||||
|
ef_keydev_enc = file_search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
||||||
if (ef_keydev) {
|
if (ef_keydev) {
|
||||||
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
|
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
|
||||||
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
|
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
|
||||||
mbedtls_ecdsa_context ecdsa;
|
mbedtls_ecdsa_context ecdsa;
|
||||||
mbedtls_ecdsa_init(&ecdsa);
|
mbedtls_ecdsa_init(&ecdsa);
|
||||||
uint8_t index = 0;
|
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, random_fill_iterator, NULL);
|
||||||
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, random_gen, &index);
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdsa_free(&ecdsa);
|
mbedtls_ecdsa_free(&ecdsa);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
uint8_t kdata[64];
|
uint8_t keydev[32] = {0};
|
||||||
size_t key_size = 0;
|
size_t key_size = 0;
|
||||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
|
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != 0 || key_size != 32) {
|
||||||
return ret;
|
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||||
|
mbedtls_ecdsa_free(&ecdsa);
|
||||||
|
return ret != 0 ? ret : PICOKEYS_EXEC_ERROR;
|
||||||
}
|
}
|
||||||
if (otp_key_1) {
|
encrypt_keydev_f1(keydev);
|
||||||
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
|
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||||
}
|
|
||||||
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
|
|
||||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
|
||||||
mbedtls_ecdsa_free(&ecdsa);
|
mbedtls_ecdsa_free(&ecdsa);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
printf(" done!\n");
|
printf(" done!\n");
|
||||||
@@ -337,22 +405,7 @@ int scan_files() {
|
|||||||
else {
|
else {
|
||||||
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
|
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
|
||||||
}
|
}
|
||||||
if (ef_mkek) { // No encrypted MKEK
|
ef_certdev = file_search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
|
||||||
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 (ef_certdev) {
|
||||||
if (!file_has_data(ef_certdev)) {
|
if (!file_has_data(ef_certdev)) {
|
||||||
uint8_t cert[2048], outk[32];
|
uint8_t cert[2048], outk[32];
|
||||||
@@ -368,7 +421,7 @@ int scan_files() {
|
|||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecdsa_free(&key);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
ret = mbedtls_ecp_mul(&key.grp, &key.Q, &key.d, &key.grp.G, random_gen, NULL);
|
ret = mbedtls_ecp_keypair_calc_public(&key, random_fill_iterator, NULL);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
mbedtls_ecdsa_free(&key);
|
mbedtls_ecdsa_free(&key);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -384,7 +437,7 @@ int scan_files() {
|
|||||||
else {
|
else {
|
||||||
printf("FATAL ERROR: CERT DEV not found in memory!\r\n");
|
printf("FATAL ERROR: CERT DEV not found in memory!\r\n");
|
||||||
}
|
}
|
||||||
ef_counter = search_by_fid(EF_COUNTER, NULL, SPECIFY_EF);
|
ef_counter = file_search_by_fid(EF_COUNTER, NULL, SPECIFY_EF);
|
||||||
if (ef_counter) {
|
if (ef_counter) {
|
||||||
if (!file_has_data(ef_counter)) {
|
if (!file_has_data(ef_counter)) {
|
||||||
uint32_t v = 0;
|
uint32_t v = 0;
|
||||||
@@ -394,19 +447,13 @@ int scan_files() {
|
|||||||
else {
|
else {
|
||||||
printf("FATAL ERROR: Global counter not found in memory!\r\n");
|
printf("FATAL ERROR: Global counter not found in memory!\r\n");
|
||||||
}
|
}
|
||||||
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
|
ef_pin = file_search_by_fid(EF_PIN, NULL, SPECIFY_EF);
|
||||||
if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
|
ef_pin_admin = file_search_by_fid(EF_PIN_ADMIN, NULL, SPECIFY_EF);
|
||||||
uint8_t pin_data[34] = { 0 }, dhash[32];
|
ef_authtoken = file_search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
|
||||||
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 (ef_authtoken) {
|
||||||
if (!file_has_data(ef_authtoken)) {
|
if (!file_has_data(ef_authtoken)) {
|
||||||
uint8_t t[32];
|
uint8_t t[32];
|
||||||
random_gen(NULL, t, sizeof(t));
|
random_fill_buffer(t, sizeof(t));
|
||||||
file_put_data(ef_authtoken, t, sizeof(t));
|
file_put_data(ef_authtoken, t, sizeof(t));
|
||||||
}
|
}
|
||||||
paut.data = file_get_data(ef_authtoken);
|
paut.data = file_get_data(ef_authtoken);
|
||||||
@@ -415,42 +462,64 @@ int scan_files() {
|
|||||||
else {
|
else {
|
||||||
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
||||||
}
|
}
|
||||||
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
file_t *ef_pauthtoken = file_search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
|
||||||
|
if (ef_pauthtoken) {
|
||||||
|
if (!file_has_data(ef_pauthtoken)) {
|
||||||
|
uint8_t t[32];
|
||||||
|
random_fill_buffer(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 = file_search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
||||||
if (!file_has_data(ef_largeblob)) {
|
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);
|
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
|
||||||
}
|
}
|
||||||
|
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
void scan_all() {
|
void scan_all(void) {
|
||||||
scan_flash();
|
file_scan_flash();
|
||||||
scan_files();
|
scan_files_fido();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void init_otp();
|
extern bool needs_power_cycle;
|
||||||
void init_fido() {
|
void init_fido(void) {
|
||||||
scan_all();
|
scan_all();
|
||||||
|
#ifdef ENABLE_OTP_APP
|
||||||
init_otp();
|
init_otp();
|
||||||
|
#endif
|
||||||
|
needs_power_cycle = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wait_button_pressed() {
|
int wait_button_pressed(void) {
|
||||||
uint32_t val = EV_PRESS_BUTTON;
|
uint32_t val = EV_PRESS_BUTTON;
|
||||||
#ifndef ENABLE_EMULATION
|
#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
|
||||||
queue_try_add(&card_to_usb_q, &val);
|
queue_try_add(&card_to_usb_q, &val);
|
||||||
do {
|
do {
|
||||||
queue_remove_blocking(&usb_to_card_q, &val);
|
queue_remove_blocking(&usb_to_card_q, &val);
|
||||||
} while (val != EV_BUTTON_PRESSED && val != EV_BUTTON_TIMEOUT);
|
} while (val != EV_BUTTON_PRESSED && val != EV_BUTTON_TIMEOUT && val != EV_BUTTON_CANCELLED);
|
||||||
#endif
|
#endif
|
||||||
return val == EV_BUTTON_TIMEOUT;
|
if (val == EV_BUTTON_TIMEOUT) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (val == EV_BUTTON_CANCELLED) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t user_present_time_limit = 0;
|
uint32_t user_present_time_limit = 0;
|
||||||
|
|
||||||
bool check_user_presence() {
|
bool check_user_presence(void) {
|
||||||
if (user_present_time_limit == 0 || user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
|
if (user_present_time_limit == 0 || user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||||
if (wait_button_pressed() == true) { //timeout
|
if (wait_button_pressed() > 0) { //timeout
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//user_present_time_limit = board_millis();
|
//user_present_time_limit = board_millis();
|
||||||
@@ -458,13 +527,13 @@ bool check_user_presence() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t get_sign_counter() {
|
uint32_t get_sign_counter(void) {
|
||||||
uint8_t *caddr = file_get_data(ef_counter);
|
uint8_t *caddr = file_get_data(ef_counter);
|
||||||
return get_uint32_t_le(caddr);
|
return get_uint32_le(caddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_opts() {
|
uint8_t get_opts(void) {
|
||||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
file_t *ef = file_search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
return *file_get_data(ef);
|
return *file_get_data(ef);
|
||||||
}
|
}
|
||||||
@@ -472,25 +541,34 @@ uint8_t get_opts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_opts(uint8_t opts) {
|
void set_opts(uint8_t opts) {
|
||||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
file_t *ef = file_search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||||
file_put_data(ef, &opts, sizeof(uint8_t));
|
file_put_data(ef, &opts, sizeof(uint8_t));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern int cmd_register();
|
|
||||||
extern int cmd_authenticate();
|
|
||||||
extern int cmd_version();
|
|
||||||
extern int cbor_parse(int, uint8_t *, size_t);
|
|
||||||
|
|
||||||
#define CTAP_CBOR 0x10
|
#define CTAP_CBOR 0x10
|
||||||
|
|
||||||
int cmd_cbor() {
|
static int cmd_vendor(void) {
|
||||||
uint8_t *old_buf = res_APDU;
|
uint8_t *old_buf = res_APDU;
|
||||||
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
|
driver_init_hid();
|
||||||
if (ret != 0) {
|
int ret = cbor_vendor(apdu.data, apdu.nc);
|
||||||
return SW_EXEC_ERROR();
|
|
||||||
}
|
|
||||||
res_APDU = old_buf;
|
res_APDU = old_buf;
|
||||||
|
if (ret != 0) {
|
||||||
|
return set_res_sw(0x64, ret);
|
||||||
|
}
|
||||||
|
res_APDU_size += 1;
|
||||||
|
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
|
||||||
|
return SW_OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_cbor(void) {
|
||||||
|
uint8_t *old_buf = res_APDU;
|
||||||
|
driver_init_hid();
|
||||||
|
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
|
||||||
|
res_APDU = old_buf;
|
||||||
|
if (ret != 0) {
|
||||||
|
return set_res_sw(0x64, ret);
|
||||||
|
}
|
||||||
res_APDU_size += 1;
|
res_APDU_size += 1;
|
||||||
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
|
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
@@ -501,10 +579,11 @@ static const cmd_t cmds[] = {
|
|||||||
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
||||||
{ CTAP_VERSION, cmd_version },
|
{ CTAP_VERSION, cmd_version },
|
||||||
{ CTAP_CBOR, cmd_cbor },
|
{ CTAP_CBOR, cmd_cbor },
|
||||||
|
{ 0x41, cmd_vendor },
|
||||||
{ 0x00, 0x0 }
|
{ 0x00, 0x0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
int fido_process_apdu() {
|
int fido_process_apdu(void) {
|
||||||
if (CLA(apdu) != 0x00 && CLA(apdu) != 0x80) {
|
if (CLA(apdu) != 0x00 && CLA(apdu) != 0x80) {
|
||||||
return SW_CLA_NOT_SUPPORTED();
|
return SW_CLA_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,37 +3,30 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _FIDO_H_
|
#ifndef _FIDO_H_
|
||||||
#define _FIDO_H_
|
#define _FIDO_H_
|
||||||
|
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#endif
|
#endif
|
||||||
#ifndef ESP_PLATFORM
|
|
||||||
#include "common.h"
|
|
||||||
#else
|
|
||||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "mbedtls/ecdsa.h"
|
#include "mbedtls/ecdsa.h"
|
||||||
|
#ifdef MBEDTLS_EDDSA_C
|
||||||
#include "mbedtls/eddsa.h"
|
#include "mbedtls/eddsa.h"
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
#include "hid/ctap_hid.h"
|
|
||||||
#else
|
|
||||||
#include <stdbool.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
#include "hid/ctap_hid.h"
|
||||||
|
|
||||||
#define CTAP_PUBKEY_LEN (65)
|
#define CTAP_PUBKEY_LEN (65)
|
||||||
#define KEY_PATH_LEN (32)
|
#define KEY_PATH_LEN (32)
|
||||||
@@ -41,32 +34,42 @@
|
|||||||
#define SHA256_DIGEST_LENGTH (32)
|
#define SHA256_DIGEST_LENGTH (32)
|
||||||
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
|
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
|
||||||
|
|
||||||
extern int scan_files();
|
extern int scan_files_fido(void);
|
||||||
extern int derive_key(const uint8_t *app_id,
|
extern int derive_key(const uint8_t *app_id,
|
||||||
bool new_key,
|
bool new_key,
|
||||||
uint8_t *key_handle,
|
uint8_t *key_handle,
|
||||||
int,
|
int,
|
||||||
mbedtls_ecp_keypair *key);
|
mbedtls_ecp_keypair *key);
|
||||||
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *);
|
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *);
|
||||||
extern bool wait_button_pressed();
|
extern int wait_button_pressed(void);
|
||||||
extern void init_fido();
|
extern void init_fido(void);
|
||||||
|
extern void init_otp(void);
|
||||||
|
extern void scan_all(void);
|
||||||
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
|
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
|
||||||
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
|
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 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 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 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);
|
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_EDDSA -8 //EdDSA
|
||||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
|
#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
|
||||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
|
#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_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
|
||||||
#define FIDO2_ALG_ES256K -47
|
#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_RS256 -257
|
||||||
#define FIDO2_ALG_RS384 -258
|
#define FIDO2_ALG_RS384 -258
|
||||||
#define FIDO2_ALG_RS512 -259
|
#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_P256 1
|
||||||
#define FIDO2_CURVE_P384 2
|
#define FIDO2_CURVE_P384 2
|
||||||
@@ -76,6 +79,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
|||||||
#define FIDO2_CURVE_ED25519 6
|
#define FIDO2_CURVE_ED25519 6
|
||||||
#define FIDO2_CURVE_ED448 7
|
#define FIDO2_CURVE_ED448 7
|
||||||
#define FIDO2_CURVE_P256K1 8
|
#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_UP 0x1
|
||||||
#define FIDO2_AUT_FLAG_UV 0x4
|
#define FIDO2_AUT_FLAG_UV 0x4
|
||||||
@@ -83,16 +89,17 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
|||||||
#define FIDO2_AUT_FLAG_ED 0x80
|
#define FIDO2_AUT_FLAG_ED 0x80
|
||||||
|
|
||||||
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
|
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
|
||||||
|
#define FIDO2_OPT_AUV 0x02 // User Verification
|
||||||
|
|
||||||
#define MAX_PIN_RETRIES 8
|
#define MAX_PIN_RETRIES 8
|
||||||
extern bool getUserPresentFlagValue();
|
extern bool getUserPresentFlagValue(void);
|
||||||
extern bool getUserVerifiedFlagValue();
|
extern bool getUserVerifiedFlagValue(void);
|
||||||
extern void clearUserPresentFlag();
|
extern void clearUserPresentFlag(void);
|
||||||
extern void clearUserVerifiedFlag();
|
extern void clearUserVerifiedFlag(void);
|
||||||
extern void clearPinUvAuthTokenPermissionsExceptLbw();
|
extern void clearPinUvAuthTokenPermissionsExceptLbw(void);
|
||||||
extern void send_keepalive();
|
extern void send_keepalive(void);
|
||||||
extern uint32_t get_sign_counter();
|
extern uint32_t get_sign_counter(void);
|
||||||
extern uint8_t get_opts();
|
extern uint8_t get_opts(void);
|
||||||
extern void set_opts(uint8_t);
|
extern void set_opts(uint8_t);
|
||||||
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
|
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
|
||||||
#define MAX_CRED_ID_LENGTH 1024
|
#define MAX_CRED_ID_LENGTH 1024
|
||||||
@@ -113,7 +120,19 @@ extern const known_app_t *find_app_by_rp_id_hash(const uint8_t *rp_id_hash);
|
|||||||
|
|
||||||
#define TRANSPORT_TIME_LIMIT (30 * 1000) //USB
|
#define TRANSPORT_TIME_LIMIT (30 * 1000) //USB
|
||||||
|
|
||||||
bool check_user_presence();
|
bool check_user_presence(void);
|
||||||
|
int fido_process_apdu(void);
|
||||||
|
int cmd_register(void);
|
||||||
|
int cmd_authenticate(void);
|
||||||
|
int cmd_version(void);
|
||||||
|
int calculate_oath(uint8_t truncate,
|
||||||
|
const uint8_t *key,
|
||||||
|
size_t key_len,
|
||||||
|
const uint8_t *chal,
|
||||||
|
size_t chal_len);
|
||||||
|
int encrypt_keydev_f1(const uint8_t keydev[32]);
|
||||||
|
int resetPinUvAuthToken(void);
|
||||||
|
int resetPersistentPinUvAuthToken(void);
|
||||||
|
|
||||||
typedef struct pinUvAuthToken {
|
typedef struct pinUvAuthToken {
|
||||||
uint8_t *data;
|
uint8_t *data;
|
||||||
@@ -126,9 +145,17 @@ typedef struct pinUvAuthToken {
|
|||||||
bool user_verified;
|
bool user_verified;
|
||||||
} pinUvAuthToken_t;
|
} pinUvAuthToken_t;
|
||||||
|
|
||||||
|
typedef struct persistentPinUvAuthToken {
|
||||||
|
uint8_t *data;
|
||||||
|
size_t len;
|
||||||
|
uint8_t permissions;
|
||||||
|
} persistentPinUvAuthToken_t;
|
||||||
|
|
||||||
extern uint32_t user_present_time_limit;
|
extern uint32_t user_present_time_limit;
|
||||||
|
|
||||||
extern pinUvAuthToken_t paut;
|
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 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];
|
extern uint8_t session_pin[32];
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
@@ -21,16 +21,17 @@ file_t file_entries[] = {
|
|||||||
{ .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = { 0 } }, // MF
|
{ .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = { 0 } }, // MF
|
||||||
{ .fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key
|
{ .fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key
|
||||||
{ .fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL,.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key Enc
|
{ .fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL,.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key Enc
|
||||||
{ .fid = EF_MKEK, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MKEK
|
|
||||||
{ .fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Certificate Device
|
{ .fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Certificate Device
|
||||||
{ .fid = EF_EE_DEV_EA, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Enterprise Attestation Certificate
|
{ .fid = EF_EE_DEV_EA, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Enterprise Attestation Certificate
|
||||||
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
|
{ .fid = EF_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_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_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_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_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
|
||||||
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
|
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
|
||||||
{ .fid = EF_OTP_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } },
|
{ .fid = EF_OTP_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } },
|
||||||
|
{ .fid = EF_PIN_ADMIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // ADMIN PIN
|
||||||
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_NOT_KNOWN, .data = NULL, .ef_structure = 0, .acl = { 0 } } //end
|
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_NOT_KNOWN, .data = NULL, .ef_structure = 0, .acl = { 0 } } //end
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ file_t *ef_keydev = NULL;
|
|||||||
file_t *ef_certdev = NULL;
|
file_t *ef_certdev = NULL;
|
||||||
file_t *ef_counter = NULL;
|
file_t *ef_counter = NULL;
|
||||||
file_t *ef_pin = NULL;
|
file_t *ef_pin = NULL;
|
||||||
|
file_t *ef_pin_admin = NULL;
|
||||||
file_t *ef_authtoken = NULL;
|
file_t *ef_authtoken = NULL;
|
||||||
file_t *ef_keydev_enc = NULL;
|
file_t *ef_keydev_enc = NULL;
|
||||||
file_t *ef_largeblob = NULL;
|
file_t *ef_largeblob = NULL;
|
||||||
file_t *ef_mkek = NULL;
|
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _FILES_H_
|
#ifndef _FILES_H_
|
||||||
@@ -22,14 +22,16 @@
|
|||||||
|
|
||||||
#define EF_KEY_DEV 0xCC00
|
#define EF_KEY_DEV 0xCC00
|
||||||
#define EF_KEY_DEV_ENC 0xCC01
|
#define EF_KEY_DEV_ENC 0xCC01
|
||||||
#define EF_MKEK 0xCC0F
|
|
||||||
#define EF_EE_DEV 0xCE00
|
#define EF_EE_DEV 0xCE00
|
||||||
#define EF_EE_DEV_EA 0xCE01
|
#define EF_EE_DEV_EA 0xCE01
|
||||||
#define EF_COUNTER 0xC000
|
#define EF_COUNTER 0xC000
|
||||||
#define EF_OPTS 0xC001
|
#define EF_OPTS 0xC001
|
||||||
#define EF_PIN 0x1080
|
#define EF_PIN 0x1080
|
||||||
|
#define EF_PIN_ADMIN 0x1084
|
||||||
#define EF_AUTHTOKEN 0x1090
|
#define EF_AUTHTOKEN 0x1090
|
||||||
|
#define EF_PAUTHTOKEN 0x1091
|
||||||
#define EF_MINPINLEN 0x1100
|
#define EF_MINPINLEN 0x1100
|
||||||
|
#define EF_PIN_COMPLEXITY_POLICY 0x1102
|
||||||
#define EF_DEV_CONF 0x1122
|
#define EF_DEV_CONF 0x1122
|
||||||
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
||||||
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
||||||
@@ -38,15 +40,17 @@
|
|||||||
#define EF_OATH_CODE 0xBAFF
|
#define EF_OATH_CODE 0xBAFF
|
||||||
#define EF_OTP_SLOT1 0xBB00
|
#define EF_OTP_SLOT1 0xBB00
|
||||||
#define EF_OTP_SLOT2 0xBB01
|
#define EF_OTP_SLOT2 0xBB01
|
||||||
|
#define EF_OTP_SLOT3 0xBB02
|
||||||
|
#define EF_OTP_SLOT4 0xBB03
|
||||||
#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN
|
#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN
|
||||||
|
|
||||||
extern file_t *ef_keydev;
|
extern file_t *ef_keydev;
|
||||||
extern file_t *ef_certdev;
|
extern file_t *ef_certdev;
|
||||||
extern file_t *ef_counter;
|
extern file_t *ef_counter;
|
||||||
extern file_t *ef_pin;
|
extern file_t *ef_pin;
|
||||||
|
extern file_t *ef_pin_admin;
|
||||||
extern file_t *ef_authtoken;
|
extern file_t *ef_authtoken;
|
||||||
extern file_t *ef_keydev_enc;
|
extern file_t *ef_keydev_enc;
|
||||||
extern file_t *ef_largeblob;
|
extern file_t *ef_largeblob;
|
||||||
extern file_t *ef_mkek;
|
|
||||||
|
|
||||||
#endif //_FILES_H_
|
#endif //_FILES_H_
|
||||||
|
|||||||
137
src/fido/kek.c
137
src/fido/kek.c
@@ -1,137 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
|
||||||
* Copyright (c) 2022 Pol Henarejos.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "fido.h"
|
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "stdlib.h"
|
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
|
||||||
#include "pico/stdlib.h"
|
|
||||||
#endif
|
|
||||||
#include "kek.h"
|
|
||||||
#include "crypto_utils.h"
|
|
||||||
#include "random.h"
|
|
||||||
#include "mbedtls/md.h"
|
|
||||||
#include "mbedtls/cmac.h"
|
|
||||||
#include "mbedtls/rsa.h"
|
|
||||||
#include "mbedtls/ecdsa.h"
|
|
||||||
#include "mbedtls/chachapoly.h"
|
|
||||||
#include "files.h"
|
|
||||||
#include "otp.h"
|
|
||||||
|
|
||||||
extern uint8_t session_pin[32];
|
|
||||||
uint8_t mkek_mask[MKEK_KEY_SIZE];
|
|
||||||
bool has_mkek_mask = false;
|
|
||||||
|
|
||||||
#define POLY 0xedb88320
|
|
||||||
|
|
||||||
uint32_t crc32c(const uint8_t *buf, size_t len) {
|
|
||||||
uint32_t crc = 0xffffffff;
|
|
||||||
while (len--) {
|
|
||||||
crc ^= *buf++;
|
|
||||||
for (int k = 0; k < 8; k++) {
|
|
||||||
crc = (crc >> 1) ^ (POLY & (0 - (crc & 1)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ~crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mkek_masked(uint8_t *mkek, const uint8_t *mask) {
|
|
||||||
if (mask) {
|
|
||||||
for (int i = 0; i < MKEK_KEY_SIZE; i++) {
|
|
||||||
MKEK_KEY(mkek)[i] ^= mask[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int load_mkek(uint8_t *mkek) {
|
|
||||||
file_t *tf = search_file(EF_MKEK);
|
|
||||||
if (file_has_data(tf)) {
|
|
||||||
memcpy(mkek, file_get_data(tf), MKEK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_mkek_mask) {
|
|
||||||
mkek_masked(mkek, mkek_mask);
|
|
||||||
}
|
|
||||||
if (file_get_size(tf) == MKEK_SIZE) {
|
|
||||||
int ret = aes_decrypt_cfb_256(session_pin, MKEK_IV(mkek), MKEK_KEY(mkek), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
|
|
||||||
if (ret != 0) {
|
|
||||||
return PICOKEY_EXEC_ERROR;
|
|
||||||
}
|
|
||||||
if (crc32c(MKEK_KEY(mkek), MKEK_KEY_SIZE) != *(uint32_t *) MKEK_CHECKSUM(mkek)) {
|
|
||||||
return PICOKEY_WRONG_DKEK;
|
|
||||||
}
|
|
||||||
if (otp_key_1) {
|
|
||||||
mkek_masked(mkek, otp_key_1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return PICOKEY_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void release_mkek(uint8_t *mkek) {
|
|
||||||
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int 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];
|
|
||||||
if ((r = load_mkek(mkek)) != PICOKEY_OK) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
r = aes_decrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
|
|
||||||
release_mkek(mkek);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
|
||||||
* Copyright (c) 2022 Pol Henarejos.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _KEK_H_
|
|
||||||
#define _KEK_H_
|
|
||||||
|
|
||||||
#include "crypto_utils.h"
|
|
||||||
#if defined(ENABLE_EMULATION) || defined(ESP_PLATFORM)
|
|
||||||
#include <stdbool.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
extern int load_mkek(uint8_t *);
|
|
||||||
extern int store_mkek(const uint8_t *);
|
|
||||||
extern void init_mkek();
|
|
||||||
extern void release_mkek(uint8_t *);
|
|
||||||
extern int mkek_encrypt(uint8_t *data, uint16_t len);
|
|
||||||
extern int mkek_decrypt(uint8_t *data, uint16_t len);
|
|
||||||
|
|
||||||
#define MKEK_IV_SIZE (IV_SIZE)
|
|
||||||
#define MKEK_KEY_SIZE (32)
|
|
||||||
#define MKEK_KEY_CS_SIZE (4)
|
|
||||||
#define MKEK_SIZE (MKEK_IV_SIZE + MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE)
|
|
||||||
#define MKEK_IV(p) (p)
|
|
||||||
#define MKEK_KEY(p) (MKEK_IV(p) + MKEK_IV_SIZE)
|
|
||||||
#define MKEK_CHECKSUM(p) (MKEK_KEY(p) + MKEK_KEY_SIZE)
|
|
||||||
#define DKEK_KEY_SIZE (32)
|
|
||||||
|
|
||||||
extern uint8_t mkek_mask[MKEK_KEY_SIZE];
|
|
||||||
extern bool has_mkek_mask;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -3,18 +3,19 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "ctap2_cbor.h"
|
#include "ctap2_cbor.h"
|
||||||
|
|
||||||
|
|||||||
@@ -3,36 +3,38 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "picokeys.h"
|
||||||
|
#include "serial.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "asn1.h"
|
#include "tlv.h"
|
||||||
#include "management.h"
|
#include "management.h"
|
||||||
|
|
||||||
int man_process_apdu();
|
bool is_gpg = true;
|
||||||
int man_unload();
|
|
||||||
|
static int man_process_apdu(void);
|
||||||
|
static int man_unload(void);
|
||||||
|
|
||||||
const uint8_t man_aid[] = {
|
const uint8_t man_aid[] = {
|
||||||
8,
|
8,
|
||||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17
|
0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17
|
||||||
};
|
};
|
||||||
extern void scan_all();
|
static int man_select(app_t *a, uint8_t force) {
|
||||||
extern void init_otp();
|
|
||||||
int man_select(app_t *a, uint8_t force) {
|
|
||||||
a->process_apdu = man_process_apdu;
|
a->process_apdu = man_process_apdu;
|
||||||
a->unload = man_unload;
|
a->unload = man_unload;
|
||||||
sprintf((char *) res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
|
sprintf((char *) res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
|
||||||
@@ -40,32 +42,35 @@ int man_select(app_t *a, uint8_t force) {
|
|||||||
apdu.ne = res_APDU_size;
|
apdu.ne = res_APDU_size;
|
||||||
if (force) {
|
if (force) {
|
||||||
scan_all();
|
scan_all();
|
||||||
|
#ifdef ENABLE_OTP_APP
|
||||||
init_otp();
|
init_otp();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return PICOKEY_OK;
|
is_gpg = false;
|
||||||
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
INITIALIZER ( man_ctor ) {
|
INITIALIZER ( man_ctor ) {
|
||||||
register_app(man_select, man_aid);
|
register_app(man_select, man_aid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int man_unload() {
|
static int man_unload(void) {
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cap_supported(uint16_t cap) {
|
bool cap_supported(uint16_t cap) {
|
||||||
file_t *ef = search_dynamic_file(EF_DEV_CONF);
|
file_t *ef = file_search(EF_DEV_CONF);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
uint16_t tag = 0x0;
|
uint16_t tag = 0x0;
|
||||||
uint8_t *tag_data = NULL, *p = NULL;
|
uint8_t *tag_data = NULL, *p = NULL;
|
||||||
uint16_t tag_len = 0;
|
uint16_t tag_len = 0;
|
||||||
asn1_ctx_t ctxi;
|
tlv_ctx_t ctxi;
|
||||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
tlv_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||||
while (walk_tlv(&ctxi, &p, &tag, &tag_len, &tag_data)) {
|
while (tlv_walk(&ctxi, &p, &tag, &tag_len, &tag_data)) {
|
||||||
if (tag == TAG_USB_ENABLED) {
|
if (tag == TAG_USB_ENABLED) {
|
||||||
uint16_t ecaps = tag_data[0];
|
uint16_t ecaps = tag_data[0];
|
||||||
if (tag_len == 2) {
|
if (tag_len == 2) {
|
||||||
ecaps = get_uint16_t_be(tag_data);
|
ecaps = get_uint16_be(tag_data);
|
||||||
}
|
}
|
||||||
return ecaps & cap;
|
return ecaps & cap;
|
||||||
}
|
}
|
||||||
@@ -74,17 +79,34 @@ bool cap_supported(uint16_t cap) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int man_get_config() {
|
static uint8_t _openpgp_aid[] = {
|
||||||
file_t *ef = search_dynamic_file(EF_DEV_CONF);
|
6,
|
||||||
|
0xD2, 0x76, 0x00, 0x01, 0x24, 0x01,
|
||||||
|
};
|
||||||
|
static uint8_t _piv_aid[] = {
|
||||||
|
5,
|
||||||
|
0xA0, 0x00, 0x00, 0x03, 0x8,
|
||||||
|
};
|
||||||
|
|
||||||
|
int man_get_config(void) {
|
||||||
|
file_t *ef = file_search(EF_DEV_CONF);
|
||||||
res_APDU_size = 0;
|
res_APDU_size = 0;
|
||||||
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
|
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
|
||||||
res_APDU[res_APDU_size++] = TAG_USB_SUPPORTED;
|
res_APDU[res_APDU_size++] = TAG_USB_SUPPORTED;
|
||||||
res_APDU[res_APDU_size++] = 2;
|
res_APDU[res_APDU_size++] = 2;
|
||||||
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
|
uint16_t caps = CAP_FIDO2 | CAP_OTP | CAP_U2F | CAP_OATH;
|
||||||
res_APDU[res_APDU_size++] = 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++] = TAG_SERIAL;
|
||||||
res_APDU[res_APDU_size++] = 4;
|
res_APDU[res_APDU_size++] = 4;
|
||||||
memcpy(res_APDU + res_APDU_size, pico_serial.id, 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_size += 4;
|
||||||
res_APDU[res_APDU_size++] = TAG_FORM_FACTOR;
|
res_APDU[res_APDU_size++] = TAG_FORM_FACTOR;
|
||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
@@ -97,8 +119,27 @@ int man_get_config() {
|
|||||||
if (!file_has_data(ef)) {
|
if (!file_has_data(ef)) {
|
||||||
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
|
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
|
||||||
res_APDU[res_APDU_size++] = 2;
|
res_APDU[res_APDU_size++] = 2;
|
||||||
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
|
caps = 0;
|
||||||
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
|
if (cap_supported(CAP_FIDO2)) {
|
||||||
|
caps |= CAP_FIDO2;
|
||||||
|
}
|
||||||
|
if (cap_supported(CAP_OTP)) {
|
||||||
|
caps |= CAP_OTP;
|
||||||
|
}
|
||||||
|
if (cap_supported(CAP_U2F)) {
|
||||||
|
caps |= CAP_U2F;
|
||||||
|
}
|
||||||
|
if (cap_supported(CAP_OATH)) {
|
||||||
|
caps |= CAP_OATH;
|
||||||
|
}
|
||||||
|
if (cap_supported(CAP_OPENPGP)) {
|
||||||
|
caps |= CAP_OPENPGP;
|
||||||
|
}
|
||||||
|
if (cap_supported(CAP_PIV)) {
|
||||||
|
caps |= CAP_PIV;
|
||||||
|
}
|
||||||
|
res_APDU[res_APDU_size++] = caps >> 8;
|
||||||
|
res_APDU[res_APDU_size++] = caps & 0xFF;
|
||||||
res_APDU[res_APDU_size++] = TAG_DEVICE_FLAGS;
|
res_APDU[res_APDU_size++] = TAG_DEVICE_FLAGS;
|
||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
res_APDU[res_APDU_size++] = FLAG_EJECT;
|
res_APDU[res_APDU_size++] = FLAG_EJECT;
|
||||||
@@ -114,23 +155,32 @@ int man_get_config() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_read_config() {
|
static int cmd_read_config(void) {
|
||||||
man_get_config();
|
man_get_config();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_write_config() {
|
static int cmd_write_config(void) {
|
||||||
if (apdu.data[0] != apdu.nc - 1) {
|
if (apdu.data[0] != apdu.nc - 1) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
file_t *ef = file_new(EF_DEV_CONF);
|
file_t *ef = file_new(EF_DEV_CONF);
|
||||||
file_put_data(ef, apdu.data + 1, (uint16_t)(apdu.nc - 1));
|
file_put_data(ef, apdu.data + 1, (uint16_t)(apdu.nc - 1));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
if (cap_supported(CAP_OTP)) {
|
||||||
|
phy_data.enabled_usb_itf |= PHY_USB_ITF_KB;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
phy_data.enabled_usb_itf &= ~PHY_USB_ITF_KB;
|
||||||
|
}
|
||||||
|
phy_save();
|
||||||
|
#endif
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern int cbor_reset();
|
extern int cbor_reset(void);
|
||||||
int cmd_factory_reset() {
|
static int cmd_factory_reset(void) {
|
||||||
cbor_reset();
|
cbor_reset();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
@@ -146,7 +196,7 @@ static const cmd_t cmds[] = {
|
|||||||
{ 0x00, 0x0 }
|
{ 0x00, 0x0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
int man_process_apdu() {
|
static int man_process_apdu(void) {
|
||||||
if (CLA(apdu) != 0x00) {
|
if (CLA(apdu) != 0x00) {
|
||||||
return SW_CLA_NOT_SUPPORTED();
|
return SW_CLA_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,23 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _MANAGEMENT_H_
|
#ifndef _MANAGEMENT_H_
|
||||||
#define _MANAGEMENT_H_
|
#define _MANAGEMENT_H_
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -50,6 +50,6 @@
|
|||||||
#define FLAG_EJECT 0x80
|
#define FLAG_EJECT 0x80
|
||||||
|
|
||||||
extern bool cap_supported(uint16_t cap);
|
extern bool cap_supported(uint16_t cap);
|
||||||
extern int man_get_config();
|
extern int man_get_config(void);
|
||||||
|
|
||||||
#endif //_MANAGEMENT_H
|
#endif //_MANAGEMENT_H
|
||||||
|
|||||||
375
src/fido/oath.c
375
src/fido/oath.c
@@ -3,25 +3,26 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "picokeys.h"
|
||||||
|
#include "serial.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "asn1.h"
|
#include "tlv.h"
|
||||||
#include "crypto_utils.h"
|
#include "crypto_utils.h"
|
||||||
#include "management.h"
|
#include "management.h"
|
||||||
|
|
||||||
@@ -44,6 +45,10 @@
|
|||||||
#define TAG_PASSWORD 0x80
|
#define TAG_PASSWORD 0x80
|
||||||
#define TAG_NEW_PASSWORD 0x81
|
#define TAG_NEW_PASSWORD 0x81
|
||||||
#define TAG_PIN_COUNTER 0x82
|
#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_SHA1 0x01
|
||||||
#define ALG_HMAC_SHA256 0x02
|
#define ALG_HMAC_SHA256 0x02
|
||||||
@@ -56,9 +61,10 @@
|
|||||||
|
|
||||||
#define PROP_INC 0x01
|
#define PROP_INC 0x01
|
||||||
#define PROP_TOUCH 0x02
|
#define PROP_TOUCH 0x02
|
||||||
|
#define PROP_PIN 0x03
|
||||||
|
|
||||||
int oath_process_apdu();
|
static int oath_process_apdu(void);
|
||||||
int oath_unload();
|
static int oath_unload(void);
|
||||||
|
|
||||||
static bool validated = true;
|
static bool validated = true;
|
||||||
static uint8_t challenge[CHALLENGE_LEN] = { 0 };
|
static uint8_t challenge[CHALLENGE_LEN] = { 0 };
|
||||||
@@ -68,7 +74,7 @@ const uint8_t oath_aid[] = {
|
|||||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01
|
0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01
|
||||||
};
|
};
|
||||||
|
|
||||||
int oath_select(app_t *a, uint8_t force) {
|
static int oath_select(app_t *a, uint8_t force) {
|
||||||
(void) force;
|
(void) force;
|
||||||
if (cap_supported(CAP_OATH)) {
|
if (cap_supported(CAP_OATH)) {
|
||||||
a->process_apdu = oath_process_apdu;
|
a->process_apdu = oath_process_apdu;
|
||||||
@@ -82,63 +88,69 @@ int oath_select(app_t *a, uint8_t force) {
|
|||||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||||
res_APDU[res_APDU_size++] = 8;
|
res_APDU[res_APDU_size++] = 8;
|
||||||
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); res_APDU_size += 8;
|
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); res_APDU_size += 8;
|
||||||
if (file_has_data(search_dynamic_file(EF_OATH_CODE)) == true) {
|
if (file_has_data(file_search(EF_OATH_CODE)) == true) {
|
||||||
random_gen(NULL, challenge, sizeof(challenge));
|
random_fill_buffer(challenge, sizeof(challenge));
|
||||||
res_APDU[res_APDU_size++] = TAG_CHALLENGE;
|
res_APDU[res_APDU_size++] = TAG_CHALLENGE;
|
||||||
res_APDU[res_APDU_size++] = sizeof(challenge);
|
res_APDU[res_APDU_size++] = sizeof(challenge);
|
||||||
memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge));
|
memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge));
|
||||||
res_APDU_size += sizeof(challenge);
|
res_APDU_size += sizeof(challenge);
|
||||||
}
|
res_APDU[res_APDU_size++] = TAG_ALGO;
|
||||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
|
||||||
if (file_has_data(ef_otp_pin)) {
|
|
||||||
const uint8_t *pin_data = file_get_data(ef_otp_pin);
|
|
||||||
res_APDU[res_APDU_size++] = TAG_PIN_COUNTER;
|
|
||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
res_APDU[res_APDU_size++] = *pin_data;
|
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
|
||||||
|
}
|
||||||
|
if (is_nk) {
|
||||||
|
res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
|
||||||
|
res_APDU[res_APDU_size++] = 8;
|
||||||
|
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
|
||||||
|
res_APDU_size += 8;
|
||||||
|
file_t *ef_otp_pin = file_search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||||
|
if (file_has_data(ef_otp_pin)) {
|
||||||
|
const uint8_t *pin_data = file_get_data(ef_otp_pin);
|
||||||
|
res_APDU[res_APDU_size++] = TAG_PIN_COUNTER;
|
||||||
|
res_APDU[res_APDU_size++] = 1;
|
||||||
|
res_APDU[res_APDU_size++] = *pin_data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
res_APDU[res_APDU_size++] = TAG_ALGO;
|
|
||||||
res_APDU[res_APDU_size++] = 1;
|
|
||||||
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
|
|
||||||
apdu.ne = res_APDU_size;
|
apdu.ne = res_APDU_size;
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
INITIALIZER ( oath_ctor ) {
|
INITIALIZER ( oath_ctor ) {
|
||||||
register_app(oath_select, oath_aid);
|
register_app(oath_select, oath_aid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int oath_unload() {
|
static int oath_unload(void) {
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
file_t *find_oath_cred(const uint8_t *name, size_t name_len) {
|
static file_t *find_oath_cred(const uint8_t *name, size_t name_len) {
|
||||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
file_t *ef = file_search((uint16_t)(EF_OATH_CRED + i));
|
||||||
asn1_ctx_t ctxi, ef_tag = { 0 };
|
tlv_ctx_t ctxi, ef_tag = { 0 };
|
||||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
tlv_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||||
if (file_has_data(ef) && asn1_find_tag(&ctxi, TAG_NAME, &ef_tag) == true && ef_tag.len == name_len && memcmp(ef_tag.data, name, name_len) == 0) {
|
if (file_has_data(ef) && tlv_find_tag(&ctxi, TAG_NAME, &ef_tag) == true && ef_tag.len == name_len && memcmp(ef_tag.data, name, name_len) == 0) {
|
||||||
return ef;
|
return ef;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_put() {
|
static int cmd_put(void) {
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, imf = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, name = { 0 }, imf = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
if (tlv_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||||
if (asn1_find_tag(&ctxi, TAG_IMF, &imf) == false) {
|
if (tlv_find_tag(&ctxi, TAG_IMF, &imf) == false) {
|
||||||
memcpy(apdu.data + apdu.nc, "\x7a\x08\x00\x00\x00\x00\x00\x00\x00\x00", 10);
|
memcpy(apdu.data + apdu.nc, "\x7a\x08\x00\x00\x00\x00\x00\x00\x00\x00", 10);
|
||||||
apdu.nc += 10;
|
apdu.nc += 10;
|
||||||
}
|
}
|
||||||
@@ -155,15 +167,15 @@ int cmd_put() {
|
|||||||
file_t *ef = find_oath_cred(name.data, name.len);
|
file_t *ef = find_oath_cred(name.data, name.len);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
file_put_data(ef, apdu.data, (uint16_t)apdu.nc);
|
file_put_data(ef, apdu.data, (uint16_t)apdu.nc);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||||
file_t *tef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
file_t *tef = file_search((uint16_t)(EF_OATH_CRED + i));
|
||||||
if (!file_has_data(tef)) {
|
if (!file_has_data(tef)) {
|
||||||
tef = file_new((uint16_t)(EF_OATH_CRED + i));
|
tef = file_new((uint16_t)(EF_OATH_CRED + i));
|
||||||
file_put_data(tef, apdu.data, (uint16_t)apdu.nc);
|
file_put_data(tef, apdu.data, (uint16_t)apdu.nc);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,16 +185,16 @@ int cmd_put() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int cmd_delete() {
|
static int cmd_delete(void) {
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, ctxo = { 0 };
|
tlv_ctx_t ctxi, ctxo = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &ctxo) == true) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &ctxo) == true) {
|
||||||
file_t *ef = find_oath_cred(ctxo.data, ctxo.len);
|
file_t *ef = find_oath_cred(ctxo.data, ctxo.len);
|
||||||
if (ef) {
|
if (ef) {
|
||||||
delete_file(ef);
|
file_delete(ef);
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
@@ -190,7 +202,7 @@ int cmd_delete() {
|
|||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
|
|
||||||
const mbedtls_md_info_t *get_oath_md_info(uint8_t alg) {
|
static const mbedtls_md_info_t *get_oath_md_info(uint8_t alg) {
|
||||||
if ((alg & ALG_MASK) == ALG_HMAC_SHA1) {
|
if ((alg & ALG_MASK) == ALG_HMAC_SHA1) {
|
||||||
return mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
|
return mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
|
||||||
}
|
}
|
||||||
@@ -203,29 +215,29 @@ const mbedtls_md_info_t *get_oath_md_info(uint8_t alg) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_set_code() {
|
static int cmd_set_code(void) {
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
if (apdu.nc == 0) {
|
if (apdu.nc == 0) {
|
||||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
file_delete(file_search(EF_OATH_CODE));
|
||||||
validated = true;
|
validated = true;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
if (tlv_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (key.len == 0) {
|
if (key.len == 0) {
|
||||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
file_delete(file_search(EF_OATH_CODE));
|
||||||
validated = true;
|
validated = true;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
if (tlv_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
if (tlv_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,45 +253,56 @@ int cmd_set_code() {
|
|||||||
if (memcmp(hmac, resp.data, resp.len) != 0) {
|
if (memcmp(hmac, resp.data, resp.len) != 0) {
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
}
|
}
|
||||||
random_gen(NULL, challenge, sizeof(challenge));
|
random_fill_buffer(challenge, sizeof(challenge));
|
||||||
file_t *ef = file_new(EF_OATH_CODE);
|
file_t *ef = file_new(EF_OATH_CODE);
|
||||||
file_put_data(ef, key.data, key.len);
|
file_put_data(ef, key.data, key.len);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
validated = false;
|
validated = false;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_reset() {
|
static int cmd_reset(void) {
|
||||||
if (P1(apdu) != 0xde || P2(apdu) != 0xad) {
|
if (P1(apdu) != 0xde || P2(apdu) != 0xad) {
|
||||||
return SW_INCORRECT_P1P2();
|
return SW_INCORRECT_P1P2();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
file_t *ef = file_search((uint16_t)(EF_OATH_CRED + i));
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
delete_file(ef);
|
file_delete(ef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
file_delete(file_search(EF_OATH_CODE));
|
||||||
flash_clear_file(search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF));
|
flash_clear_file(file_search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
validated = true;
|
validated = true;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_list() {
|
static int cmd_list(void) {
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
|
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
|
||||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
file_t *ef = file_search((uint16_t)(EF_OATH_CRED + i));
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
|
||||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
tlv_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) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == true && tlv_find_tag(&ctxi, TAG_KEY, &key) == true) {
|
||||||
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
|
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];
|
res_APDU[res_APDU_size++] = key.data[0];
|
||||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||||
|
if (ext) {
|
||||||
|
uint8_t props = 0x0;
|
||||||
|
if (tlv_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || tlv_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || tlv_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
|
||||||
|
props |= 0x4;
|
||||||
|
}
|
||||||
|
if (tlv_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
|
||||||
|
props |= 0x1;
|
||||||
|
}
|
||||||
|
res_APDU[res_APDU_size++] = props;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,16 +310,16 @@ int cmd_list() {
|
|||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_validate() {
|
static int cmd_validate(void) {
|
||||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
if (tlv_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
if (tlv_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
file_t *ef = search_dynamic_file(EF_OATH_CODE);
|
file_t *ef = file_search(EF_OATH_CODE);
|
||||||
if (file_has_data(ef) == false) {
|
if (file_has_data(ef) == false) {
|
||||||
validated = true;
|
validated = true;
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
@@ -337,7 +360,7 @@ int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const u
|
|||||||
int r = mbedtls_md_hmac(md_info, key + 2, key_len - 2, chal, chal_len, hmac);
|
int r = mbedtls_md_hmac(md_info, key + 2, key_len - 2, chal, chal_len, hmac);
|
||||||
size_t hmac_size = mbedtls_md_get_size(md_info);
|
size_t hmac_size = mbedtls_md_get_size(md_info);
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
return PICOKEY_EXEC_ERROR;
|
return PICOKEYS_EXEC_ERROR;
|
||||||
}
|
}
|
||||||
if (truncate == 0x01) {
|
if (truncate == 0x01) {
|
||||||
res_APDU[res_APDU_size++] = 4 + 1;
|
res_APDU[res_APDU_size++] = 4 + 1;
|
||||||
@@ -354,36 +377,36 @@ int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const u
|
|||||||
memcpy(res_APDU + res_APDU_size, hmac, hmac_size); res_APDU_size += (uint16_t)hmac_size;
|
memcpy(res_APDU + res_APDU_size, hmac, hmac_size); res_APDU_size += (uint16_t)hmac_size;
|
||||||
}
|
}
|
||||||
apdu.ne = res_APDU_size;
|
apdu.ne = res_APDU_size;
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_calculate() {
|
static int cmd_calculate(void) {
|
||||||
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
||||||
return SW_INCORRECT_P1P2();
|
return SW_INCORRECT_P1P2();
|
||||||
}
|
}
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
if (tlv_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
file_t *ef = find_oath_cred(name.data, name.len);
|
file_t *ef = find_oath_cred(name.data, name.len);
|
||||||
if (file_has_data(ef) == false) {
|
if (file_has_data(ef) == false) {
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxe;
|
tlv_ctx_t ctxe;
|
||||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
tlv_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
||||||
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
if (tlv_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||||
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
if (tlv_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,48 +414,48 @@ int cmd_calculate() {
|
|||||||
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
||||||
|
|
||||||
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||||
uint64_t v = get_uint64_t_be(chal.data);
|
uint64_t v = get_uint64_be(chal.data);
|
||||||
size_t ef_size = file_get_size(ef);
|
size_t ef_size = file_get_size(ef);
|
||||||
v++;
|
v++;
|
||||||
uint8_t *tmp = (uint8_t *) calloc(1, ef_size);
|
uint8_t *tmp = (uint8_t *) calloc(1, ef_size);
|
||||||
memcpy(tmp, file_get_data(ef), ef_size);
|
memcpy(tmp, file_get_data(ef), ef_size);
|
||||||
asn1_ctx_t ctxt;
|
tlv_ctx_t ctxt;
|
||||||
asn1_ctx_init(tmp, (uint16_t)ef_size, &ctxt);
|
tlv_ctx_init(tmp, (uint16_t)ef_size, &ctxt);
|
||||||
asn1_find_tag(&ctxt, TAG_IMF, &chal);
|
tlv_find_tag(&ctxt, TAG_IMF, &chal);
|
||||||
put_uint64_t_be(v, chal.data);
|
put_uint64_be(v, chal.data);
|
||||||
file_put_data(ef, tmp, (uint16_t)ef_size);
|
file_put_data(ef, tmp, (uint16_t)ef_size);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
free(tmp);
|
free(tmp);
|
||||||
}
|
}
|
||||||
apdu.ne = res_APDU_size;
|
apdu.ne = res_APDU_size;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_calculate_all() {
|
static int cmd_calculate_all(void) {
|
||||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, prop = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, prop = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
||||||
return SW_INCORRECT_P1P2();
|
return SW_INCORRECT_P1P2();
|
||||||
}
|
}
|
||||||
if (validated == false) {
|
if (validated == false) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
if (tlv_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
res_APDU_size = 0;
|
res_APDU_size = 0;
|
||||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
file_t *ef = file_search((uint16_t)(EF_OATH_CRED + i));
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
const uint8_t *ef_data = file_get_data(ef);
|
const uint8_t *ef_data = file_get_data(ef);
|
||||||
size_t ef_len = file_get_size(ef);
|
size_t ef_len = file_get_size(ef);
|
||||||
asn1_ctx_t ctxe;
|
tlv_ctx_t ctxe;
|
||||||
asn1_ctx_init((uint8_t *)ef_data, (uint16_t)ef_len, &ctxe);
|
tlv_ctx_init((uint8_t *)ef_data, (uint16_t)ef_len, &ctxe);
|
||||||
if (asn1_find_tag(&ctxe, TAG_NAME, &name) == false || asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
if (tlv_find_tag(&ctxe, TAG_NAME, &name) == false || tlv_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||||
@@ -443,7 +466,7 @@ int cmd_calculate_all() {
|
|||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
res_APDU[res_APDU_size++] = key.data[1];
|
res_APDU[res_APDU_size++] = key.data[1];
|
||||||
}
|
}
|
||||||
else if (asn1_find_tag(&ctxe, TAG_PROPERTY, &prop) == true && (prop.data[0] & PROP_TOUCH)) {
|
else if (tlv_find_tag(&ctxe, TAG_PROPERTY, &prop) == true && (prop.data[0] & PROP_TOUCH)) {
|
||||||
res_APDU[res_APDU_size++] = TAG_TOUCH_RESPONSE;
|
res_APDU[res_APDU_size++] = TAG_TOUCH_RESPONSE;
|
||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
res_APDU[res_APDU_size++] = key.data[1];
|
res_APDU[res_APDU_size++] = key.data[1];
|
||||||
@@ -451,7 +474,7 @@ int cmd_calculate_all() {
|
|||||||
else {
|
else {
|
||||||
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
||||||
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
res_APDU[res_APDU_size++] = 1;
|
res_APDU[res_APDU_size++] = 1;
|
||||||
res_APDU[res_APDU_size++] = key.data[1];
|
res_APDU[res_APDU_size++] = key.data[1];
|
||||||
}
|
}
|
||||||
@@ -462,62 +485,62 @@ int cmd_calculate_all() {
|
|||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_send_remaining() {
|
static int cmd_send_remaining(void) {
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_set_otp_pin() {
|
static int cmd_set_otp_pin(void) {
|
||||||
uint8_t hsh[33] = { 0 };
|
uint8_t hsh[33] = { 0 };
|
||||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
file_t *ef_otp_pin = file_search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef_otp_pin)) {
|
if (file_has_data(ef_otp_pin)) {
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, pw = { 0 };
|
tlv_ctx_t ctxi, pw = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
if (tlv_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
hsh[0] = MAX_OTP_COUNTER;
|
hsh[0] = MAX_OTP_COUNTER;
|
||||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||||
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_change_otp_pin() {
|
static int cmd_change_otp_pin(void) {
|
||||||
uint8_t hsh[33] = { 0 };
|
uint8_t hsh[33] = { 0 };
|
||||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
file_t *ef_otp_pin = file_search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||||
if (!file_has_data(ef_otp_pin)) {
|
if (!file_has_data(ef_otp_pin)) {
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, pw = { 0 }, new_pw = { 0 };
|
tlv_ctx_t ctxi, pw = { 0 }, new_pw = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
if (tlv_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||||
if (memcmp(file_get_data(ef_otp_pin) + 1, hsh + 1, 32) != 0) {
|
if (memcmp(file_get_data(ef_otp_pin) + 1, hsh + 1, 32) != 0) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_NEW_PASSWORD, &new_pw) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NEW_PASSWORD, &new_pw) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
hsh[0] = MAX_OTP_COUNTER;
|
hsh[0] = MAX_OTP_COUNTER;
|
||||||
double_hash_pin(new_pw.data, new_pw.len, hsh + 1);
|
double_hash_pin(new_pw.data, new_pw.len, hsh + 1);
|
||||||
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_verify_otp_pin() {
|
static int cmd_verify_otp_pin(void) {
|
||||||
uint8_t hsh[33] = { 0 }, data_hsh[33];
|
uint8_t hsh[33] = { 0 }, data_hsh[33];
|
||||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
file_t *ef_otp_pin = file_search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||||
if (!file_has_data(ef_otp_pin)) {
|
if (!file_has_data(ef_otp_pin)) {
|
||||||
return SW_CONDITIONS_NOT_SATISFIED();
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxi, pw = { 0 };
|
tlv_ctx_t ctxi, pw = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
if (tlv_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||||
@@ -527,49 +550,49 @@ int cmd_verify_otp_pin() {
|
|||||||
data_hsh[0] -= 1;
|
data_hsh[0] -= 1;
|
||||||
}
|
}
|
||||||
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
validated = false;
|
validated = false;
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
data_hsh[0] = MAX_OTP_COUNTER;
|
data_hsh[0] = MAX_OTP_COUNTER;
|
||||||
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
validated = true;
|
validated = true;
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_verify_hotp() {
|
static int cmd_verify_hotp(void) {
|
||||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, code = { 0 };
|
tlv_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, code = { 0 };
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
uint32_t code_int = 0;
|
uint32_t code_int = 0;
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
file_t *ef = find_oath_cred(name.data, name.len);
|
file_t *ef = file_search_by_fid(EF_OATH_CRED, NULL, SPECIFY_EF);
|
||||||
if (file_has_data(ef) == false) {
|
if (file_has_data(ef) == false) {
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
}
|
}
|
||||||
asn1_ctx_t ctxe;
|
tlv_ctx_t ctxe;
|
||||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
tlv_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
||||||
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
if (tlv_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((key.data[0] & OATH_TYPE_MASK) != OATH_TYPE_HOTP) {
|
if ((key.data[0] & OATH_TYPE_MASK) != OATH_TYPE_HOTP) {
|
||||||
return SW_DATA_INVALID();
|
return SW_DATA_INVALID();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
if (tlv_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
||||||
return SW_INCORRECT_PARAMS();
|
return SW_INCORRECT_PARAMS();
|
||||||
}
|
}
|
||||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &code) == true) {
|
if (tlv_find_tag(&ctxi, TAG_RESPONSE, &code) == true) {
|
||||||
code_int = get_uint32_t_be(code.data);
|
code_int = get_uint32_be(code.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret = calculate_oath(0x01, key.data, key.len, chal.data, chal.len);
|
int ret = calculate_oath(0x01, key.data, key.len, chal.data, chal.len);
|
||||||
if (ret != PICOKEY_OK) {
|
if (ret != PICOKEYS_OK) {
|
||||||
return SW_EXEC_ERROR();
|
return SW_EXEC_ERROR();
|
||||||
}
|
}
|
||||||
uint32_t res_int = get_uint32_t_be(res_APDU + 2);
|
uint32_t res_int = get_uint32_be(res_APDU + 2);
|
||||||
if (res_APDU[1] == 6) {
|
if (res_APDU[1] == 6) {
|
||||||
res_int %= (uint32_t) 1e6;
|
res_int %= (uint32_t) 1e6;
|
||||||
}
|
}
|
||||||
@@ -584,18 +607,25 @@ int cmd_verify_hotp() {
|
|||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
int cmd_rename() {
|
static int cmd_rename(void) {
|
||||||
asn1_ctx_t ctxi, name = { 0 }, new_name = { 0 };
|
tlv_ctx_t ctxi, name = { 0 }, new_name = { 0 };
|
||||||
|
|
||||||
|
if (validated == false) {
|
||||||
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
|
}
|
||||||
if (apdu.data[0] != TAG_NAME) {
|
if (apdu.data[0] != TAG_NAME) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
|
|
||||||
asn1_ctx_init(name.data + name.len, (uint16_t)(apdu.nc - (name.data + name.len - apdu.data)), &ctxi);
|
tlv_ctx_init(name.data + name.len, (uint16_t)(apdu.nc - (name.data + name.len - apdu.data)), &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &new_name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &new_name) == false) {
|
||||||
|
return SW_WRONG_DATA();
|
||||||
|
}
|
||||||
|
if (name.len == new_name.len && memcmp(name.data, new_name.data, name.len) == 0) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
file_t *ef = find_oath_cred(name.data, name.len);
|
file_t *ef = find_oath_cred(name.data, name.len);
|
||||||
@@ -604,27 +634,68 @@ int cmd_rename() {
|
|||||||
}
|
}
|
||||||
uint8_t *fdata = file_get_data(ef);
|
uint8_t *fdata = file_get_data(ef);
|
||||||
uint16_t fsize = file_get_size(ef);
|
uint16_t fsize = file_get_size(ef);
|
||||||
asn1_ctx_init(fdata, fsize, &ctxi);
|
tlv_ctx_init(fdata, fsize, &ctxi);
|
||||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
if (tlv_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
uint8_t *new_data;
|
uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
|
||||||
if (new_name.len > name.len) {
|
|
||||||
new_data = (uint8_t *) calloc(1, file_get_size(ef) + new_name.len - name.len);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
new_data = (uint8_t *) calloc(1, file_get_size(ef));
|
|
||||||
}
|
|
||||||
memcpy(new_data, fdata, name.data - fdata);
|
memcpy(new_data, fdata, name.data - fdata);
|
||||||
*(new_data + (name.data - fdata) - 1) = new_name.len;
|
*(new_data + (name.data - fdata) - 1) = new_name.len;
|
||||||
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
|
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
|
||||||
memcpy(new_data + (name.data - fdata) + new_name.len, name.data + name.len, fsize - (name.data + name.len - fdata));
|
memcpy(new_data + (name.data - fdata) + new_name.len, name.data + name.len, fsize - (name.data + name.len - fdata));
|
||||||
file_put_data(ef, new_data, fsize + new_name.len - name.len);
|
file_put_data(ef, new_data, fsize + new_name.len - name.len);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
free(new_data);
|
free(new_data);
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int cmd_get_credential(void) {
|
||||||
|
tlv_ctx_t ctxi, name = { 0 };
|
||||||
|
if (apdu.nc < 3) {
|
||||||
|
return SW_INCORRECT_PARAMS();
|
||||||
|
}
|
||||||
|
if (apdu.data[0] != TAG_NAME) {
|
||||||
|
return SW_WRONG_DATA();
|
||||||
|
}
|
||||||
|
tlv_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||||
|
if (tlv_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();
|
||||||
|
}
|
||||||
|
tlv_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
|
||||||
|
tlv_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||||
|
if (tlv_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 (tlv_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 (tlv_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 (tlv_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 (tlv_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_PUT 0x01
|
||||||
#define INS_DELETE 0x02
|
#define INS_DELETE 0x02
|
||||||
#define INS_SET_CODE 0x03
|
#define INS_SET_CODE 0x03
|
||||||
@@ -639,6 +710,7 @@ int cmd_rename() {
|
|||||||
#define INS_VERIFY_PIN 0xb2
|
#define INS_VERIFY_PIN 0xb2
|
||||||
#define INS_CHANGE_PIN 0xb3
|
#define INS_CHANGE_PIN 0xb3
|
||||||
#define INS_SET_PIN 0xb4
|
#define INS_SET_PIN 0xb4
|
||||||
|
#define INS_GET_CREDENTIAL 0xb5
|
||||||
|
|
||||||
static const cmd_t cmds[] = {
|
static const cmd_t cmds[] = {
|
||||||
{ INS_PUT, cmd_put },
|
{ INS_PUT, cmd_put },
|
||||||
@@ -655,10 +727,11 @@ static const cmd_t cmds[] = {
|
|||||||
{ INS_CHANGE_PIN, cmd_change_otp_pin },
|
{ INS_CHANGE_PIN, cmd_change_otp_pin },
|
||||||
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
|
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
|
||||||
{ INS_VERIFY_CODE, cmd_verify_hotp },
|
{ INS_VERIFY_CODE, cmd_verify_hotp },
|
||||||
|
{ INS_GET_CREDENTIAL, cmd_get_credential },
|
||||||
{ 0x00, 0x0 }
|
{ 0x00, 0x0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
int oath_process_apdu() {
|
static int oath_process_apdu(void) {
|
||||||
if (CLA(apdu) != 0x00) {
|
if (CLA(apdu) != 0x00) {
|
||||||
return SW_CLA_NOT_SUPPORTED();
|
return SW_CLA_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
|
|||||||
408
src/fido/otp.c
408
src/fido/otp.c
@@ -3,35 +3,47 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "picokeys.h"
|
||||||
|
#include "serial.h"
|
||||||
|
#include "button.h"
|
||||||
#include "fido.h"
|
#include "fido.h"
|
||||||
#include "pico_keys.h"
|
|
||||||
#include "apdu.h"
|
#include "apdu.h"
|
||||||
#include "files.h"
|
#include "files.h"
|
||||||
#include "random.h"
|
#include "random.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "asn1.h"
|
|
||||||
#include "hid/ctap_hid.h"
|
#include "hid/ctap_hid.h"
|
||||||
#include "usb.h"
|
#include "usb.h"
|
||||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
#if defined(PICO_PLATFORM)
|
||||||
#include "bsp/board.h"
|
#include "bsp/board.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef ENABLE_EMULATION
|
||||||
|
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {
|
||||||
|
(void)buf;
|
||||||
|
(void)len;
|
||||||
|
(void)press_enter;
|
||||||
|
}
|
||||||
|
void append_keyboard_buffer(const uint8_t *buf, size_t len) {
|
||||||
|
(void)buf;
|
||||||
|
(void)len;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#include "tusb.h"
|
||||||
|
#endif
|
||||||
#include "mbedtls/aes.h"
|
#include "mbedtls/aes.h"
|
||||||
#include "management.h"
|
#include "management.h"
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
#include "tusb.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define FIXED_SIZE 16
|
#define FIXED_SIZE 16
|
||||||
#define KEY_SIZE 16
|
#define KEY_SIZE 16
|
||||||
@@ -46,6 +58,11 @@
|
|||||||
#define CONFIG_LED_INV 0x10
|
#define CONFIG_LED_INV 0x10
|
||||||
#define CONFIG_STATUS_MASK 0x1f
|
#define CONFIG_STATUS_MASK 0x1f
|
||||||
|
|
||||||
|
#define CONFIG3_VALID 0x01
|
||||||
|
#define CONFIG4_VALID 0x02
|
||||||
|
#define CONFIG3_TOUCH 0x04
|
||||||
|
#define CONFIG4_TOUCH 0x08
|
||||||
|
|
||||||
/* EXT Flags */
|
/* EXT Flags */
|
||||||
#define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press)
|
#define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press)
|
||||||
#define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field
|
#define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field
|
||||||
@@ -111,44 +128,40 @@ typedef struct otp_config {
|
|||||||
}) otp_config_t;
|
}) otp_config_t;
|
||||||
|
|
||||||
#define otp_config_size sizeof(otp_config_t)
|
#define otp_config_size sizeof(otp_config_t)
|
||||||
uint16_t otp_status(bool is_otp);
|
static uint16_t otp_status(bool is_otp);
|
||||||
|
static int otp_process_apdu(void);
|
||||||
|
static int otp_unload(void);
|
||||||
|
|
||||||
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 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);
|
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);
|
static 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);
|
static 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[] = {
|
const uint8_t otp_aid[] = {
|
||||||
7,
|
7,
|
||||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
|
0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
|
||||||
};
|
};
|
||||||
|
|
||||||
int otp_select(app_t *a, uint8_t force) {
|
static int otp_select(app_t *a, uint8_t force) {
|
||||||
(void) force;
|
(void) force;
|
||||||
if (cap_supported(CAP_OTP)) {
|
if (cap_supported(CAP_OTP)) {
|
||||||
a->process_apdu = otp_process_apdu;
|
a->process_apdu = otp_process_apdu;
|
||||||
a->unload = otp_unload;
|
a->unload = otp_unload;
|
||||||
if (file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ||
|
if (file_has_data(file_search(EF_OTP_SLOT1)) || file_has_data(file_search(EF_OTP_SLOT2))) {
|
||||||
file_has_data(search_dynamic_file(EF_OTP_SLOT2))) {
|
|
||||||
config_seq = 1;
|
config_seq = 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
config_seq = 0;
|
config_seq = 0;
|
||||||
}
|
}
|
||||||
otp_status(false);
|
otp_status(false);
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
return PICOKEYS_ERR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t modhex_tab[] =
|
uint8_t modhex_tab[] =
|
||||||
{ 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' };
|
{ 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' };
|
||||||
int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
|
static int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
|
||||||
for (size_t l = 0; l < len; l++) {
|
for (size_t l = 0; l < len; l++) {
|
||||||
*out++ = modhex_tab[in[l] >> 4];
|
*out++ = modhex_tab[in[l] >> 4];
|
||||||
*out++ = modhex_tab[in[l] & 0xf];
|
*out++ = modhex_tab[in[l] & 0xf];
|
||||||
@@ -156,36 +169,29 @@ int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
static bool scanned = false;
|
static bool scanned = false;
|
||||||
extern void scan_all();
|
void init_otp(void) {
|
||||||
void init_otp() {
|
|
||||||
if (scanned == false) {
|
if (scanned == false) {
|
||||||
scan_all();
|
scan_all();
|
||||||
for (uint8_t i = 0; i < 2; i++) {
|
for (uint8_t i = 0; i < 4; i++) {
|
||||||
file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
|
file_t *ef = file_search(EF_OTP_SLOT1 + i);
|
||||||
uint8_t *data = file_get_data(ef);
|
uint8_t *data = file_get_data(ef);
|
||||||
otp_config_t *otp_config = (otp_config_t *) data;
|
otp_config_t *otp_config = (otp_config_t *) data;
|
||||||
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) &&
|
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) &&
|
||||||
!(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
|
!(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
|
||||||
uint16_t counter = get_uint16_t_be(data + otp_config_size);
|
uint16_t counter = get_uint16_be(data + otp_config_size);
|
||||||
if (++counter <= 0x7fff) {
|
if (++counter <= 0x7fff) {
|
||||||
uint8_t new_data[otp_config_size + 8];
|
uint8_t new_data[otp_config_size + 8];
|
||||||
memcpy(new_data, data, sizeof(new_data));
|
memcpy(new_data, data, sizeof(new_data));
|
||||||
put_uint16_t_be(counter, new_data + otp_config_size);
|
put_uint16_be(counter, new_data + otp_config_size);
|
||||||
file_put_data(ef, new_data, sizeof(new_data));
|
file_put_data(ef, new_data, sizeof(new_data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scanned = true;
|
scanned = true;
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern int calculate_oath(uint8_t truncate,
|
static uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
|
||||||
const uint8_t *key,
|
|
||||||
size_t key_len,
|
|
||||||
const uint8_t *chal,
|
|
||||||
size_t chal_len);
|
|
||||||
|
|
||||||
uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
|
|
||||||
uint16_t crc = 0xFFFF;
|
uint16_t crc = 0xFFFF;
|
||||||
for (size_t idx = 0; idx < data_len; idx++) {
|
for (size_t idx = 0; idx < data_len; idx++) {
|
||||||
crc ^= data[idx];
|
crc ^= data[idx];
|
||||||
@@ -200,16 +206,14 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
|
|||||||
return crc & 0xFFFF;
|
return crc & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
static uint8_t session_counter[2] = { 0 };
|
static uint8_t session_counter[2] = { 0 };
|
||||||
#endif
|
static int otp_button_pressed(uint8_t slot) {
|
||||||
int otp_button_pressed(uint8_t slot) {
|
|
||||||
init_otp();
|
init_otp();
|
||||||
if (!cap_supported(CAP_OTP)) {
|
if (!cap_supported(CAP_OTP)) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
#ifndef ENABLE_EMULATION
|
uint16_t slot_ef = EF_OTP_SLOT1 + slot - 1;
|
||||||
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
file_t *ef = file_search(slot_ef);
|
||||||
const uint8_t *data = file_get_data(ef);
|
const uint8_t *data = file_get_data(ef);
|
||||||
otp_config_t *otp_config = (otp_config_t *) data;
|
otp_config_t *otp_config = (otp_config_t *) data;
|
||||||
if (file_has_data(ef) == false) {
|
if (file_has_data(ef) == false) {
|
||||||
@@ -218,24 +222,25 @@ int otp_button_pressed(uint8_t slot) {
|
|||||||
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
#ifdef ENABLE_OATH_APP
|
||||||
if (otp_config->tkt_flags & OATH_HOTP) {
|
if (otp_config->tkt_flags & OATH_HOTP) {
|
||||||
uint8_t tmp_key[KEY_SIZE + 2];
|
uint8_t tmp_key[KEY_SIZE + 2];
|
||||||
tmp_key[0] = 0x01;
|
tmp_key[0] = 0x01;
|
||||||
memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
|
memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
|
||||||
uint64_t imf = 0;
|
uint64_t imf = 0;
|
||||||
const uint8_t *p = data + otp_config_size;
|
const uint8_t *p = data + otp_config_size;
|
||||||
imf = get_uint64_t_be(p);
|
imf = get_uint64_be(p);
|
||||||
p += 8;
|
p += 8;
|
||||||
if (imf == 0) {
|
if (imf == 0) {
|
||||||
imf = get_uint16_t_be(otp_config->uid + 4);
|
imf = get_uint16_be(otp_config->uid + 4);
|
||||||
}
|
}
|
||||||
uint8_t chal[8];
|
uint8_t chal[8];
|
||||||
put_uint64_t_be(imf, chal);
|
put_uint64_be(imf, chal);
|
||||||
res_APDU_size = 0;
|
res_APDU_size = 0;
|
||||||
int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
|
int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
|
||||||
if (ret == PICOKEY_OK) {
|
if (ret == PICOKEYS_OK) {
|
||||||
uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
|
uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
|
||||||
uint32_t number = get_uint16_t_be(res_APDU + 2);
|
uint32_t number = get_uint16_be(res_APDU + 2);
|
||||||
number %= base;
|
number %= base;
|
||||||
char number_str[9];
|
char number_str[9];
|
||||||
if (otp_config->cfg_flags & OATH_HOTP8) {
|
if (otp_config->cfg_flags & OATH_HOTP8) {
|
||||||
@@ -248,17 +253,18 @@ int otp_button_pressed(uint8_t slot) {
|
|||||||
}
|
}
|
||||||
imf++;
|
imf++;
|
||||||
uint8_t new_chal[8];
|
uint8_t new_chal[8];
|
||||||
put_uint64_t_be(imf, new_chal);
|
put_uint64_be(imf, new_chal);
|
||||||
uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
|
uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
|
||||||
memcpy(new_otp_config, otp_config, otp_config_size);
|
memcpy(new_otp_config, otp_config, otp_config_size);
|
||||||
memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
|
memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
|
||||||
file_put_data(ef, new_otp_config, sizeof(new_otp_config));
|
file_put_data(ef, new_otp_config, sizeof(new_otp_config));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
if (otp_config->tkt_flags & APPEND_CR) {
|
if (otp_config->tkt_flags & APPEND_CR) {
|
||||||
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||||
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
|
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
|
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
|
||||||
@@ -272,7 +278,7 @@ int otp_button_pressed(uint8_t slot) {
|
|||||||
else {
|
else {
|
||||||
uint8_t otpk[22], *po = otpk;
|
uint8_t otpk[22], *po = otpk;
|
||||||
bool update_counter = false;
|
bool update_counter = false;
|
||||||
uint16_t counter = get_uint16_t_be(data + otp_config_size), crc = 0;
|
uint16_t counter = get_uint16_be(data + otp_config_size), crc = 0;
|
||||||
uint32_t ts = board_millis() / 1000;
|
uint32_t ts = board_millis() / 1000;
|
||||||
if (counter == 0) {
|
if (counter == 0) {
|
||||||
update_counter = true;
|
update_counter = true;
|
||||||
@@ -282,16 +288,16 @@ int otp_button_pressed(uint8_t slot) {
|
|||||||
po += 6;
|
po += 6;
|
||||||
memcpy(po, otp_config->uid, UID_SIZE);
|
memcpy(po, otp_config->uid, UID_SIZE);
|
||||||
po += UID_SIZE;
|
po += UID_SIZE;
|
||||||
po += put_uint16_t_le(counter, po);
|
po += put_uint16_le(counter, po);
|
||||||
ts >>= 1;
|
ts >>= 1;
|
||||||
*po++ = ts & 0xff;
|
*po++ = ts & 0xff;
|
||||||
*po++ = ts >> 8;
|
*po++ = ts >> 8;
|
||||||
*po++ = ts >> 16;
|
*po++ = ts >> 16;
|
||||||
*po++ = session_counter[slot - 1];
|
*po++ = session_counter[slot - 1];
|
||||||
random_gen(NULL, po, 2);
|
random_fill_buffer(po, 2);
|
||||||
po += 2;
|
po += 2;
|
||||||
crc = calculate_crc(otpk + 6, 14);
|
crc = calculate_crc(otpk + 6, 14);
|
||||||
po += put_uint16_t_le(~crc, po);
|
po += put_uint16_le(~crc, po);
|
||||||
mbedtls_aes_context ctx;
|
mbedtls_aes_context ctx;
|
||||||
mbedtls_aes_init(&ctx);
|
mbedtls_aes_init(&ctx);
|
||||||
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
||||||
@@ -312,31 +318,61 @@ int otp_button_pressed(uint8_t slot) {
|
|||||||
if (update_counter == true) {
|
if (update_counter == true) {
|
||||||
uint8_t new_data[otp_config_size + 8];
|
uint8_t new_data[otp_config_size + 8];
|
||||||
memcpy(new_data, data, sizeof(new_data));
|
memcpy(new_data, data, sizeof(new_data));
|
||||||
put_uint16_t_be(counter, new_data + otp_config_size);
|
put_uint16_be(counter, new_data + otp_config_size);
|
||||||
file_put_data(ef, new_data, sizeof(new_data));
|
file_put_data(ef, new_data, sizeof(new_data));
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
(void) slot;
|
|
||||||
#endif
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
INITIALIZER( otp_ctor ) {
|
INITIALIZER( otp_ctor ) {
|
||||||
register_app(otp_select, otp_aid);
|
register_app(otp_select, otp_aid);
|
||||||
button_pressed_cb = otp_button_pressed;
|
button_pressed_cb = otp_button_pressed;
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
hid_set_report_cb = otp_hid_set_report_cb;
|
hid_set_report_cb = otp_hid_set_report_cb;
|
||||||
hid_get_report_cb = otp_hid_get_report_cb;
|
hid_get_report_cb = otp_hid_get_report_cb;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int otp_unload() {
|
static int otp_unload(void) {
|
||||||
return PICOKEY_OK;
|
return PICOKEYS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t otp_status(bool is_otp) {
|
uint8_t status_byte = 0x0;
|
||||||
|
static uint16_t otp_status_ext(void) {
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
file_t *ef = file_search(EF_OTP_SLOT1 + i);
|
||||||
|
if (file_has_data(ef)) {
|
||||||
|
res_APDU[res_APDU_size++] = 0xB0 + i;
|
||||||
|
res_APDU[res_APDU_size++] = 0; // Filled later
|
||||||
|
uint8_t *p = res_APDU + res_APDU_size;
|
||||||
|
otp_config_t *otp_config = (otp_config_t *)file_get_data(ef);
|
||||||
|
*p++ = 0xA0;
|
||||||
|
*p++ = 2;
|
||||||
|
*p++ = otp_config->tkt_flags;
|
||||||
|
*p++ = otp_config->cfg_flags;
|
||||||
|
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (otp_config->tkt_flags & OATH_HOTP) {
|
||||||
|
}
|
||||||
|
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*p++ = 0xC0;
|
||||||
|
*p++ = 6;
|
||||||
|
memcpy(p, otp_config->fixed_data, 6);
|
||||||
|
p += 6;
|
||||||
|
}
|
||||||
|
uint8_t len = p - (res_APDU + res_APDU_size);
|
||||||
|
res_APDU[res_APDU_size - 1] = len;
|
||||||
|
res_APDU_size += len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SW_OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t otp_status(bool is_otp) {
|
||||||
if (scanned == false) {
|
if (scanned == false) {
|
||||||
scan_all();
|
scan_all();
|
||||||
scanned = true;
|
scanned = true;
|
||||||
@@ -349,12 +385,26 @@ uint16_t otp_status(bool is_otp) {
|
|||||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
||||||
res_APDU[res_APDU_size++] = 0;
|
res_APDU[res_APDU_size++] = 0;
|
||||||
res_APDU[res_APDU_size++] = config_seq;
|
res_APDU[res_APDU_size++] = config_seq;
|
||||||
res_APDU[res_APDU_size++] = (CONFIG2_TOUCH | CONFIG1_TOUCH) |
|
uint8_t opts = 0;
|
||||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ? CONFIG1_VALID :
|
file_t *ef = file_search(EF_OTP_SLOT1);
|
||||||
0x00) |
|
if (file_has_data(ef)) {
|
||||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT2)) ? CONFIG2_VALID :
|
opts |= CONFIG1_VALID;
|
||||||
0x00);
|
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
|
||||||
|
if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
|
||||||
|
opts |= CONFIG1_TOUCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ef = file_search(EF_OTP_SLOT2);
|
||||||
|
if (file_has_data(ef)) {
|
||||||
|
opts |= CONFIG2_VALID;
|
||||||
|
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
|
||||||
|
if (!(otp_config->tkt_flags & CHAL_RESP) || otp_config->cfg_flags & CHAL_BTN_TRIG) {
|
||||||
|
opts |= CONFIG2_TOUCH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res_APDU[res_APDU_size++] = opts;
|
||||||
res_APDU[res_APDU_size++] = 0;
|
res_APDU[res_APDU_size++] = 0;
|
||||||
|
res_APDU[res_APDU_size++] = status_byte;
|
||||||
if (is_otp) {
|
if (is_otp) {
|
||||||
res_APDU_size = 0;
|
res_APDU_size = 0;
|
||||||
}
|
}
|
||||||
@@ -365,49 +415,54 @@ uint16_t otp_status(bool is_otp) {
|
|||||||
return SW_OK();
|
return SW_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_crc(const otp_config_t *data) {
|
static bool check_crc(const otp_config_t *data) {
|
||||||
uint16_t crc = calculate_crc((const uint8_t *) data, otp_config_size);
|
uint16_t crc = calculate_crc((const uint8_t *) data, otp_config_size);
|
||||||
return crc == 0xF0B8;
|
return crc == 0xF0B8;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _is_otp = false;
|
bool _is_otp = false;
|
||||||
int cmd_otp() {
|
static int cmd_otp(void) {
|
||||||
uint8_t p1 = P1(apdu), p2 = P2(apdu);
|
uint8_t p1 = P1(apdu), p2 = P2(apdu);
|
||||||
if (p2 != 0x00) {
|
|
||||||
return SW_INCORRECT_P1P2();
|
|
||||||
}
|
|
||||||
if (p1 == 0x01 || p1 == 0x03) { // Configure slot
|
if (p1 == 0x01 || p1 == 0x03) { // Configure slot
|
||||||
otp_config_t *odata = (otp_config_t *) apdu.data;
|
otp_config_t *odata = (otp_config_t *) apdu.data;
|
||||||
file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
if (p1 == 0x03 && p2 != 0x0) {
|
||||||
|
return SW_INCORRECT_P1P2();
|
||||||
|
}
|
||||||
|
uint16_t slot = (p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
|
||||||
|
file_t *ef = file_new(slot);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
||||||
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
||||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int c = 0; c < otp_config_size; c++) {
|
for (size_t c = 0; c < otp_config_size; c++) {
|
||||||
if (apdu.data[c] != 0) {
|
if (apdu.data[c] != 0) {
|
||||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
|
memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
|
||||||
file_put_data(ef, apdu.data, otp_config_size + 8);
|
file_put_data(ef, apdu.data, otp_config_size + 8);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
config_seq++;
|
config_seq++;
|
||||||
return otp_status(_is_otp);
|
return otp_status(_is_otp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Delete slot
|
// Delete slot
|
||||||
delete_file(ef);
|
file_delete(ef);
|
||||||
config_seq++;
|
config_seq++;
|
||||||
return otp_status(_is_otp);
|
return otp_status(_is_otp);
|
||||||
}
|
}
|
||||||
else if (p1 == 0x04 || p1 == 0x05) {
|
else if (p1 == 0x04 || p1 == 0x05) { // Update slot
|
||||||
otp_config_t *odata = (otp_config_t *) apdu.data;
|
otp_config_t *odata = (otp_config_t *) apdu.data;
|
||||||
|
if (p1 == 0x05 && p2 != 0x0) {
|
||||||
|
return SW_INCORRECT_P1P2();
|
||||||
|
}
|
||||||
|
uint16_t slot = (p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
|
||||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
file_t *ef = file_search(slot);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
||||||
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
||||||
@@ -419,18 +474,32 @@ int cmd_otp() {
|
|||||||
(odata->ext_flags & EXTFLAG_UPDATE_MASK);
|
(odata->ext_flags & EXTFLAG_UPDATE_MASK);
|
||||||
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) |
|
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) |
|
||||||
(odata->tkt_flags & TKTFLAG_UPDATE_MASK);
|
(odata->tkt_flags & TKTFLAG_UPDATE_MASK);
|
||||||
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) |
|
if (!(otpc->tkt_flags & CHAL_RESP)) {
|
||||||
(odata->cfg_flags & CFGFLAG_UPDATE_MASK);
|
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) |
|
||||||
|
(odata->cfg_flags & CFGFLAG_UPDATE_MASK);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
odata->cfg_flags = otpc->cfg_flags;
|
||||||
|
}
|
||||||
file_put_data(ef, apdu.data, otp_config_size);
|
file_put_data(ef, apdu.data, otp_config_size);
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
|
config_seq++;
|
||||||
}
|
}
|
||||||
return otp_status(_is_otp);
|
return otp_status(_is_otp);
|
||||||
}
|
}
|
||||||
else if (p1 == 0x06) {
|
else if (p1 == 0x06) { // Swap slots
|
||||||
uint8_t tmp[otp_config_size + 8];
|
uint8_t tmp[otp_config_size + 8];
|
||||||
bool ef1_data = false;
|
bool ef1_data = false;
|
||||||
file_t *ef1 = file_new(EF_OTP_SLOT1);
|
uint16_t slot1 = EF_OTP_SLOT1, slot2 = EF_OTP_SLOT2;
|
||||||
file_t *ef2 = file_new(EF_OTP_SLOT2);
|
if (apdu.ne > 0) {
|
||||||
|
if (apdu.ne != 2) {
|
||||||
|
return SW_WRONG_LENGTH();
|
||||||
|
}
|
||||||
|
slot1 += apdu.data[0];
|
||||||
|
slot2 += apdu.data[1];
|
||||||
|
}
|
||||||
|
file_t *ef1 = file_new(slot1);
|
||||||
|
file_t *ef2 = file_new(slot2);
|
||||||
if (file_has_data(ef1)) {
|
if (file_has_data(ef1)) {
|
||||||
memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
|
memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
|
||||||
ef1_data = true;
|
ef1_data = true;
|
||||||
@@ -439,39 +508,79 @@ int cmd_otp() {
|
|||||||
file_put_data(ef1, file_get_data(ef2), file_get_size(ef2));
|
file_put_data(ef1, file_get_data(ef2), file_get_size(ef2));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
delete_file(ef1);
|
file_delete(ef1);
|
||||||
|
// When a dynamic file is deleted, existing referenes are invalidated
|
||||||
|
ef2 = file_new(slot2);
|
||||||
}
|
}
|
||||||
if (ef1_data) {
|
if (ef1_data) {
|
||||||
file_put_data(ef2, tmp, sizeof(tmp));
|
file_put_data(ef2, tmp, sizeof(tmp));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
delete_file(ef2);
|
file_delete(ef2);
|
||||||
}
|
}
|
||||||
low_flash_available();
|
flash_commit();
|
||||||
|
config_seq++;
|
||||||
return otp_status(_is_otp);
|
return otp_status(_is_otp);
|
||||||
}
|
}
|
||||||
else if (p1 == 0x10) {
|
else if (p1 == 0x10) {
|
||||||
memcpy(res_APDU, pico_serial.id, 4);
|
memcpy(res_APDU, pico_serial.id, 4);
|
||||||
|
res_APDU[0] &= ~0xFC; // Force 8-digit serial number
|
||||||
res_APDU_size = 4;
|
res_APDU_size = 4;
|
||||||
}
|
}
|
||||||
else if (p1 == 0x13) {
|
else if (p1 == 0x13) { // Get config
|
||||||
man_get_config();
|
man_get_config();
|
||||||
}
|
}
|
||||||
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) {
|
else if (p1 == 0x14) {
|
||||||
file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
otp_status_ext();
|
||||||
|
}
|
||||||
|
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) { // Calculate OTP
|
||||||
|
if ((p1 == 0x38 || p1 == 0x28) && p2 != 0x0) {
|
||||||
|
return SW_INCORRECT_P1P2();
|
||||||
|
}
|
||||||
|
uint16_t slot = (p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2) + p2;
|
||||||
|
file_t *ef = file_search(slot);
|
||||||
if (file_has_data(ef)) {
|
if (file_has_data(ef)) {
|
||||||
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
|
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
|
||||||
if (!(otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP)) {
|
if (!(otp_config->tkt_flags & CHAL_RESP)) {
|
||||||
return SW_WRONG_DATA();
|
return SW_WRONG_DATA();
|
||||||
}
|
}
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
uint8_t *rdata_bk = apdu.rdata;
|
||||||
|
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
|
||||||
|
status_byte = 0x20;
|
||||||
|
otp_status(_is_otp);
|
||||||
|
#ifndef ENABLE_EMULATION
|
||||||
|
if (button_wait()) {
|
||||||
|
status_byte = 0x00;
|
||||||
|
otp_status(_is_otp);
|
||||||
|
return SW_CONDITIONS_NOT_SATISFIED();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
status_byte = 0x10;
|
||||||
|
apdu.rdata = rdata_bk;
|
||||||
|
}
|
||||||
if (p1 == 0x30 || p1 == 0x38) {
|
if (p1 == 0x30 || p1 == 0x38) {
|
||||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), otp_config->aes_key, KEY_SIZE, apdu.data, 8, res_APDU);
|
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
|
||||||
|
return SW_WRONG_DATA();
|
||||||
|
}
|
||||||
|
uint8_t aes_key[KEY_SIZE + UID_SIZE];
|
||||||
|
memcpy(aes_key, otp_config->aes_key, KEY_SIZE);
|
||||||
|
memcpy(aes_key + KEY_SIZE, otp_config->uid, UID_SIZE);
|
||||||
|
uint8_t chal_len = 64;
|
||||||
|
if (otp_config->cfg_flags & HMAC_LT64) {
|
||||||
|
while (chal_len > 0 && apdu.data[63] == apdu.data[chal_len - 1]) {
|
||||||
|
chal_len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), aes_key, sizeof(aes_key), apdu.data, chal_len, res_APDU);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
res_APDU_size = 20;
|
res_APDU_size = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (p1 == 0x20 || p1 == 0x28) {
|
else if (p1 == 0x20 || p1 == 0x28) {
|
||||||
|
if (!(otp_config->cfg_flags & CHAL_YUBICO)) {
|
||||||
|
return SW_WRONG_DATA();
|
||||||
|
}
|
||||||
uint8_t challenge[16];
|
uint8_t challenge[16];
|
||||||
memcpy(challenge, apdu.data, 6);
|
memcpy(challenge, apdu.data, 6);
|
||||||
memcpy(challenge + 6, pico_serial_str, 10);
|
memcpy(challenge + 6, pico_serial_str, 10);
|
||||||
@@ -484,6 +593,9 @@ int cmd_otp() {
|
|||||||
res_APDU_size = 16;
|
res_APDU_size = 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ret == 0) {
|
||||||
|
status_byte = 0x00;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SW_OK();
|
return SW_OK();
|
||||||
@@ -496,7 +608,7 @@ static const cmd_t cmds[] = {
|
|||||||
{ 0x00, 0x0 }
|
{ 0x00, 0x0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
int otp_process_apdu() {
|
static int otp_process_apdu(void) {
|
||||||
if (CLA(apdu) != 0x00) {
|
if (CLA(apdu) != 0x00) {
|
||||||
return SW_CLA_NOT_SUPPORTED();
|
return SW_CLA_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
@@ -511,18 +623,14 @@ int otp_process_apdu() {
|
|||||||
return SW_INS_NOT_SUPPORTED();
|
return SW_INS_NOT_SUPPORTED();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef ENABLE_EMULATION
|
|
||||||
|
|
||||||
uint8_t otp_frame_rx[70] = {0};
|
uint8_t otp_frame_rx[70] = {0};
|
||||||
uint8_t otp_frame_tx[70] = {0};
|
uint8_t otp_frame_tx[70] = {0};
|
||||||
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
|
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
|
||||||
uint8_t otp_header[4] = {0};
|
uint8_t otp_header[4] = {0};
|
||||||
|
|
||||||
extern uint16_t *get_send_buffer_size(uint8_t itf);
|
static int otp_send_frame(uint8_t *frame, size_t frame_len) {
|
||||||
|
|
||||||
int otp_send_frame(uint8_t *frame, size_t frame_len) {
|
|
||||||
uint16_t crc = calculate_crc(frame, frame_len);
|
uint16_t crc = calculate_crc(frame, frame_len);
|
||||||
frame_len += put_uint16_t_le(~crc, frame + frame_len);
|
frame_len += put_uint16_le(~crc, frame + frame_len);
|
||||||
*get_send_buffer_size(ITF_KEYBOARD) = frame_len;
|
*get_send_buffer_size(ITF_KEYBOARD) = frame_len;
|
||||||
otp_exp_seq = (frame_len / 7);
|
otp_exp_seq = (frame_len / 7);
|
||||||
if (frame_len % 7) {
|
if (frame_len % 7) {
|
||||||
@@ -532,51 +640,48 @@ int otp_send_frame(uint8_t *frame, size_t frame_len) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int otp_hid_set_report_cb(uint8_t itf,
|
static int otp_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
|
||||||
uint8_t report_id,
|
(void)itf;
|
||||||
hid_report_type_t report_type,
|
(void)report_id;
|
||||||
uint8_t const *buffer,
|
(void)bufsize;
|
||||||
uint16_t bufsize)
|
if (report_type == 3) {
|
||||||
{
|
DEBUG_PAYLOAD(buffer, bufsize);
|
||||||
if (itf == ITF_KEYBOARD) {
|
if (buffer[7] == 0xFF) { // reset
|
||||||
if (report_type == 3) {
|
*get_send_buffer_size(ITF_KEYBOARD) = 0;
|
||||||
DEBUG_PAYLOAD(buffer, bufsize);
|
otp_curr_seq = otp_exp_seq = 0;
|
||||||
if (buffer[7] == 0xFF) { // reset
|
memset(otp_frame_tx, 0, sizeof(otp_frame_tx));
|
||||||
*get_send_buffer_size(ITF_KEYBOARD) = 0;
|
}
|
||||||
otp_curr_seq = otp_exp_seq = 0;
|
else if (buffer[7] & 0x80) { // a frame
|
||||||
memset(otp_frame_tx, 0, sizeof(otp_frame_tx));
|
uint8_t rseq = buffer[7] & 0x1F;
|
||||||
}
|
if (rseq < 10) {
|
||||||
else if (buffer[7] & 0x80) { // a frame
|
if (rseq == 0) {
|
||||||
uint8_t rseq = buffer[7] & 0x1F;
|
memset(otp_frame_rx, 0, sizeof(otp_frame_rx));
|
||||||
if (rseq < 10) {
|
}
|
||||||
if (rseq == 0) {
|
memcpy(otp_frame_rx + rseq * 7, buffer, 7);
|
||||||
memset(otp_frame_rx, 0, sizeof(otp_frame_rx));
|
if (rseq == 9) {
|
||||||
|
DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
|
||||||
|
DEBUG_PAYLOAD(otp_frame_rx, sizeof(otp_frame_rx));
|
||||||
|
uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = get_uint16_le(otp_frame_rx + 65);
|
||||||
|
uint8_t slot_id = otp_frame_rx[64];
|
||||||
|
if (residual_crc == rcrc) {
|
||||||
|
uint8_t hdr[5];
|
||||||
|
apdu.header = hdr;
|
||||||
|
apdu.data = otp_frame_rx;
|
||||||
|
apdu.nc = 64;
|
||||||
|
apdu.rdata = otp_frame_tx;
|
||||||
|
apdu.header[0] = 0;
|
||||||
|
apdu.header[1] = 0x01;
|
||||||
|
apdu.header[2] = slot_id;
|
||||||
|
apdu.header[3] = 0;
|
||||||
|
_is_otp = true;
|
||||||
|
int ret = otp_process_apdu();
|
||||||
|
if (ret == 0x9000 && res_APDU_size > 0) {
|
||||||
|
otp_send_frame(apdu.rdata, apdu.rlen);
|
||||||
|
}
|
||||||
|
_is_otp = false;
|
||||||
}
|
}
|
||||||
memcpy(otp_frame_rx + rseq * 7, buffer, 7);
|
else {
|
||||||
if (rseq == 9) {
|
printf("[OTP] Bad CRC!\n");
|
||||||
DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
|
|
||||||
uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = get_uint16_t_le(otp_frame_rx + 65);
|
|
||||||
uint8_t slot_id = otp_frame_rx[64];
|
|
||||||
if (residual_crc == rcrc) {
|
|
||||||
uint8_t hdr[5];
|
|
||||||
apdu.header = hdr;
|
|
||||||
apdu.data = otp_frame_rx;
|
|
||||||
apdu.nc = 64;
|
|
||||||
apdu.rdata = otp_frame_tx;
|
|
||||||
apdu.header[0] = 0;
|
|
||||||
apdu.header[1] = 0x01;
|
|
||||||
apdu.header[2] = slot_id;
|
|
||||||
apdu.header[3] = 0;
|
|
||||||
_is_otp = true;
|
|
||||||
int ret = otp_process_apdu();
|
|
||||||
if (ret == 0x9000 && res_APDU_size > 0) {
|
|
||||||
otp_send_frame(apdu.rdata, apdu.rlen);
|
|
||||||
}
|
|
||||||
_is_otp = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("[OTP] Bad CRC!\n");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,11 +691,11 @@ int otp_hid_set_report_cb(uint8_t itf,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t otp_hid_get_report_cb(uint8_t itf,
|
static uint16_t otp_hid_get_report_cb(uint8_t itf,
|
||||||
uint8_t report_id,
|
uint8_t report_id,
|
||||||
hid_report_type_t report_type,
|
hid_report_type_t report_type,
|
||||||
uint8_t *buffer,
|
uint8_t *buffer,
|
||||||
uint16_t reqlen) {
|
uint16_t reqlen) {
|
||||||
// TODO not Implemented
|
// TODO not Implemented
|
||||||
(void) itf;
|
(void) itf;
|
||||||
(void) report_id;
|
(void) report_id;
|
||||||
@@ -615,9 +720,8 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
|
|||||||
else {
|
else {
|
||||||
res_APDU = buffer;
|
res_APDU = buffer;
|
||||||
otp_status(true);
|
otp_status(true);
|
||||||
|
DEBUG_DATA(buffer, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reqlen;
|
return reqlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -3,22 +3,22 @@
|
|||||||
* Copyright (c) 2022 Pol Henarejos.
|
* Copyright (c) 2022 Pol Henarejos.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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.
|
* the Free Software Foundation, version 3.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but
|
* This program is distributed in the hope that it will be useful, but
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
* 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
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __VERSION_H_
|
#ifndef __VERSION_H_
|
||||||
#define __VERSION_H_
|
#define __VERSION_H_
|
||||||
|
|
||||||
#define PICO_FIDO_VERSION 0x0604
|
#define PICO_FIDO_VERSION 0x0706
|
||||||
|
|
||||||
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
||||||
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
source tests/docker_env.sh
|
source tests/docker_env.sh
|
||||||
#run_in_docker rm -rf CMakeFiles
|
#run_in_docker rm -rf CMakeFiles
|
||||||
run_in_docker mkdir -p build_in_docker
|
run_in_docker mkdir -p build_in_docker
|
||||||
run_in_docker -w "$PWD/build_in_docker" cmake -DENABLE_EMULATION=1 ..
|
run_in_docker -w "$PWD/build_in_docker" cmake -DENABLE_EMULATION=1 -DENABLE_EDDSA=1 ..
|
||||||
run_in_docker -w "$PWD/build_in_docker" make -j ${NUM_PROC}
|
run_in_docker -w "$PWD/build_in_docker" make -j ${NUM_PROC}
|
||||||
|
|||||||
@@ -20,12 +20,13 @@
|
|||||||
|
|
||||||
from http import client
|
from http import client
|
||||||
from fido2.hid import CtapHidDevice
|
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.attestation import FidoU2FAttestation
|
||||||
from fido2.ctap2.pin import ClientPin
|
from fido2.ctap2.pin import ClientPin
|
||||||
from fido2.server import Fido2Server
|
from fido2.server import Fido2Server
|
||||||
from fido2.ctap import CtapError
|
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 utils import *
|
||||||
from fido2.cose import ES256
|
from fido2.cose import ES256
|
||||||
import sys
|
import sys
|
||||||
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Device():
|
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.__user = None
|
||||||
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
||||||
self.__set_server(rp=rp, attestation=attestation)
|
self.__set_server(rp=rp, attestation=attestation)
|
||||||
|
|
||||||
|
def __verify_rp(rp_id, origin):
|
||||||
|
return True
|
||||||
|
|
||||||
def __set_client(self, origin, user_interaction, uv):
|
def __set_client(self, origin, user_interaction, uv):
|
||||||
self.__uv = uv
|
self.__uv = uv
|
||||||
@@ -101,14 +104,23 @@ class Device():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Set up a FIDO 2 client using the origin https://example.com
|
# 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
|
# Prefer UV if supported and configured
|
||||||
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
||||||
self.__uv = "preferred"
|
self.__uv = "preferred"
|
||||||
print("Authenticator supports User Verification")
|
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.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
|
||||||
self.ctap1 = self.__client1._backend.ctap1
|
self.ctap1 = self.__client1._backend.ctap1
|
||||||
|
|
||||||
@@ -117,7 +129,7 @@ class Device():
|
|||||||
self.__attestation = attestation
|
self.__attestation = attestation
|
||||||
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
||||||
self.__server.allowed_algorithms = [
|
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
|
for p in self.__client._backend.info.algorithms
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -216,9 +228,7 @@ class Device():
|
|||||||
'key_params':key_params}}
|
'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):
|
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(
|
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||||
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
|
||||||
)
|
|
||||||
rp = rp if rp is not Ellipsis else self.__rp
|
rp = rp if rp is not Ellipsis else self.__rp
|
||||||
user = user if user is not Ellipsis else self.user()
|
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
|
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
||||||
@@ -226,22 +236,31 @@ class Device():
|
|||||||
client = self.__client1
|
client = self.__client1
|
||||||
else:
|
else:
|
||||||
client = self.__client
|
client = self.__client
|
||||||
result = client._backend.do_make_credential(
|
options=PublicKeyCredentialCreationOptions(
|
||||||
client_data=client_data,
|
rp=PublicKeyCredentialRpEntity.from_dict(rp),
|
||||||
rp=rp,
|
user=PublicKeyCredentialUserEntity.from_dict(user),
|
||||||
user=user,
|
pub_key_cred_params=key_params,
|
||||||
key_params=key_params,
|
exclude_credentials=exclude_list,
|
||||||
exclude_list=exclude_list,
|
|
||||||
extensions=extensions,
|
extensions=extensions,
|
||||||
rk=rk,
|
challenge=os.urandom(32),
|
||||||
user_verification=user_verification,
|
authenticator_selection=AuthenticatorSelectionCriteria(
|
||||||
enterprise_attestation=enterprise_attestation,
|
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
|
event=event
|
||||||
)
|
)
|
||||||
return {'res':result,'req':{'client_data':client_data,
|
return {'res':result.response,'req':{'client_data':client_data,
|
||||||
'rp':rp,
|
'rp':rp,
|
||||||
'user':user,
|
'user':user,
|
||||||
'key_params':key_params}}
|
'key_params':key_params},'client_extension_results':result.client_extension_results}
|
||||||
|
|
||||||
def try_make_credential(self, options=None):
|
def try_make_credential(self, options=None):
|
||||||
if (options is None):
|
if (options is None):
|
||||||
@@ -267,14 +286,14 @@ class Device():
|
|||||||
|
|
||||||
# Complete registration
|
# Complete registration
|
||||||
auth_data = self.__server.register_complete(
|
auth_data = self.__server.register_complete(
|
||||||
state, result.client_data, result.attestation_object
|
state=state, response=result
|
||||||
)
|
)
|
||||||
credentials = [auth_data.credential_data]
|
credentials = [auth_data.credential_data]
|
||||||
|
|
||||||
print("New credential created!")
|
print("New credential created!")
|
||||||
|
|
||||||
print("CLIENT DATA:", result.client_data)
|
print("CLIENT DATA:", result.response.client_data)
|
||||||
print("ATTESTATION OBJECT:", result.attestation_object)
|
print("ATTESTATION OBJECT:", result.response.attestation_object)
|
||||||
print()
|
print()
|
||||||
print("CREDENTIAL DATA:", auth_data.credential_data)
|
print("CREDENTIAL DATA:", auth_data.credential_data)
|
||||||
|
|
||||||
@@ -294,17 +313,14 @@ class Device():
|
|||||||
self.__server.authenticate_complete(
|
self.__server.authenticate_complete(
|
||||||
state,
|
state,
|
||||||
credentials,
|
credentials,
|
||||||
result.credential_id,
|
result
|
||||||
result.client_data,
|
|
||||||
result.authenticator_data,
|
|
||||||
result.signature,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Credential authenticated!")
|
print("Credential authenticated!")
|
||||||
|
|
||||||
print("CLIENT DATA:", result.client_data)
|
print("CLIENT DATA:", result.response.client_data)
|
||||||
print()
|
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):
|
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']
|
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()
|
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):
|
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(
|
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||||
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
|
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']
|
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):
|
if (ctap1 is True):
|
||||||
client = self.__client1
|
client = self.__client1
|
||||||
else:
|
else:
|
||||||
client = self.__client
|
client = self.__client
|
||||||
try:
|
try:
|
||||||
result = client._backend.do_get_assertion(
|
result = client._backend.do_get_assertion(
|
||||||
|
options=options,
|
||||||
client_data=client_data,
|
client_data=client_data,
|
||||||
rp_id=rp_id,
|
rp_id=rp_id,
|
||||||
allow_list=allow_list,
|
|
||||||
extensions=extensions,
|
|
||||||
user_verification=user_verification,
|
|
||||||
event=event
|
event=event
|
||||||
)
|
)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
@@ -347,11 +373,9 @@ class Device():
|
|||||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||||
client_pin.set_pin(DEFAULT_PIN)
|
client_pin.set_pin(DEFAULT_PIN)
|
||||||
result = client._backend.do_get_assertion(
|
result = client._backend.do_get_assertion(
|
||||||
|
options=options,
|
||||||
client_data=client_data,
|
client_data=client_data,
|
||||||
rp_id=rp_id,
|
rp_id=rp_id,
|
||||||
allow_list=allow_list,
|
|
||||||
extensions=extensions,
|
|
||||||
user_verification=user_verification,
|
|
||||||
event=event
|
event=event
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
|
|||||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
], *args)
|
], *args)
|
||||||
aut_data = res['res'].get_response(0)
|
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
|
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.signature)
|
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
|
||||||
return aut_data
|
return aut_data
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
@@ -442,7 +466,7 @@ def ccid_card():
|
|||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def select_oath(ccid_card):
|
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)
|
resp = send_apdu(ccid_card, 0xA4, 0x04, 0x00, aid)
|
||||||
return ccid_card
|
return ccid_card
|
||||||
|
|
||||||
|
|||||||
36
tests/docker/bookworm/Dockerfile
Normal file
36
tests/docker/bookworm/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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 \
|
||||||
|
libtss2-dev \
|
||||||
|
tpm2-tools \
|
||||||
|
swtpm \
|
||||||
|
&& 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 \
|
cmake \
|
||||||
libfuse-dev \
|
libfuse-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
|
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
|
||||||
RUN git clone https://github.com/polhenarejos/python-fido2.git
|
|
||||||
WORKDIR /python-fido2
|
|
||||||
RUN git checkout development
|
|
||||||
RUN pip3 install .
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
||||||
WORKDIR /vsmartcard/virtualsmartcard
|
WORKDIR /vsmartcard/virtualsmartcard
|
||||||
|
|||||||
@@ -27,16 +27,17 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from .base import HidDescriptor
|
import logging
|
||||||
from ..ctap import CtapDevice, CtapError, STATUS
|
import os
|
||||||
from ..utils import LOG_LEVEL_TRAFFIC
|
|
||||||
from threading import Event
|
|
||||||
from enum import IntEnum, IntFlag, unique
|
|
||||||
from typing import Tuple, Optional, Callable, Iterator
|
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import os
|
from enum import IntEnum, IntFlag, unique
|
||||||
import logging
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
|
|||||||
from . import openbsd as backend
|
from . import openbsd as backend
|
||||||
else:
|
else:
|
||||||
raise Exception("Unsupported platform")
|
raise Exception("Unsupported platform")
|
||||||
|
|
||||||
from . import emulation as backend
|
from . import emulation as backend
|
||||||
|
|
||||||
list_descriptors = backend.list_descriptors
|
list_descriptors = backend.list_descriptors
|
||||||
@@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor
|
|||||||
open_connection = backend.open_connection
|
open_connection = backend.open_connection
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionFailure(Exception):
|
||||||
|
"""The CTAP connection failed or returned an invalid response."""
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class CTAPHID(IntEnum):
|
class CTAPHID(IntEnum):
|
||||||
PING = 0x01
|
PING = 0x01
|
||||||
@@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
response = self.call(CTAPHID.INIT, nonce)
|
response = self.call(CTAPHID.INIT, nonce)
|
||||||
r_nonce, response = response[:8], response[8:]
|
r_nonce, response = response[:8], response[8:]
|
||||||
if r_nonce != nonce:
|
if r_nonce != nonce:
|
||||||
raise Exception("Wrong nonce")
|
raise ConnectionFailure("Wrong nonce")
|
||||||
(
|
(
|
||||||
self._channel_id,
|
self._channel_id,
|
||||||
self._u2fhid_version,
|
self._u2fhid_version,
|
||||||
@@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
return self._u2fhid_version
|
return self._u2fhid_version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_version(self) -> Tuple[int, int, int]:
|
def device_version(self) -> tuple[int, int, int]:
|
||||||
"""Device version number."""
|
"""Device version number."""
|
||||||
return self._device_version
|
return self._device_version
|
||||||
|
|
||||||
@@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice):
|
|||||||
return self._capabilities
|
return self._capabilities
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def product_name(self) -> Optional[str]:
|
def product_name(self) -> str | None:
|
||||||
"""Product name of device."""
|
"""Product name of device."""
|
||||||
return self.descriptor.product_name
|
return self.descriptor.product_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serial_number(self) -> Optional[str]:
|
def serial_number(self) -> str | None:
|
||||||
"""Serial number of device."""
|
"""Serial number of device."""
|
||||||
return self.descriptor.serial_number
|
return self.descriptor.serial_number
|
||||||
|
|
||||||
@@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice):
|
|||||||
self,
|
self,
|
||||||
cmd: int,
|
cmd: int,
|
||||||
data: bytes = b"",
|
data: bytes = b"",
|
||||||
event: Optional[Event] = None,
|
event: Event | None = None,
|
||||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
on_keepalive: Callable[[STATUS], None] | None = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
event = event or Event()
|
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
|
remaining = data
|
||||||
seq = 0
|
seq = 0
|
||||||
|
|
||||||
@@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
r_channel = struct.unpack_from(">I", recv)[0]
|
r_channel = struct.unpack_from(">I", recv)[0]
|
||||||
recv = recv[4:]
|
recv = recv[4:]
|
||||||
if r_channel != self._channel_id:
|
if r_channel != self._channel_id:
|
||||||
raise Exception("Wrong channel")
|
raise ConnectionFailure("Wrong channel")
|
||||||
|
|
||||||
if not response: # Initialization packet
|
if not response: # Initialization packet
|
||||||
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
||||||
@@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice):
|
|||||||
if r_cmd == TYPE_INIT | cmd:
|
if r_cmd == TYPE_INIT | cmd:
|
||||||
pass # first data packet
|
pass # first data packet
|
||||||
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
||||||
ka_status = struct.unpack_from(">B", recv)[0]
|
try:
|
||||||
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
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:
|
if on_keepalive and ka_status != last_ka:
|
||||||
try:
|
|
||||||
ka_status = STATUS(ka_status)
|
|
||||||
except ValueError:
|
|
||||||
pass # Unknown status value
|
|
||||||
last_ka = ka_status
|
last_ka = ka_status
|
||||||
on_keepalive(ka_status)
|
on_keepalive(ka_status)
|
||||||
continue
|
continue
|
||||||
@@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
r_seq = struct.unpack_from(">B", recv)[0]
|
r_seq = struct.unpack_from(">B", recv)[0]
|
||||||
recv = recv[1:]
|
recv = recv[1:]
|
||||||
if r_seq != seq:
|
if r_seq != seq:
|
||||||
raise Exception("Wrong sequence number")
|
raise ConnectionFailure("Wrong sequence number")
|
||||||
seq += 1
|
seq += 1
|
||||||
|
|
||||||
response += recv
|
response += recv
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
|
|
||||||
# default values, can be overridden by the environment
|
# 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}"
|
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ def test_lockout(device, resetdevice, client_pin):
|
|||||||
res = client_pin.get_pin_retries()
|
res = client_pin.get_pin_retries()
|
||||||
assert res[0] == attempts
|
assert res[0] == attempts
|
||||||
|
|
||||||
if err == CtapError.ERR.PIN_AUTH_BLOCKED:
|
if e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED:
|
||||||
device.reboot()
|
device.reboot()
|
||||||
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
from fido2.client import CtapError
|
from fido2.client import CtapError
|
||||||
from fido2.cose import ES256, ES384, ES512, EdDSA
|
from fido2.cose import ES256, ES384, ES512, EdDSA
|
||||||
import fido2.features
|
|
||||||
fido2.features.webauthn_json_mapping.enabled = False
|
|
||||||
from utils import ES256K
|
from utils import ES256K
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
|
|||||||
|
|
||||||
def test_missing_user(device):
|
def test_missing_user(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user=None)
|
device.MC(user=None)
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
def test_bad_type_user_user(device):
|
def test_bad_type_user_user(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user=b"12345678")
|
device.MC(user=b"12345678")
|
||||||
|
|
||||||
def test_missing_rp(device):
|
def test_missing_rp(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
|
|||||||
|
|
||||||
def test_missing_pubKeyCredParams(device):
|
def test_missing_pubKeyCredParams(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(key_params=None)
|
device.MC(key_params=None)
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
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):
|
def test_bad_type_rp_name(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_type_rp_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
|
device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
|
||||||
|
|
||||||
def test_bad_type_rp_icon(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
|
|
||||||
|
|
||||||
def test_bad_type_user_name(device):
|
def test_bad_type_user_name(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_type_user_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_type_user_displayName(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
|
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
|
||||||
|
|
||||||
def test_bad_type_user_icon(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
|
|
||||||
|
|
||||||
def test_bad_type_pubKeyCredParams(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(key_params=["wrong"])
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
|
"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):
|
def test_missing_pubKeyCredParams_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
assert e.value.code == CtapError.ERR.INVALID_CBOR
|
||||||
|
|
||||||
def test_missing_pubKeyCredParams_alg(device):
|
def test_missing_pubKeyCredParams_alg(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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 [
|
assert e.value.code in [
|
||||||
CtapError.ERR.INVALID_CBOR,
|
CtapError.ERR.INVALID_CBOR,
|
||||||
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
|
|||||||
|
|
||||||
def test_bad_type_pubKeyCredParams_alg(device):
|
def test_bad_type_pubKeyCredParams_alg(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
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
|
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
||||||
|
|
||||||
def test_exclude_list(resetdevice):
|
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):
|
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):
|
def test_bad_type_exclude_list(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(exclude_list=["1234"])
|
device.MC(exclude_list=["1234"])
|
||||||
|
|
||||||
def test_missing_exclude_list_type(device):
|
def test_missing_exclude_list_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_missing_exclude_list_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_type_exclude_list_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_type_exclude_list_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ def test_authenticate(device):
|
|||||||
AUTRes = device.authenticate(credentials)
|
AUTRes = device.authenticate(credentials)
|
||||||
|
|
||||||
def test_assertion_auth_data(GARes):
|
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):
|
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):
|
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
|
||||||
res = device.GA(allow_list=[
|
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 """
|
""" Check that authenticator filters and stores items in allow list correctly """
|
||||||
allow_list = []
|
allow_list = []
|
||||||
|
|
||||||
rp1 = {"id": "rp1.com", "name": "rp1.com"}
|
rp1 = {"id": "example.com", "name": "rp1.com"}
|
||||||
rp2 = {"id": "rp2.com", "name": "rp2.com"}
|
rp2 = {"id": "example.com", "name": "rp2.com"}
|
||||||
|
|
||||||
rp1_registrations = []
|
rp1_registrations = []
|
||||||
rp2_registrations = []
|
rp2_registrations = []
|
||||||
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
|
|||||||
rp_id += ".com"
|
rp_id += ".com"
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||||
|
|
||||||
def test_missing_rp(device):
|
def test_missing_rp(device):
|
||||||
@@ -137,7 +137,7 @@ def test_missing_rp(device):
|
|||||||
|
|
||||||
def test_bad_rp(device):
|
def test_bad_rp(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_missing_cdh(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
|
|||||||
|
|
||||||
def test_bad_allow_list(device):
|
def test_bad_allow_list(device):
|
||||||
with pytest.raises(CtapError) as e:
|
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):
|
def test_bad_allow_list_item(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
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"}
|
{"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)
|
assert res.auth_data.flags & (1 << 0)
|
||||||
|
|
||||||
def test_allow_list_fake_item(device, MCRes):
|
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"}
|
{"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):
|
def test_allow_list_missing_field(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
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"}
|
{"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):
|
def test_allow_list_id_wrong_type(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
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"}
|
{"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):
|
def test_allow_list_missing_id(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
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"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,12 +57,12 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
|
|||||||
|
|
||||||
device.reset()
|
device.reset()
|
||||||
|
|
||||||
|
# It returns a silent authentication
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
ga_res = device.doGA(allow_list=allow_list)
|
ga_res = device.doGA(allow_list=allow_list)
|
||||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_resident_key(MCRes_DC, info):
|
def test_resident_key(MCRes_DC, info):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
|
|||||||
auths = []
|
auths = []
|
||||||
regs = []
|
regs = []
|
||||||
# Use unique RP to not collide with other credentials
|
# 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):
|
for i in range(0, 3):
|
||||||
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||||
regs.append(res)
|
regs.append(res)
|
||||||
@@ -117,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
|
|||||||
auths = resGA.get_assertions()
|
auths = resGA.get_assertions()
|
||||||
|
|
||||||
user_max_GA = auths[0]
|
user_max_GA = auths[0]
|
||||||
print(auths)
|
|
||||||
for y in ("name", "displayName", "id"):
|
for y in ("name", "displayName", "id"):
|
||||||
if (y in user_max_GA):
|
if (y in user_max_GA):
|
||||||
assert user_max_GA.user[y] == user_max[y]
|
assert user_max_GA.user[y] == user_max[y]
|
||||||
@@ -127,7 +127,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
|||||||
"""
|
"""
|
||||||
Test maximum returned capacity of the RK for the given RP
|
Test maximum returned capacity of the RK for the given RP
|
||||||
"""
|
"""
|
||||||
|
device.reset()
|
||||||
# Try to determine from get_info, or default to 19.
|
# Try to determine from get_info, or default to 19.
|
||||||
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
||||||
if not RK_CAPACITY_PER_RP:
|
if not RK_CAPACITY_PER_RP:
|
||||||
@@ -141,7 +141,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
# Use unique RP to not collide with other credentials from other tests.
|
# 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)
|
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
|
||||||
# res = device.sendGA(*req.toGA())
|
# res = device.sendGA(*req.toGA())
|
||||||
@@ -184,10 +184,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
rk_rp = {"id": "rk-cred.org", "name": "Example"}
|
rk_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_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 = [
|
allow_list_with_different_rp_cred = [
|
||||||
{
|
{
|
||||||
@@ -198,21 +198,40 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
|||||||
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||||
|
|
||||||
|
|
||||||
|
def test_same_prefix_userId(device):
|
||||||
|
"""
|
||||||
|
A make credential request with two different UserIds that share the same prefix should NOT overwrite.
|
||||||
|
"""
|
||||||
|
rp = {"id": "sameprefix.org", "name": "Example"}
|
||||||
|
user1 = {"id": b"user_12", "name": "A fixed name", "displayName": "A fixed display name"}
|
||||||
|
user2 = {"id": b"user_123", "name": "A fixed name", "displayName": "A fixed display name"}
|
||||||
|
|
||||||
|
mc_res1 = device.MC(rp = rp, options={"rk":True}, user = user1)
|
||||||
|
|
||||||
|
# Should not overwrite the first credential.
|
||||||
|
mc_res2 = device.MC(rp = rp, options={"rk":True}, user = user2)
|
||||||
|
|
||||||
|
ga_res = device.GA(rp_id=rp['id'])['res']
|
||||||
|
|
||||||
|
assert ga_res.number_of_credentials == 2
|
||||||
|
|
||||||
|
|
||||||
def test_same_userId_overwrites_rk(resetdevice):
|
def test_same_userId_overwrites_rk(resetdevice):
|
||||||
"""
|
"""
|
||||||
A make credential request with a UserId & Rp that is the same as an existing one should overwrite.
|
A make credential request with a UserId & Rp that is the same as an existing one should overwrite.
|
||||||
"""
|
"""
|
||||||
|
resetdevice.reset()
|
||||||
rp = {"id": "overwrite.org", "name": "Example"}
|
rp = {"id": "overwrite.org", "name": "Example"}
|
||||||
user = generate_random_user()
|
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.
|
# 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']
|
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
|
||||||
|
|
||||||
@@ -228,7 +247,7 @@ def test_larger_icon_than_128(device):
|
|||||||
user = generate_random_user()
|
user = generate_random_user()
|
||||||
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
|
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):
|
def test_returned_credential(device):
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from fido2.ctap import CtapError
|
from fido2.ctap import CtapError
|
||||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||||
|
from fido2.utils import websafe_decode
|
||||||
from utils import verify
|
from utils import verify
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def MCLBK(device):
|
def MCLBK(device):
|
||||||
res = device.doMC(
|
mc = device.doMC(
|
||||||
rk=True,
|
rk=True,
|
||||||
extensions={'largeBlob':{'support':'required'}}
|
extensions={'largeBlob':{'support':'required'}}
|
||||||
)['res']
|
)
|
||||||
return res
|
res = mc['res']
|
||||||
|
ext = mc['client_extension_results']
|
||||||
|
return {'res': res, 'ext': ext}
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBRead(device, MCLBK):
|
def GALBRead(device, MCLBK):
|
||||||
res = device.doGA(
|
res = device.doGA(
|
||||||
allow_list=[
|
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}}
|
],extensions={'largeBlob':{'read': True}}
|
||||||
)
|
)
|
||||||
assertions = res['res'].get_assertions()
|
assertions = res['res'].get_assertions()
|
||||||
for a in 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']
|
return res['res']
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBReadLB(GALBRead):
|
def GALBReadLB(GALBRead):
|
||||||
|
print(GALBRead.get_response(0))
|
||||||
return GALBRead.get_response(0)
|
return GALBRead.get_response(0)
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBWrite(device, MCLBK):
|
def GALBWrite(device, MCLBK):
|
||||||
res = device.doGA(
|
res = device.doGA(
|
||||||
allow_list=[
|
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}}
|
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
||||||
)
|
)
|
||||||
assertions = res['res'].get_assertions()
|
assertions = res['res'].get_assertions()
|
||||||
for a in 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)
|
return res['res'].get_response(0)
|
||||||
|
|
||||||
def test_supports_credblob(info):
|
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)
|
assert info.max_large_blob is None or (info.max_large_blob > 1024)
|
||||||
|
|
||||||
def test_get_largeblobkey_mc(MCLBK):
|
def test_get_largeblobkey_mc(MCLBK):
|
||||||
assert 'supported' in MCLBK.extension_results
|
assert 'largeBlob' in MCLBK['ext']
|
||||||
assert MCLBK.extension_results['supported'] is True
|
assert 'supported' in MCLBK['ext']['largeBlob']
|
||||||
|
assert MCLBK['ext']['largeBlob']['supported'] is True
|
||||||
|
|
||||||
def test_get_largeblobkey_ga(GALBReadLBK):
|
def test_get_largeblobkey_ga(GALBReadLBK):
|
||||||
assert GALBReadLBK.large_blob_key is not None
|
assert GALBReadLBK.large_blob_key is not None
|
||||||
|
|
||||||
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
||||||
assert 'written' in GALBWrite.extension_results
|
assert 'largeBlob' in GALBWrite.client_extension_results
|
||||||
assert GALBWrite.extension_results['written'] is True
|
assert 'written' in GALBWrite.client_extension_results['largeBlob']
|
||||||
|
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
|
||||||
|
|
||||||
assert 'blob' in GALBReadLB.extension_results
|
assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
|
||||||
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
|
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.ctap2.extensions import CredProtectExtension
|
||||||
from fido2.webauthn import UserVerificationRequirement
|
from fido2.webauthn import UserVerificationRequirement
|
||||||
from fido2.ctap import CtapError
|
from fido2.ctap import CtapError
|
||||||
|
from utils import generate_random_user
|
||||||
|
|
||||||
class CredProtect:
|
class CredProtect:
|
||||||
UserVerificationOptional = 1
|
UserVerificationOptional = 1
|
||||||
@@ -30,140 +31,139 @@ class CredProtect:
|
|||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def MCCredProtectOptional(resetdevice):
|
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
|
return res
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def MCCredProtectOptionalList(resetdevice):
|
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
|
return res
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
def MCCredProtectRequired(resetdevice):
|
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
|
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):
|
def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
|
||||||
assert MCCredProtectOptional.auth_data.extensions
|
assert MCCredProtectOptionalList.auth_data.extensions
|
||||||
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
|
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
|
||||||
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
|
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
|
||||||
|
|
||||||
def test_credprotect_make_credential_2(MCCredProtectOptionalList):
|
def test_credprotect_make_credential_3(self, MCCredProtectRequired):
|
||||||
assert MCCredProtectOptionalList.auth_data.extensions
|
assert MCCredProtectRequired.auth_data.extensions
|
||||||
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
|
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
|
||||||
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
|
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
|
||||||
|
|
||||||
def test_credprotect_make_credential_3(MCCredProtectRequired):
|
def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
|
||||||
assert MCCredProtectRequired.auth_data.extensions
|
""" CredProtectOptional Cred should be visible to be excluded with no UV """
|
||||||
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
|
exclude_list = [
|
||||||
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
|
{
|
||||||
|
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||||
|
"type": "public-key",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
def test_credprotect_optional_excluded(device, MCCredProtectOptional):
|
with pytest.raises(CtapError) as e:
|
||||||
""" CredProtectOptional Cred should be visible to be excluded with no UV """
|
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
|
||||||
exclude_list = [
|
|
||||||
{
|
|
||||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
|
||||||
"type": "public-key",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
|
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
def test_credprotect_optional_list_excluded(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):
|
with pytest.raises(CtapError) as e:
|
||||||
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
|
device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
|
||||||
exclude_list = [
|
|
||||||
{
|
|
||||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
|
||||||
"type": "public-key",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
|
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
def test_credprotect_required_not_excluded_with_no_uv(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):
|
# works
|
||||||
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
|
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
|
||||||
exclude_list = [
|
|
||||||
{
|
|
||||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
|
||||||
"type": "public-key",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
# works
|
def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
|
||||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
|
|
||||||
|
|
||||||
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
|
# works
|
||||||
|
res = device.doGA()['res'].get_assertions()[0]
|
||||||
|
|
||||||
# works
|
# If there's only one credential, this is None
|
||||||
res = device.doGA()['res'].get_assertions()[0]
|
assert res.number_of_credentials == None
|
||||||
|
|
||||||
# If there's only one credential, this is None
|
def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
|
||||||
assert res.number_of_credentials == None
|
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):
|
results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
|
||||||
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)['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.
|
def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
|
||||||
for res in results:
|
|
||||||
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
|
|
||||||
|
|
||||||
def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
|
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
|
||||||
):
|
|
||||||
|
|
||||||
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"]:
|
class TestCredProtectUv:
|
||||||
assert res.auth_data.extensions
|
def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
|
||||||
assert ext in res.auth_data.extensions
|
allow_list = [
|
||||||
assert res.auth_data.extensions[ext] == True
|
{
|
||||||
|
"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):
|
client_pin.set_pin(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"
|
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
|
||||||
|
|
||||||
client_pin.set_pin(pin)
|
assert res1.number_of_credentials in (None, 3)
|
||||||
|
|
||||||
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
|
|
||||||
|
|
||||||
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.utils import hmac_sha256
|
||||||
from fido2.ctap2.pin import PinProtocolV2
|
from fido2.ctap2.pin import PinProtocolV2
|
||||||
from fido2.webauthn import UserVerificationRequirement
|
from fido2.webauthn import UserVerificationRequirement
|
||||||
|
from fido2.client import ClientError
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
salt1 = b"\xa5" * 32
|
salt1 = b"\xa5" * 32
|
||||||
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
|
|||||||
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
||||||
return res['res'].attestation_object
|
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):
|
def test_hmac_secret_make_credential(MCHmacSecret):
|
||||||
assert MCHmacSecret.auth_data.extensions
|
assert MCHmacSecret.auth_data.extensions
|
||||||
assert "hmac-secret" in 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)])
|
@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]}
|
hout = {'salt1':salts[0]}
|
||||||
if (len(salts) > 1):
|
if (len(salts) > 1):
|
||||||
hout['salt2'] = salts[1]
|
hout['salt2'] = salts[1]
|
||||||
|
|
||||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
ext = auth.extension_results
|
ext = auth.client_extension_results
|
||||||
assert ext
|
assert ext
|
||||||
assert "hmacGetSecret" in 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:
|
if len(salts) == 1:
|
||||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
|
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||||
if len(salts) == 2:
|
if len(salts) == 2:
|
||||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
|
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 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]}
|
hout = {'salt1':salts[0]}
|
||||||
if (len(salts) > 1):
|
if (len(salts) > 1):
|
||||||
hout['salt2'] = salts[1]
|
hout['salt2'] = salts[1]
|
||||||
|
|
||||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
|
|
||||||
ext = auth.extension_results
|
ext = auth.client_extension_results
|
||||||
assert ext
|
assert ext
|
||||||
assert "hmacGetSecret" in 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:
|
if len(salts) == 2:
|
||||||
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
|
return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
|
||||||
else:
|
else:
|
||||||
return ext["hmacGetSecret"]['output1']
|
return ext.hmac_get_secret.output1
|
||||||
|
|
||||||
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
def test_hmac_secret_sanity(device, MCHmacSecret):
|
||||||
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
|
output1 = get_output(device, MCHmacSecret, (salt1,))
|
||||||
output12 = get_output(
|
output12 = get_output(
|
||||||
device, MCHmacSecret, hmac, (salt1, salt2)
|
device, MCHmacSecret, (salt1, salt2)
|
||||||
)
|
)
|
||||||
output21 = get_output(
|
output21 = get_output(
|
||||||
device, MCHmacSecret, hmac, (salt2, salt1)
|
device, MCHmacSecret, (salt2, salt1)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert output12[0] == output1
|
assert output12[0] == output1
|
||||||
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
|||||||
assert output21[0] == output12[1]
|
assert output21[0] == output12[1]
|
||||||
assert output12[0] != output12[1]
|
assert output12[0] != output12[1]
|
||||||
|
|
||||||
def test_missing_keyAgreement(device, hmac):
|
def test_missing_keyAgreement(device):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError):
|
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):
|
def test_missing_saltAuth(device):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
def test_missing_saltEnc(device, hmac):
|
def test_missing_saltEnc(device,):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
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}})
|
key_agreement = {
|
||||||
bad_auth = list(hout[3][:])
|
1: 2,
|
||||||
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
|
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
|
||||||
bad_auth = bytes(bad_auth)
|
-1: 1,
|
||||||
|
-2: b'\x00'*32,
|
||||||
|
-3: b'\x00'*32,
|
||||||
|
}
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
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
|
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
|
||||||
|
|
||||||
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
||||||
def test_invalid_salt_length(device, hmac, salts):
|
def test_invalid_salt_length(device, salts):
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises((CtapError,ClientError)) as e:
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
|
|
||||||
device.doGA(extensions={"hmacGetSecret": hout})
|
device.doGA(extensions={"hmacGetSecret": hout})
|
||||||
|
|
||||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||||
def test_get_next_assertion_has_extension(
|
def test_get_next_assertion_has_extension(
|
||||||
device, hmac, salts
|
device, salts
|
||||||
):
|
):
|
||||||
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
accounts = 3
|
accounts = 3
|
||||||
regs = []
|
regs = []
|
||||||
auths = []
|
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)]
|
fixed_users = [generate_random_user() for _ in range(accounts)]
|
||||||
for i in range(accounts):
|
for i in range(accounts):
|
||||||
res = device.doMC(extensions={"hmacCreateSecret": True},
|
res = device.doMC(extensions={"hmacCreateSecret": True},
|
||||||
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
|
|||||||
assert "hmac-secret" in ext
|
assert "hmac-secret" in ext
|
||||||
assert isinstance(ext["hmac-secret"], bytes)
|
assert isinstance(ext["hmac-secret"], bytes)
|
||||||
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
||||||
key = hmac.process_get_output(x)
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
|
||||||
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
|
||||||
salts = [salt1]
|
salts = [salt1]
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
|
|
||||||
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
|
auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 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 ext_no_uv
|
||||||
assert "hmac-secret" in ext_no_uv
|
assert "hmac-secret" in ext_no_uv
|
||||||
assert isinstance(ext_no_uv["hmac-secret"], bytes)
|
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]
|
hout['salt2'] = salts[1]
|
||||||
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
||||||
|
|
||||||
assert auth_uv.authenticator_data.flags & (1 << 2)
|
assert auth_uv.response.authenticator_data.flags & (1 << 2)
|
||||||
ext_uv = auth_uv.extension_results
|
ext_uv = auth_uv.client_extension_results
|
||||||
assert ext_uv
|
assert ext_uv
|
||||||
assert "hmacGetSecret" in 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
|
# Now see if the hmac-secrets are different
|
||||||
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ def test_minpin(SetMinPin, MCMinPin):
|
|||||||
|
|
||||||
def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin):
|
def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin):
|
||||||
assert not MCMinPin.auth_data.extensions
|
assert not MCMinPin.auth_data.extensions
|
||||||
assert "minPinLength" not in MCMinPin.auth_data.extensions
|
#assert "minPinLength" not in MCMinPin.auth_data.extensions
|
||||||
|
|
||||||
def test_setminpin(device, SetMinPin, MCMinPin):
|
def test_setminpin(device, SetMinPin, MCMinPin):
|
||||||
cfg = FidoConfig(device)
|
cfg = FidoConfig(device)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
|
|||||||
res = device.doGA(ctap1=False, allow_list=[
|
res = device.doGA(ctap1=False, allow_list=[
|
||||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"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
|
# Test FIDO2 register works with U2F auth
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ INS_PUT = 0x01
|
|||||||
INS_DELETE = 0x02
|
INS_DELETE = 0x02
|
||||||
INS_SET_CODE = 0x03
|
INS_SET_CODE = 0x03
|
||||||
INS_RESET = 0x04
|
INS_RESET = 0x04
|
||||||
|
INS_RENAME = 0x05
|
||||||
INS_LIST = 0xa1
|
INS_LIST = 0xa1
|
||||||
INS_CALCULATE = 0xa2
|
INS_CALCULATE = 0xa2
|
||||||
INS_VALIDATE = 0xa3
|
INS_VALIDATE = 0xa3
|
||||||
@@ -89,6 +90,24 @@ def test_life(reset_oath):
|
|||||||
resp = list_apdu(reset_oath)
|
resp = list_apdu(reset_oath)
|
||||||
assert(len(resp) == 0)
|
assert(len(resp) == 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_prefix_extension(reset_oath):
|
||||||
|
old_name = b"30/test"
|
||||||
|
new_name = b"30/test2"
|
||||||
|
key = list(bytes(b"foo bar"))
|
||||||
|
|
||||||
|
put_data = [TAG_NAME, len(old_name)] + list(old_name)
|
||||||
|
put_data += [TAG_KEY, len(key) + 2, TYPE_TOTP | ALG_SHA1, 6] + key
|
||||||
|
send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=put_data)
|
||||||
|
|
||||||
|
rename_data = [TAG_NAME, len(old_name)] + list(old_name)
|
||||||
|
rename_data += [TAG_NAME, len(new_name)] + list(new_name)
|
||||||
|
send_apdu(reset_oath, INS_RENAME, p1=0, p2=0, data=rename_data)
|
||||||
|
|
||||||
|
resp = list_apdu(reset_oath)
|
||||||
|
exp = [TAG_NAME_LIST, len(new_name) + 1, TYPE_TOTP | ALG_SHA1] + list(new_name)
|
||||||
|
assert resp == exp
|
||||||
|
|
||||||
def test_overwrite(reset_oath):
|
def test_overwrite(reset_oath):
|
||||||
data = data_name + data_key
|
data = data_name + data_key
|
||||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
||||||
@@ -123,7 +142,7 @@ def test_auth(reset_oath):
|
|||||||
resp = list_apdu(reset_oath)
|
resp = list_apdu(reset_oath)
|
||||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
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)
|
resp = send_apdu(reset_oath, 0xA4, 0x04, 0x00, aid)
|
||||||
assert(resp[15] == TAG_CHALLENGE)
|
assert(resp[15] == TAG_CHALLENGE)
|
||||||
assert(resp[16] == 8)
|
assert(resp[16] == 8)
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
/usr/sbin/pcscd &
|
/usr/sbin/pcscd &
|
||||||
sleep 2
|
sleep 2
|
||||||
rm -f memory.flash
|
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 &
|
./build_in_docker/pico_fido > /dev/null &
|
||||||
pytest tests
|
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()
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import sys
|
|
||||||
import keyring
|
|
||||||
|
|
||||||
DOMAIN = "PicoKeys.com"
|
|
||||||
USERNAME = "Pico-Fido"
|
|
||||||
|
|
||||||
try:
|
|
||||||
import keyring
|
|
||||||
from keyrings.osx_keychain_keys.backend import OSXKeychainKeysBackend, OSXKeychainKeyType, OSXKeyChainKeyClassType
|
|
||||||
except:
|
|
||||||
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
|
|
||||||
except:
|
|
||||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
def get_backend(use_secure_enclave=False):
|
|
||||||
backend = OSXKeychainKeysBackend(
|
|
||||||
key_type=OSXKeychainKeyType.EC, # Key type, e.g. RSA, RC, DSA, ...
|
|
||||||
key_class_type=OSXKeyChainKeyClassType.Private, # Private key, Public key, Symmetric-key
|
|
||||||
key_size_in_bits=256,
|
|
||||||
is_permanent=True, # If set, saves the key in keychain; else, returns a transient key
|
|
||||||
use_secure_enclave=use_secure_enclave, # Saves the key in the T2 (TPM) chip, requires a code-signed interpreter
|
|
||||||
access_group=None, # Limits key management and retrieval to set group, requires a code-signed interpreter
|
|
||||||
is_extractable=True # If set, private key is extractable; else, it can't be retrieved, but only operated against
|
|
||||||
)
|
|
||||||
return backend
|
|
||||||
|
|
||||||
def generate_secure_key(use_secure_enclave=False):
|
|
||||||
backend = get_backend(use_secure_enclave)
|
|
||||||
backend.set_password(DOMAIN, USERNAME, password=None)
|
|
||||||
return backend.get_password(DOMAIN, USERNAME)
|
|
||||||
|
|
||||||
def get_d(key):
|
|
||||||
return key.private_numbers().private_value.to_bytes(32, 'big')
|
|
||||||
|
|
||||||
def set_secure_key(pk):
|
|
||||||
backend = get_backend(False)
|
|
||||||
try:
|
|
||||||
backend.delete_password(DOMAIN, USERNAME)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
|
|
||||||
|
|
||||||
def get_secure_key():
|
|
||||||
key = None
|
|
||||||
try:
|
|
||||||
backend = get_backend(False)
|
|
||||||
key = backend.get_password(DOMAIN, USERNAME)[0]
|
|
||||||
if (key is None):
|
|
||||||
raise TypeError
|
|
||||||
except (keyring.errors.KeyringError, TypeError):
|
|
||||||
try:
|
|
||||||
key = generate_secure_key(False)[0] # It should be True, but secure enclave causes python segfault
|
|
||||||
except keyring.errors.PasswordSetError:
|
|
||||||
key = generate_secure_key(False)[0]
|
|
||||||
return get_d(key)
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
DOMAIN = "PicoKeys.com"
|
|
||||||
USERNAME = "Pico-Fido"
|
|
||||||
|
|
||||||
try:
|
|
||||||
import keyring
|
|
||||||
except:
|
|
||||||
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyring`')
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
|
|
||||||
from cryptography.hazmat.primitives.asymmetric import ec
|
|
||||||
except:
|
|
||||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_secure_key():
|
|
||||||
pkey = ec.generate_private_key(ec.SECP256R1())
|
|
||||||
set_secure_key(pkey)
|
|
||||||
return keyring.get_password(DOMAIN, USERNAME)
|
|
||||||
|
|
||||||
def get_d(key):
|
|
||||||
return load_pem_private_key(key, password=None).private_numbers().private_value.to_bytes(32, 'big')
|
|
||||||
|
|
||||||
def set_secure_key(pk):
|
|
||||||
try:
|
|
||||||
keyring.delete_password(DOMAIN, USERNAME)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
keyring.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode())
|
|
||||||
|
|
||||||
def get_secure_key():
|
|
||||||
key = None
|
|
||||||
try:
|
|
||||||
key = keyring.get_password(DOMAIN, USERNAME)
|
|
||||||
if (key is None):
|
|
||||||
raise TypeError
|
|
||||||
except (keyring.errors.KeyringError, TypeError):
|
|
||||||
key = generate_secure_key()
|
|
||||||
return get_d(key.encode())
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -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
|
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
|
git clone --recursive https://github.com/espressif/esp-idf.git
|
||||||
cd esp-idf
|
cd esp-idf
|
||||||
|
git checkout tags/v5.5
|
||||||
./install.sh esp32s3
|
./install.sh esp32s3
|
||||||
. ./export.sh
|
. ./export.sh
|
||||||
cd ..
|
cd ..
|
||||||
@@ -48,6 +49,7 @@ cd build
|
|||||||
esptool.py --chip ESP32-S2 merge_bin -o ../release/pico_fido_esp32-s2.bin @flash_args
|
esptool.py --chip ESP32-S2 merge_bin -o ../release/pico_fido_esp32-s2.bin @flash_args
|
||||||
cd ..
|
cd ..
|
||||||
else
|
else
|
||||||
|
sudo apt install -y libtss2-dev tpm2-tools swtpm cmake
|
||||||
mkdir build
|
mkdir build
|
||||||
cd build
|
cd build
|
||||||
cmake -DENABLE_EMULATION=1 ..
|
cmake -DENABLE_EMULATION=1 ..
|
||||||
|
|||||||
Reference in New Issue
Block a user