Compare commits
472 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6265992162 | ||
|
|
63b7b9b8d2 | ||
|
|
8db06bf3ac | ||
|
|
77dd1c4b98 | ||
|
|
6a67800057 | ||
|
|
a70e259a90 | ||
|
|
7800056597 | ||
|
|
eeecf513cb | ||
|
|
9b0b584c14 | ||
|
|
1c45685926 | ||
|
|
cff544b485 | ||
|
|
1f805b1df2 | ||
|
|
1d20321d69 | ||
|
|
b42a664ac6 | ||
|
|
2d356a315e | ||
|
|
9bfbc45f84 | ||
|
|
a5a0f3508c | ||
|
|
9c9074c1ef | ||
|
|
022503fdc0 | ||
|
|
dba805dc04 | ||
|
|
bbf474811b | ||
|
|
2eca08161d | ||
|
|
46ada2c1f7 | ||
|
|
5faab169a8 | ||
|
|
3148649f86 | ||
|
|
3c40706aae | ||
|
|
4a64c11740 | ||
|
|
2319abe44e | ||
|
|
a5fe9b5d47 | ||
|
|
d5af2cd8ed | ||
|
|
e994078790 | ||
|
|
d99bcc90ec | ||
|
|
7a59b51849 | ||
|
|
10c58b4be7 | ||
|
|
730e76af75 | ||
|
|
ee80462a4a | ||
|
|
4ecb325e07 | ||
|
|
646b423fe4 | ||
|
|
77c3568885 | ||
|
|
3b43c5112b | ||
|
|
244c18fb51 | ||
|
|
78604f820d | ||
|
|
a68fbd65e9 | ||
|
|
bc0e022d85 | ||
|
|
3fad6baf89 | ||
|
|
df2977e6ad | ||
|
|
1fbf3da4f5 | ||
|
|
4ce6b2df5c | ||
|
|
e5910b1cba | ||
|
|
0df1330f92 | ||
|
|
3ce8496faa | ||
|
|
ef49560d0a | ||
|
|
53ed3a46c4 | ||
|
|
dc07653ae7 | ||
|
|
2d09a5c8e5 | ||
|
|
720c2e45f3 | ||
|
|
aeea3c7183 | ||
|
|
8838ac9e54 | ||
|
|
623db840d3 | ||
|
|
e2b06b908e | ||
|
|
b9e791ca90 | ||
|
|
ed560f10a4 | ||
|
|
1f839c5f99 | ||
|
|
effb8e4063 | ||
|
|
b2e45b0f7f | ||
|
|
24521dff4b | ||
|
|
7bc4a70319 | ||
|
|
cbef14beec | ||
|
|
0e54998d58 | ||
|
|
2e16036bb5 | ||
|
|
f98df743f9 | ||
|
|
4fe1c0804c | ||
|
|
7071949a1f | ||
|
|
e07b5194e3 | ||
|
|
e05115ffac | ||
|
|
38eca2fdd4 | ||
|
|
f276e99342 | ||
|
|
6f517e8fca | ||
|
|
39e2ff40c3 | ||
|
|
ffbe3fcbad | ||
|
|
cf5dbc9ae5 | ||
|
|
2fca44540a | ||
|
|
ec612a451d | ||
|
|
c43006f8c2 | ||
|
|
95cae29206 | ||
|
|
11c28adbb0 | ||
|
|
661442956d | ||
|
|
778c6b038a | ||
|
|
de1c50db4f | ||
|
|
c1e985c9af | ||
|
|
4f787eaaba | ||
|
|
b77277b72e | ||
|
|
02556fcde1 | ||
|
|
f234b0dc26 | ||
|
|
8ba9116454 | ||
|
|
5a31405244 | ||
|
|
902a988350 | ||
|
|
6256a9547d | ||
|
|
5568aa7b69 | ||
|
|
5e86745672 | ||
|
|
cffa8e29ff | ||
|
|
6c74db9763 | ||
|
|
dac6407134 | ||
|
|
f49833291f | ||
|
|
8c1e002892 | ||
|
|
8d49ed5ffc | ||
|
|
a0d9ad7a3a | ||
|
|
910fb66f3c | ||
|
|
ed12d6f8e9 | ||
|
|
a9799dc77f | ||
|
|
d7d75caecf | ||
|
|
af4eb075c7 | ||
|
|
0c5280e12a | ||
|
|
163e936231 | ||
|
|
1b4dd9bed0 | ||
|
|
5b95e35ca9 | ||
|
|
69ec242095 | ||
|
|
6eb6cd35d0 | ||
|
|
f21e203093 | ||
|
|
e96da09a84 | ||
|
|
6fe16a63e4 | ||
|
|
d5fe405a87 | ||
|
|
54bbc0e9ea | ||
|
|
b0b0187919 | ||
|
|
1f0e1fb8f4 | ||
|
|
f3f34cf66b | ||
|
|
82ed96b2e2 | ||
|
|
92d04f9131 | ||
|
|
7a71bf48fc | ||
|
|
7e2ecdbc56 | ||
|
|
e54df525c4 | ||
|
|
1d9107d4bb | ||
|
|
a9be759da3 | ||
|
|
34bfc3b2ef | ||
|
|
d985cf6301 | ||
|
|
0b00e01187 | ||
|
|
bef1922c8f | ||
|
|
107e5c34db | ||
|
|
6157a91fdf | ||
|
|
779db90713 | ||
|
|
a0f1d2334d | ||
|
|
5c6f87ab8f | ||
|
|
cf152c1692 | ||
|
|
04238509ee | ||
|
|
85298062cd | ||
|
|
0464ad8964 | ||
|
|
19197e54a8 | ||
|
|
01a6c9f77f | ||
|
|
ba57cc4527 | ||
|
|
68b5614fb9 | ||
|
|
4fd4d75e21 | ||
|
|
599fd706ce | ||
|
|
28e979939a | ||
|
|
849221fd95 | ||
|
|
011429a982 | ||
|
|
b99181a00c | ||
|
|
041bb788f9 | ||
|
|
20a8ef08f0 | ||
|
|
e757ad2945 | ||
|
|
1ce0d98c34 | ||
|
|
96de6efed6 | ||
|
|
195096ad52 | ||
|
|
1ee86f8634 | ||
|
|
2b9a5829e5 | ||
|
|
8056e64cab | ||
|
|
ffb3beb84a | ||
|
|
d78d9d10aa | ||
|
|
f8d4f1d02e | ||
|
|
b493a81ddc | ||
|
|
5c20909b03 | ||
|
|
27b9e3954a | ||
|
|
440ec5c854 | ||
|
|
cb2744cab3 | ||
|
|
5db1014850 | ||
|
|
421bea6421 | ||
|
|
65039c0959 | ||
|
|
8e36b4c379 | ||
|
|
3652368542 | ||
|
|
e5d1ef29a4 | ||
|
|
0fd36806cc | ||
|
|
7bf26b28fc | ||
|
|
da94a82487 | ||
|
|
c24be5a631 | ||
|
|
46ce9390bf | ||
|
|
c1fd5736f9 | ||
|
|
b1c4ff877e | ||
|
|
6c85d57412 | ||
|
|
2e5b8f4c71 | ||
|
|
a9697ba4e0 | ||
|
|
aec488f070 | ||
|
|
9c90095e96 | ||
|
|
7c5f2cee4b | ||
|
|
5e0c42a9f9 | ||
|
|
da7b918dc4 | ||
|
|
cfcfb941e0 | ||
|
|
45c2cf65fe | ||
|
|
1217d82361 | ||
|
|
332debea6d | ||
|
|
bafede2ae5 | ||
|
|
539420b996 | ||
|
|
0c08590dcc | ||
|
|
cac4ae1751 | ||
|
|
974868d8e4 | ||
|
|
2bbaf7c274 | ||
|
|
df26040838 | ||
|
|
539ea61436 | ||
|
|
75771e5e46 | ||
|
|
8e26ec8bcd | ||
|
|
26148282e6 | ||
|
|
05044b498d | ||
|
|
be44947475 | ||
|
|
0d280ca252 | ||
|
|
4c3042a8bf | ||
|
|
b7ceec8d49 | ||
|
|
63e15b19bb | ||
|
|
b2c4e0e1c1 | ||
|
|
b72c596aa6 | ||
|
|
2d81a3c472 | ||
|
|
bb20dd7a53 | ||
|
|
c258dad8e6 | ||
|
|
ce040a79f5 | ||
|
|
8ffd1bfe38 | ||
|
|
5105545df0 | ||
|
|
d011314500 | ||
|
|
51cbfe5fe9 | ||
|
|
aa7362f88f | ||
|
|
2b1227b105 | ||
|
|
a79842b33f | ||
|
|
30f51b8453 | ||
|
|
c00c83dfe6 | ||
|
|
cda97259b3 | ||
|
|
c883083a75 | ||
|
|
016780b3de | ||
|
|
24224b78dd | ||
|
|
07729f807b | ||
|
|
e0c793dd0a | ||
|
|
9d6003d1e5 | ||
|
|
147a93d7fb | ||
|
|
f12c55805c | ||
|
|
7e10e25f96 | ||
|
|
9052c66a7f | ||
|
|
443ca69547 | ||
|
|
415c1b2e9c | ||
|
|
d87c1530c7 | ||
|
|
f90baaf095 | ||
|
|
1d7bdb0861 | ||
|
|
fa811e2a0f | ||
|
|
ff498ebfdf | ||
|
|
cceb735cc0 | ||
|
|
5a9de32e02 | ||
|
|
c9eacc4a3d | ||
|
|
c23d92ea28 | ||
|
|
da04fbb824 | ||
|
|
0bfa760903 | ||
|
|
bd9d4286d5 | ||
|
|
3d1c68fa40 | ||
|
|
26ac66e813 | ||
|
|
05afcd706e | ||
|
|
8c90dd55bd | ||
|
|
c6c1d0c6eb | ||
|
|
d4ed55b5a5 | ||
|
|
cfb0b8f3f2 | ||
|
|
eca8656bd9 | ||
|
|
5b5a9fc0fe | ||
|
|
59ec9b75fc | ||
|
|
8b2be54ede | ||
|
|
483073ebb8 | ||
|
|
12250a1c31 | ||
|
|
3f8aed1ecf | ||
|
|
707af84292 | ||
|
|
717e7135d5 | ||
|
|
fc909fa93d | ||
|
|
03f29f5be6 | ||
|
|
267e66eaee | ||
|
|
22317d4322 | ||
|
|
376b49db95 | ||
|
|
9756b451bb | ||
|
|
dcdf605a5e | ||
|
|
6d9208f434 | ||
|
|
9a493b9972 | ||
|
|
ef993d0f7b | ||
|
|
5360d62062 | ||
|
|
6d123e2a0f | ||
|
|
379f136699 | ||
|
|
5e0b0bfe38 | ||
|
|
f3decd8649 | ||
|
|
4f33d999e3 | ||
|
|
46661ee808 | ||
|
|
b1fdb9b1d1 | ||
|
|
b3b2e98ec1 | ||
|
|
cdf96e3564 | ||
|
|
4fe29750f2 | ||
|
|
87bdea7e28 | ||
|
|
cecb5da3a0 | ||
|
|
49cf031037 | ||
|
|
3a2ab27567 | ||
|
|
601e1bcda7 | ||
|
|
dc6f25caf3 | ||
|
|
20345ebd10 | ||
|
|
6674a0bb1e | ||
|
|
c45c70d95d | ||
|
|
061b5e919e | ||
|
|
0dc547dbe5 | ||
|
|
c383f6c446 | ||
|
|
c3d6d4c657 | ||
|
|
e387033266 | ||
|
|
97336bf8d4 | ||
|
|
6ebaa05523 | ||
|
|
00f30ba00c | ||
|
|
363ad1c9e2 | ||
|
|
94806f9bf0 | ||
|
|
03b35cfe88 | ||
|
|
bc9bbaf292 | ||
|
|
e5ca759dea | ||
|
|
f780b2a0b4 | ||
|
|
b9f1adf211 | ||
|
|
880a1b003d | ||
|
|
545860ccbc | ||
|
|
9ac5200792 | ||
|
|
7542fad31c | ||
|
|
fe95093484 | ||
|
|
483a0931a6 | ||
|
|
6644b500fa | ||
|
|
c8775ec69f | ||
|
|
9160bbb8fe | ||
|
|
19dd52f944 | ||
|
|
4c724d0e8b | ||
|
|
81d3da2645 | ||
|
|
9bcfacfe08 | ||
|
|
641c2fb880 | ||
|
|
cb24927a80 | ||
|
|
4ddd45f16e | ||
|
|
f39a51afca | ||
|
|
aa4255b875 | ||
|
|
a4d82136c2 | ||
|
|
4a3f957fdf | ||
|
|
24b66dcffc | ||
|
|
703e4697ec | ||
|
|
839562130a | ||
|
|
e87ae34ab5 | ||
|
|
2431812a18 | ||
|
|
81717135f5 | ||
|
|
1d1d8ce5c3 | ||
|
|
6030f33977 | ||
|
|
360b8eadaa | ||
|
|
0d51d3c727 | ||
|
|
d0924f5ecc | ||
|
|
866d69a82d | ||
|
|
00ba0db87a | ||
|
|
c5644d14b0 | ||
|
|
2cf211cbd0 | ||
|
|
50418113a9 | ||
|
|
3a92238c0c | ||
|
|
270a54f3b7 | ||
|
|
a8364c281b | ||
|
|
5dcf89cd66 | ||
|
|
5c7be811e8 | ||
|
|
1707430593 | ||
|
|
a151dc72e4 | ||
|
|
315f01372e | ||
|
|
860cca53e0 | ||
|
|
d7016f6065 | ||
|
|
8e9eafaec5 | ||
|
|
1376c51528 | ||
|
|
fa5926a3cc | ||
|
|
710e03f5a6 | ||
|
|
9d79505c5a | ||
|
|
4cb0af5045 | ||
|
|
196430517f | ||
|
|
083f9bc787 | ||
|
|
eb66ec3064 | ||
|
|
e5834ff7c4 | ||
|
|
3f1aba889e | ||
|
|
58fbea8929 | ||
|
|
8bf53a6497 | ||
|
|
c89b044825 | ||
|
|
004073c3dd | ||
|
|
bae8450a8d | ||
|
|
a355f87f82 | ||
|
|
b023668788 | ||
|
|
1b6d1e4b7f | ||
|
|
3bea6adf7a | ||
|
|
54c2df3570 | ||
|
|
ae42e28384 | ||
|
|
71c0e865dc | ||
|
|
052ff2d60a | ||
|
|
8b70c864a4 | ||
|
|
765db0e98b | ||
|
|
6b2e95deb0 | ||
|
|
d45fa9aae0 | ||
|
|
23c7e16e6e | ||
|
|
5923f435fe | ||
|
|
04868f2d7b | ||
|
|
54c0769dbd | ||
|
|
0bbcba2f60 | ||
|
|
723648173d | ||
|
|
e6c128fe0d | ||
|
|
2174b516c3 | ||
|
|
4577e4430c | ||
|
|
9a8f4c0f4d | ||
|
|
e21d985344 | ||
|
|
43cd8869f9 | ||
|
|
a42131876f | ||
|
|
e1f4e3035d | ||
|
|
71ecb23af6 | ||
|
|
8c21a2bbcd | ||
|
|
53cc16ab6d | ||
|
|
f213854f8b | ||
|
|
2c125e76eb | ||
|
|
19d8f16056 | ||
|
|
40065217fd | ||
|
|
32c938674a | ||
|
|
4425722a71 | ||
|
|
69eef7651c | ||
|
|
7f97ea4f24 | ||
|
|
467523769e | ||
|
|
2d295d0d98 | ||
|
|
0758644583 | ||
|
|
c3a5b8e708 | ||
|
|
b134d261ae | ||
|
|
4f93b984cd | ||
|
|
ea0547ef49 | ||
|
|
e5b7dff8cc | ||
|
|
6a077d0d8f | ||
|
|
7c271fc4f3 | ||
|
|
2734259c02 | ||
|
|
ba4faa9840 | ||
|
|
746c324113 | ||
|
|
ac224063fc | ||
|
|
cf4778b9ad | ||
|
|
3f80acc81b | ||
|
|
4cea6ebe87 | ||
|
|
02e5eb8dba | ||
|
|
037019b348 | ||
|
|
ae237db9ca | ||
|
|
c2e16fda41 | ||
|
|
f84d36b1da | ||
|
|
04aaf0f572 | ||
|
|
577edbb62f | ||
|
|
40b5f70761 | ||
|
|
a294840425 | ||
|
|
d786a9c6e5 | ||
|
|
b87eb3f278 | ||
|
|
6f226001df | ||
|
|
6e91694253 | ||
|
|
0c5b308aef | ||
|
|
3fc41a12a7 | ||
|
|
8ad8c82baf | ||
|
|
85818d009c | ||
|
|
bb069c5651 | ||
|
|
e3b036456f | ||
|
|
47ea749454 | ||
|
|
cb4827688b | ||
|
|
d43b6caf16 | ||
|
|
7534d7bb76 | ||
|
|
cc8d9e0741 | ||
|
|
4e94cbe40e | ||
|
|
b1b9dad9f5 | ||
|
|
0c51160d23 | ||
|
|
958a20ce11 | ||
|
|
1e7d711c03 | ||
|
|
cc0a181f75 | ||
|
|
b8568d834a | ||
|
|
174241c0a0 | ||
|
|
fa17d5c906 | ||
|
|
7a4be766bc | ||
|
|
1835afe54a | ||
|
|
ad07052e6a | ||
|
|
da577b8e8d | ||
|
|
0ec563c8de | ||
|
|
d4b7bfd6cc | ||
|
|
995870d77e |
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: polhenarejos
|
||||
custom: ["https://www.paypal.me/polhenarejos"]
|
||||
20
.github/workflows/codeql.yml
vendored
20
.github/workflows/codeql.yml
vendored
@@ -13,12 +13,13 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
branches: [ "main", "development" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
branches: [ "main", "development" ]
|
||||
schedule:
|
||||
- cron: '23 5 * * 4'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -32,9 +33,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'cpp' ]
|
||||
language: [ 'cpp', 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
mode: [ 'pico', 'esp32', 'local' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
@@ -42,17 +44,17 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
# - name: Autobuild
|
||||
@@ -61,12 +63,12 @@ jobs:
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
- run: |
|
||||
echo "Run, Build Application using script"
|
||||
./workflows/autobuild.sh
|
||||
./workflows/autobuild.sh ${{ matrix.mode }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
35
.github/workflows/nightly.yml
vendored
Normal file
35
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: "Nightly deploy"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
nightly:
|
||||
name: Deploy nightly
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
refs: [main, development]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ matrix.refs }}
|
||||
submodules: 'recursive'
|
||||
- name : Build
|
||||
env:
|
||||
PICO_SDK_PATH: ../pico-sdk
|
||||
run: |
|
||||
./workflows/autobuild.sh pico
|
||||
./build_pico_fido.sh
|
||||
./workflows/autobuild.sh esp32
|
||||
- name: Update nightly release
|
||||
uses: pyTooling/Actions/releaser@main
|
||||
with:
|
||||
tag: nightly-${{ matrix.refs }}
|
||||
rm: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
files: release/*.*
|
||||
37
.github/workflows/test.yml
vendored
Normal file
37
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "Emulation and test"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "development" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main", "development" ]
|
||||
schedule:
|
||||
- cron: '23 5 * * 4'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository and submodules
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build in container
|
||||
run: ./tests/build-in-docker.sh
|
||||
- name: Start emulation and test
|
||||
run: ./tests/run-test-in-docker.sh
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "pico-hsm-sdk"]
|
||||
path = pico-hsm-sdk
|
||||
url = ../pico-hsm-sdk
|
||||
[submodule "pico-keys-sdk"]
|
||||
path = pico-keys-sdk
|
||||
url = https://github.com/polhenarejos/pico-keys-sdk
|
||||
|
||||
114
CMakeLists.txt
114
CMakeLists.txt
@@ -17,38 +17,69 @@
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
if(ESP_PLATFORM)
|
||||
set(DEBUG_APDU 1)
|
||||
set(DENABLE_POWER_ON_RESET 0)
|
||||
set(EXTRA_COMPONENT_DIRS src pico-keys-sdk/src)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
else()
|
||||
|
||||
if(ENABLE_EMULATION)
|
||||
else()
|
||||
include(pico_sdk_import.cmake)
|
||||
endif()
|
||||
|
||||
project(pico_fido C CXX ASM)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
|
||||
if(ENABLE_EMULATION)
|
||||
else()
|
||||
pico_sdk_init()
|
||||
endif()
|
||||
|
||||
add_executable(pico_fido)
|
||||
|
||||
option(ENABLE_UP_BUTTON "Enable/disable user presence button" ON)
|
||||
if(ENABLE_UP_BUTTON)
|
||||
add_definitions(-DENABLE_UP_BUTTON=1)
|
||||
message("Enabling user presence with button")
|
||||
else()
|
||||
add_definitions(-DENABLE_UP_BUTTON=0)
|
||||
message("Disabling user presence with button")
|
||||
endif(ENABLE_UP_BUTTON)
|
||||
endif()
|
||||
|
||||
option(ENABLE_POWER_ON_RESET "Enable/disable power cycle on reset" ON)
|
||||
if(ENABLE_POWER_ON_RESET)
|
||||
add_definitions(-DENABLE_POWER_ON_RESET=1)
|
||||
message("Enabling power cycle on reset")
|
||||
message(STATUS "Power cycle on reset: \t enabled")
|
||||
else()
|
||||
add_definitions(-DENABLE_POWER_ON_RESET=0)
|
||||
message("Disabling power cycle on reset")
|
||||
message(STATUS "Power cycle on reset: \t disabled")
|
||||
endif(ENABLE_POWER_ON_RESET)
|
||||
|
||||
target_sources(pico_fido PUBLIC
|
||||
option(ENABLE_OATH_APP "Enable/disable OATH application" ON)
|
||||
if(ENABLE_OATH_APP)
|
||||
add_definitions(-DENABLE_OATH_APP=1)
|
||||
message(STATUS "OATH Application: \t\t enabled")
|
||||
else()
|
||||
add_definitions(-DENABLE_OATH_APP=0)
|
||||
message(STATUS "OATH Application: \t\t disabled")
|
||||
endif(ENABLE_OATH_APP)
|
||||
|
||||
option(ENABLE_OTP_APP "Enable/disable OTP application" ON)
|
||||
if(ENABLE_OTP_APP)
|
||||
add_definitions(-DENABLE_OTP_APP=1)
|
||||
message(STATUS "OTP Application: \t\t enabled")
|
||||
else()
|
||||
add_definitions(-DENABLE_OTP_APP=0)
|
||||
message(STATUS "OTP Application: \t\t disabled")
|
||||
endif(ENABLE_OTP_APP)
|
||||
|
||||
if(ENABLE_OTP_APP OR ENABLE_OATH_APP)
|
||||
set(USB_ITF_CCID 1)
|
||||
set(USB_ITF_WCID 1)
|
||||
else()
|
||||
set(USB_ITF_CCID 0)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/fido.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/files.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/kek.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_register.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_authenticate.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cmd_version.c
|
||||
@@ -60,25 +91,72 @@ target_sources(pico_fido PUBLIC
|
||||
${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
|
||||
)
|
||||
set(HSM_DRIVER "hid")
|
||||
include(pico-hsm-sdk/pico_hsm_sdk_import.cmake)
|
||||
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()
|
||||
|
||||
target_include_directories(pico_fido PUBLIC
|
||||
set(USB_ITF_HID 1)
|
||||
include(pico-keys-sdk/pico_keys_sdk_import.cmake)
|
||||
SET_VERSION(ver_major ver_minor "${CMAKE_CURRENT_LIST_DIR}/src/fido/version.h" 1)
|
||||
if(ESP_PLATFORM)
|
||||
project(pico_fido)
|
||||
endif()
|
||||
set(INCLUDES ${INCLUDES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido
|
||||
)
|
||||
if(NOT ESP_PLATFORM)
|
||||
target_sources(pico_fido PUBLIC ${SOURCES})
|
||||
target_include_directories(pico_fido PUBLIC ${INCLUDES})
|
||||
|
||||
target_compile_options(pico_fido PUBLIC
|
||||
-Wall
|
||||
)
|
||||
if (NOT MSVC)
|
||||
target_compile_options(pico_fido PUBLIC
|
||||
-Werror
|
||||
)
|
||||
|
||||
string(FIND ${CMAKE_C_COMPILER} ":" COMPILER_COLON)
|
||||
if (${COMPILER_COLON} GREATER_EQUAL 0)
|
||||
target_compile_options(pico_fido PUBLIC
|
||||
-Wno-error=use-after-free
|
||||
)
|
||||
endif()
|
||||
endif(NOT MSVC)
|
||||
|
||||
pico_add_extra_outputs(pico_fido)
|
||||
|
||||
target_link_libraries(pico_fido PRIVATE pico_hsm_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id hardware_rtc tinyusb_device tinyusb_board)
|
||||
if(ENABLE_EMULATION)
|
||||
if(NOT MSVC)
|
||||
target_compile_options(pico_fido PUBLIC
|
||||
-fdata-sections
|
||||
-ffunction-sections
|
||||
)
|
||||
endif(NOT MSVC)
|
||||
if(APPLE)
|
||||
target_link_options(pico_fido PUBLIC
|
||||
-Wl,-dead_strip
|
||||
)
|
||||
else()
|
||||
target_link_options(pico_fido PUBLIC
|
||||
-Wl,--gc-sections
|
||||
)
|
||||
endif (APPLE)
|
||||
target_link_libraries(pico_fido PRIVATE pthread m)
|
||||
else()
|
||||
target_link_libraries(pico_fido PRIVATE pico_keys_sdk pico_stdlib pico_multicore hardware_flash hardware_sync hardware_adc pico_unique_id pico_aon_timer tinyusb_device tinyusb_board)
|
||||
pico_add_extra_outputs(${CMAKE_PROJECT_NAME})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
110
README.md
110
README.md
@@ -1,8 +1,8 @@
|
||||
# Pico FIDO
|
||||
This project aims at transforming your Raspberry Pico into a FIDO key integrated. The Pico works as a FIDO key, like a normal USB key for authentication.
|
||||
This project transforms your Raspberry Pi Pico or ESP32 microcontroller into an integrated FIDO Passkey, functioning like a standard USB Passkey for authentication.
|
||||
|
||||
## Features
|
||||
Pico FIDO has implemented the following features:
|
||||
Pico FIDO includes the following features:
|
||||
|
||||
- CTAP 2.1 / CTAP 1
|
||||
- WebAuthn
|
||||
@@ -10,38 +10,88 @@ Pico FIDO has implemented the following features:
|
||||
- HMAC-Secret extension
|
||||
- CredProtect extension
|
||||
- User presence enforcement through physical button
|
||||
- User Verification with PIN
|
||||
- Discoverable credentials
|
||||
- User verification with PIN
|
||||
- Discoverable credentials (resident keys)
|
||||
- Credential management
|
||||
- ECDSA authentication
|
||||
- Support for SECP256R1, SECP384R1, SECP521R1, and SECP256K1 curves
|
||||
- App registration and login
|
||||
- Device selection
|
||||
- Support for vendor configuration
|
||||
- Backup with 24 words
|
||||
- Secure lock to protect the device from flash dumps
|
||||
- Permissions support (MC, GA, CM, ACFG, LBW)
|
||||
- Authenticator configuration
|
||||
- minPinLength extension
|
||||
- Self attestation
|
||||
- Enterprise attestation
|
||||
- credBlobs extension
|
||||
- largeBlobKey extension
|
||||
- Large blobs support (2048 bytes max)
|
||||
- OATH (based on YKOATH protocol specification)
|
||||
- TOTP / HOTP
|
||||
- Yubikey One Time Password
|
||||
- Challenge-response generation
|
||||
- Emulated keyboard interface
|
||||
- Button press generates an OTP that is directly typed
|
||||
- Yubico YKMAN compatible
|
||||
- Nitrokey nitropy and nitroapp compatible
|
||||
- Secure Boot and Secure Lock in RP2350 and ESP32-S3 MCUs
|
||||
- One Time Programming to store the master key that encrypts all resident keys and seeds.
|
||||
- Rescue interface to allow recovery of the device if it becomes unresponsive or undetectable.
|
||||
- LED customization with Pico Commissioner.
|
||||
|
||||
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
|
||||
All features comply with the specifications. If you encounter unexpected behavior or deviations from the specifications, please open an issue.
|
||||
|
||||
## Security considerations
|
||||
Pico FIDO is an open platform so be careful. The contents in the flash memory may be easily dumpled and obtain the private/master keys. Therefore, it is not possible to encrypt the content. At least, one key (the master, the supreme key) must be stored in clear text.
|
||||
## Security Considerations
|
||||
Microcontrollers RP2350 and ESP32-S3 are designed to support secure environments when Secure Boot is enabled, and optionally, Secure Lock. These features allow a master key encryption key (MKEK) to be stored in a one-time programmable (OTP) memory region, which is inaccessible from outside secure code. This master key is then used to encrypt all private and secret keys on the device, protecting sensitive data from potential flash memory dumps.
|
||||
|
||||
If the Pico is stolen the contents of private and secret keys can be read.
|
||||
**However**, the RP2040 microcontroller lacks this level of security hardware, meaning that it cannot provide the same protection. Data stored on its flash memory, including private or master keys, can be easily accessed or dumped, as encryption of the master key itself is not feasible. Consequently, if an RP2040 device is stolen, any stored private or secret keys may be exposed.
|
||||
|
||||
## Download
|
||||
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page")) and download the UF2 file for your board.
|
||||
**If you own an ESP32-S3 board, go to [ESP32 Flasher](https://www.picokeys.com/esp32-flasher/) for flashing your Pico FIDO.**
|
||||
|
||||
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the VID/PID patcher as follows: `./pico-fido-patch-vidpid.sh VID:PID input_fido_file.uf2 output_fido_file.uf2`
|
||||
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.
|
||||
|
||||
You can use whatever VID/PID, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
|
||||
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").
|
||||
|
||||
## Build
|
||||
Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive.
|
||||
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.
|
||||
|
||||
git clone https://github.com/polhenarejos/pico-fido
|
||||
cd pico-fido
|
||||
mkdir build
|
||||
cd build
|
||||
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
|
||||
make
|
||||
Note that the pure-browser option [Pico Commissioner](https://www.picokeys.com/pico-commissioner/ "Pico Commissioner") is the most recommended.
|
||||
|
||||
Note that PICO_BOARD, USB_VID and USB_PID are optional. If not provided, pico board and VID/PID FEFF:FCFD will be used.
|
||||
## 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.
|
||||
|
||||
After make ends, the binary file pico_fido.uf2 will be generated. Put your pico board into loading mode, by pushing BOOTSEL button while pluging on, and copy the UF2 to the new fresh usb mass storage Pico device. Once copied, the pico mass storage will be disconnected automatically and the pico board will reset with the new firmware. A blinking led will indicate the device is ready to work.
|
||||
```sh
|
||||
git clone https://github.com/polhenarejos/pico-fido
|
||||
git submodule update --init --recursive
|
||||
cd pico-fido
|
||||
mkdir build
|
||||
cd build
|
||||
PICO_SDK_PATH=/path/to/pico-sdk cmake .. -DPICO_BOARD=board_type -DUSB_VID=0x1234 -DUSB_PID=0x5678
|
||||
make
|
||||
```
|
||||
Note that `PICO_BOARD`, `USB_VID` and `USB_PID` are optional. If not provided, `pico` board and VID/PID `FEFF:FCFD` will be used.
|
||||
|
||||
Additionally, you can pass the `VIDPID=value` parameter to build the firmware with a known VID/PID. The supported values are:
|
||||
|
||||
- `NitroHSM`
|
||||
- `NitroFIDO2`
|
||||
- `NitroStart`
|
||||
- `NitroPro`
|
||||
- `Nitro3`
|
||||
- `Yubikey5`
|
||||
- `YubikeyNeo`
|
||||
- `YubiHSM`
|
||||
- `Gnuk`
|
||||
- `GnuPG`
|
||||
|
||||
After running `make`, the binary file `pico_fido.uf2` will be generated. To load this onto your Pico board:
|
||||
|
||||
1. Put the Pico board into loading mode by holding the `BOOTSEL` button while plugging it in.
|
||||
2. Copy the `pico_fido.uf2` file to the new USB mass storage device that appears.
|
||||
3. Once the file is copied, the Pico mass storage device will automatically disconnect, and the Pico board will reset with the new firmware.
|
||||
4. A blinking LED will indicate that the device is ready to work.
|
||||
|
||||
## Led blink
|
||||
Pico FIDO uses the led to indicate the current status. Four states are available:
|
||||
@@ -67,7 +117,23 @@ While processing, the Pico FIDO is busy and cannot receive additional commands u
|
||||
|
||||
## Driver
|
||||
|
||||
Pico FIDO uses the `HID` driver, present in all OS. It should be detected by all OS and browser/applications, like normal USB FIDO keys.
|
||||
Pico FIDO uses the `HID` driver, which is present in all operating systems. It should be detected by all OS and browser/applications just like normal USB FIDO keys.
|
||||
|
||||
## Tests
|
||||
|
||||
Tests can be found in the `tests` folder. They are based on [FIDO2 tests](https://github.com/solokeys/fido2-tests "FIDO2 tests") from Solokeys but adapted to the [python-fido2](https://github.com/Yubico/python-fido2 "python-fido2") v1.0 package, which is a major refactor from the previous 0.8 version and includes the latest improvements from CTAP 2.1.
|
||||
|
||||
To run all tests, use:
|
||||
|
||||
```sh
|
||||
pytest
|
||||
```
|
||||
|
||||
To run a subset of tests, use the `-k <test>` flag:
|
||||
|
||||
```sh
|
||||
pytest -k test_credprotect
|
||||
```
|
||||
|
||||
## Credits
|
||||
Pico FIDO uses the following libraries or portion of code:
|
||||
|
||||
108
build_pico_fido.sh
Executable file
108
build_pico_fido.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION_MAJOR="6"
|
||||
VERSION_MINOR="2"
|
||||
SUFFIX="${VERSION_MAJOR}.${VERSION_MINOR}"
|
||||
#if ! [[ -z "${GITHUB_SHA}" ]]; then
|
||||
# SUFFIX="${SUFFIX}.${GITHUB_SHA}"
|
||||
#fi
|
||||
|
||||
rm -rf release/*
|
||||
mkdir -p build_release
|
||||
mkdir -p release
|
||||
cd build_release
|
||||
|
||||
for board in 0xcb_helios \
|
||||
adafruit_feather_rp2040_usb_host \
|
||||
adafruit_feather_rp2040 \
|
||||
adafruit_itsybitsy_rp2040 \
|
||||
adafruit_kb2040 \
|
||||
adafruit_macropad_rp2040 \
|
||||
adafruit_qtpy_rp2040 \
|
||||
adafruit_trinkey_qt2040 \
|
||||
amethyst_fpga \
|
||||
archi \
|
||||
arduino_nano_rp2040_connect \
|
||||
cytron_maker_pi_rp2040 \
|
||||
datanoisetv_rp2040_dsp \
|
||||
eetree_gamekit_rp2040 \
|
||||
garatronic_pybstick26_rp2040 \
|
||||
gen4_rp2350_24 \
|
||||
gen4_rp2350_24ct \
|
||||
gen4_rp2350_24t \
|
||||
gen4_rp2350_28 \
|
||||
gen4_rp2350_28ct \
|
||||
gen4_rp2350_28t \
|
||||
gen4_rp2350_32 \
|
||||
gen4_rp2350_32ct \
|
||||
gen4_rp2350_32t \
|
||||
gen4_rp2350_35 \
|
||||
gen4_rp2350_35ct \
|
||||
gen4_rp2350_35t \
|
||||
hellbender_2350A_devboard \
|
||||
ilabs_challenger_rp2350_bconnect \
|
||||
ilabs_challenger_rp2350_wifi_ble \
|
||||
ilabs_opendec02 \
|
||||
melopero_perpetuo_rp2350_lora \
|
||||
melopero_shake_rp2040 \
|
||||
metrotech_xerxes_rp2040 \
|
||||
net8086_usb_interposer \
|
||||
nullbits_bit_c_pro \
|
||||
phyx_rick_tny_rp2350 \
|
||||
pi-plates_micropi \
|
||||
pico \
|
||||
pico_w \
|
||||
pico2 \
|
||||
pimoroni_badger2040 \
|
||||
pimoroni_interstate75 \
|
||||
pimoroni_keybow2040 \
|
||||
pimoroni_motor2040 \
|
||||
pimoroni_pga2040 \
|
||||
pimoroni_pga2350 \
|
||||
pimoroni_pico_plus2_rp2350 \
|
||||
pimoroni_picolipo_4mb \
|
||||
pimoroni_picolipo_16mb \
|
||||
pimoroni_picosystem \
|
||||
pimoroni_plasma2040 \
|
||||
pimoroni_plasma2350 \
|
||||
pimoroni_servo2040 \
|
||||
pimoroni_tiny2040 \
|
||||
pimoroni_tiny2040_2mb \
|
||||
pimoroni_tiny2350 \
|
||||
pololu_3pi_2040_robot \
|
||||
pololu_zumo_2040_robot \
|
||||
seeed_xiao_rp2040 \
|
||||
seeed_xiao_rp2350 \
|
||||
solderparty_rp2040_stamp \
|
||||
solderparty_rp2040_stamp_carrier \
|
||||
solderparty_rp2040_stamp_round_carrier \
|
||||
solderparty_rp2350_stamp_xl \
|
||||
solderparty_rp2350_stamp \
|
||||
sparkfun_micromod \
|
||||
sparkfun_promicro \
|
||||
sparkfun_promicro_rp2350 \
|
||||
sparkfun_thingplus \
|
||||
switchscience_picossci2_conta_base \
|
||||
switchscience_picossci2_dev_board \
|
||||
switchscience_picossci2_micro \
|
||||
switchscience_picossci2_rp2350_breakout \
|
||||
switchscience_picossci2_tiny \
|
||||
tinycircuits_thumby_color_rp2350 \
|
||||
vgaboard \
|
||||
waveshare_rp2040_lcd_0.96 \
|
||||
waveshare_rp2040_lcd_1.28 \
|
||||
waveshare_rp2040_one \
|
||||
waveshare_rp2040_plus_4mb \
|
||||
waveshare_rp2040_plus_16mb \
|
||||
waveshare_rp2040_zero \
|
||||
weact_studio_rp2040_2mb \
|
||||
weact_studio_rp2040_4mb \
|
||||
weact_studio_rp2040_8mb \
|
||||
weact_studio_rp2040_16mb \
|
||||
wiznet_w5100s_evb_pico
|
||||
do
|
||||
rm -rf *
|
||||
PICO_SDK_PATH="${PICO_SDK_PATH:-../../pico-sdk}" cmake .. -DPICO_BOARD=$board
|
||||
make -j`nproc`
|
||||
mv pico_fido.uf2 ../release/pico_fido_$board-$SUFFIX.uf2
|
||||
done
|
||||
@@ -17,8 +17,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
VERSION_MAJOR="3" #Version of Pico CCID Core
|
||||
VERSION_MINOR="4"
|
||||
VERSION_MAJOR="4" #Version of Pico CCID Core
|
||||
VERSION_MINOR="0"
|
||||
|
||||
echo "----------------------------"
|
||||
echo "VID/PID patcher for Pico FIDO"
|
||||
@@ -87,7 +87,7 @@ fi
|
||||
LITTLE_VID="\x${VID:2:2}\x${VID:0:2}"
|
||||
LITTLE_PID="\x${PID:2:2}\x${PID:0:2}"
|
||||
|
||||
perl -pi -e "s/\xfe\xca\x31\x42\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01/" $UF2_FILE_OF
|
||||
perl -pi -e "s/[\x00-\xff]{4}\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/$LITTLE_VID$LITTLE_PID\x$VERSION_MINOR\x$VERSION_MAJOR\x01\x02\x03\x01\x00\x00/" $UF2_FILE_OF
|
||||
|
||||
echo "Done!"
|
||||
echo ""
|
||||
|
||||
Submodule pico-hsm-sdk deleted from 657913d29a
1
pico-keys-sdk
Submodule
1
pico-keys-sdk
Submodule
Submodule pico-keys-sdk added at 3d912878f1
@@ -18,9 +18,20 @@ if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_P
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
@@ -29,11 +40,22 @@ if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG master
|
||||
)
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
)
|
||||
else ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
FetchContent_Populate(pico_sdk)
|
||||
|
||||
55
sdkconfig.defaults
Normal file
55
sdkconfig.defaults
Normal file
@@ -0,0 +1,55 @@
|
||||
# This file was generated using idf.py save-defconfig. It can be edited manually.
|
||||
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
|
||||
#
|
||||
IGNORE_UNKNOWN_FILES_FOR_MANAGED_COMPONENTS=1
|
||||
|
||||
CONFIG_TINYUSB=y
|
||||
CONFIG_TINYUSB_TASK_STACK_SIZE=16384
|
||||
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="pico-keys-sdk/config/esp32/partitions.csv"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_WL_SECTOR_SIZE_512=y
|
||||
CONFIG_WL_SECTOR_MODE_PERF=y
|
||||
COMPILER_OPTIMIZATION="Performance"
|
||||
|
||||
CONFIG_MBEDTLS_CMAC_C=y
|
||||
CONFIG_MBEDTLS_CHACHA20_C=y
|
||||
CONFIG_MBEDTLS_POLY1305_C=y
|
||||
CONFIG_MBEDTLS_CHACHAPOLY_C=y
|
||||
CONFIG_MBEDTLS_HKDF_C=y
|
||||
CONFIG_MBEDTLS_HARDWARE_ECC=y
|
||||
CONFIG_MBEDTLS_HARDWARE_GCM=y
|
||||
# CONFIG_MBEDTLS_HARDWARE_MPI is not set
|
||||
CONFIG_MBEDTLS_HARDWARE_SHA=y
|
||||
CONFIG_MBEDTLS_HARDWARE_AES=y
|
||||
# CONFIG_MBEDTLS_ROM_MD5 is not set
|
||||
CONFIG_MBEDTLS_SHA512_C=y
|
||||
CONFIG_MBEDTLS_TLS_DISABLED=y
|
||||
# CONFIG_MBEDTLS_TLS_ENABLED is not set
|
||||
# CONFIG_ESP_TLS_USE_DS_PERIPHERAL is not set
|
||||
# CONFIG_ESP_WIFI_ENABLED is not set
|
||||
# CONFIG_ESP_WIFI_MBEDTLS_CRYPTO is not set
|
||||
# CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT is not set
|
||||
# CONFIG_WPA_MBEDTLS_CRYPTO is not set
|
||||
# CONFIG_MBEDTLS_PSK_MODES is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_RSA is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA is not set
|
||||
# CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA is not set
|
||||
# CONFIG_MBEDTLS_SSL_RENEGOTIATION is not set
|
||||
# CONFIG_MBEDTLS_SSL_PROTO_TLS1_2 is not set
|
||||
# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set
|
||||
# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set
|
||||
# CONFIG_MBEDTLS_SSL_ALPN is not set
|
||||
# CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS is not set
|
||||
# CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS is not set
|
||||
# CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE is not set
|
||||
# CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA is not set
|
||||
# CONFIG_ESP_WIFI_ENABLE_WPA3_SAE is not set
|
||||
# CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA is not set
|
||||
|
||||
CONFIG_ESP_COREDUMP_ENABLE_TO_UART=y
|
||||
6
src/fido/CMakeLists.txt
Normal file
6
src/fido/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
idf_component_register(
|
||||
SRCS ${SOURCES}
|
||||
INCLUDE_DIRS . ../../pico-keys-sdk/src ../../pico-keys-sdk/src/fs ../../pico-keys-sdk/src/rng ../../pico-keys-sdk/src/usb ../../pico-keys-sdk/tinycbor/src
|
||||
REQUIRES bootloader_support esp_partition esp_tinyusb zorxx__neopixel mbedtls efuse
|
||||
)
|
||||
idf_component_set_property(${COMPONENT_NAME} WHOLE_ARCHIVE ON)
|
||||
242
src/fido/cbor.c
242
src/fido/cbor.c
@@ -15,66 +15,246 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "pico_keys.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "hsm.h"
|
||||
#include "usb.h"
|
||||
#include "apdu.h"
|
||||
#include "management.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "version.h"
|
||||
|
||||
const bool _btrue = true, _bfalse = false;
|
||||
|
||||
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")
|
||||
int cbor_reset();
|
||||
int cbor_get_info();
|
||||
int cbor_make_credential(const uint8_t *data, size_t len);
|
||||
int cbor_client_pin(const uint8_t *data, size_t len);
|
||||
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||
int cbor_get_next_assertion(const uint8_t *data, size_t len);
|
||||
int cbor_selection();
|
||||
int cbor_cred_mgmt(const uint8_t *data, size_t len);
|
||||
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 *cbor_data = NULL;
|
||||
size_t cbor_len = 0;
|
||||
uint8_t cbor_cmd = 0;
|
||||
|
||||
int cbor_parse(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
if (len == 0 && cmd == CTAPHID_CBOR) {
|
||||
return CTAP1_ERR_INVALID_LEN;
|
||||
driver_prepare_response();
|
||||
if (data[0] == CTAP_MAKE_CREDENTIAL)
|
||||
return cbor_make_credential(data + 1, len - 1);
|
||||
if (data[0] == CTAP_GET_INFO)
|
||||
return cbor_get_info();
|
||||
else if (data[0] == CTAP_RESET)
|
||||
return cbor_reset();
|
||||
else if (data[0] == CTAP_CLIENT_PIN)
|
||||
return cbor_client_pin(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_GET_ASSERTION)
|
||||
return cbor_get_assertion(data + 1, len - 1, false);
|
||||
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
|
||||
return cbor_get_next_assertion(data + 1, len - 1);
|
||||
return CTAP2_ERR_INVALID_CBOR;
|
||||
}
|
||||
if (len > 0) {
|
||||
DEBUG_DATA(data + 1, len - 1);
|
||||
}
|
||||
if (cap_supported(CAP_FIDO2)) {
|
||||
if (cmd == CTAPHID_CBOR) {
|
||||
if (data[0] == CTAP_MAKE_CREDENTIAL) {
|
||||
return cbor_make_credential(data + 1, len - 1);
|
||||
}
|
||||
if (data[0] == CTAP_GET_INFO) {
|
||||
return cbor_get_info();
|
||||
}
|
||||
else if (data[0] == CTAP_RESET) {
|
||||
return cbor_reset();
|
||||
}
|
||||
else if (data[0] == CTAP_CLIENT_PIN) {
|
||||
return cbor_client_pin(data + 1, len - 1);
|
||||
}
|
||||
else if (data[0] == CTAP_GET_ASSERTION) {
|
||||
return cbor_get_assertion(data + 1, len - 1, false);
|
||||
}
|
||||
else if (data[0] == CTAP_GET_NEXT_ASSERTION) {
|
||||
return cbor_get_next_assertion(data + 1, len - 1);
|
||||
}
|
||||
else if (data[0] == CTAP_SELECTION) {
|
||||
return cbor_selection();
|
||||
}
|
||||
else if (data[0] == CTAP_CREDENTIAL_MGMT || data[0] == 0x41) {
|
||||
return cbor_cred_mgmt(data + 1, len - 1);
|
||||
}
|
||||
else if (data[0] == CTAP_CONFIG) {
|
||||
return cbor_config(data + 1, len - 1);
|
||||
}
|
||||
else if (data[0] == CTAP_LARGE_BLOBS) {
|
||||
return cbor_large_blobs(data + 1, len - 1);
|
||||
}
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_CBOR) {
|
||||
return cbor_vendor(data, len);
|
||||
}
|
||||
else if (cmd == 0xC2) {
|
||||
if (cmd_read_config() == 0x9000) {
|
||||
memmove(res_APDU-1, res_APDU, res_APDU_size);
|
||||
res_APDU_size -= 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CTAP1_ERR_INVALID_CMD;
|
||||
}
|
||||
|
||||
void cbor_thread() {
|
||||
|
||||
void cbor_thread(void) {
|
||||
card_init_core1();
|
||||
while (1) {
|
||||
uint32_t m;
|
||||
queue_remove_blocking(&usb_to_card_q, &m);
|
||||
uint32_t flag = m + 1;
|
||||
queue_add_blocking(&card_to_usb_q, &flag);
|
||||
|
||||
if (m == EV_EXIT) {
|
||||
break;
|
||||
}
|
||||
apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len);
|
||||
if (apdu.sw == 0) {
|
||||
DEBUG_DATA(res_APDU + 1, res_APDU_size);
|
||||
}
|
||||
else {
|
||||
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
|
||||
res_APDU[-1] = apdu.sw;
|
||||
apdu.sw = 0;
|
||||
}
|
||||
else {
|
||||
res_APDU[0] = apdu.sw;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
finished_data_size = res_APDU_size + 1;
|
||||
|
||||
apdu.sw = cbor_parse(cbor_data, cbor_len);
|
||||
|
||||
finished_data_size = res_APDU_size+1;
|
||||
uint32_t flag = EV_EXEC_FINISHED;
|
||||
flag = EV_EXEC_FINISHED;
|
||||
queue_add_blocking(&card_to_usb_q, &flag);
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
vTaskDelete(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
int cbor_process(const uint8_t *data, size_t len) {
|
||||
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
||||
cbor_data = data;
|
||||
cbor_len = len;
|
||||
cbor_cmd = last_cmd;
|
||||
ctap_resp->init.data[0] = 0;
|
||||
res_APDU = ctap_resp->init.data + 1;
|
||||
res_APDU_size = 0;
|
||||
return 1;
|
||||
return 2; // CBOR processing
|
||||
}
|
||||
|
||||
CborError COSE_key_params(int crv, int alg, mbedtls_ecp_group *grp, mbedtls_ecp_point *Q, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
|
||||
CborError error = CborNoError;
|
||||
int kty = 1;
|
||||
if (crv == FIDO2_CURVE_P256 || crv == FIDO2_CURVE_P384 || crv == FIDO2_CURVE_P521 ||
|
||||
crv == FIDO2_CURVE_P256K1) {
|
||||
kty = 2;
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(mapEncoderParent, mapEncoder, kty == 2 ? 5 : 4));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(mapEncoder, kty));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(mapEncoder, 3));
|
||||
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, -alg));
|
||||
|
||||
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(mapEncoder, crv));
|
||||
|
||||
|
||||
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 2));
|
||||
uint8_t pkey[67];
|
||||
if (kty == 2) {
|
||||
size_t plen = mbedtls_mpi_size(&grp->P);
|
||||
CBOR_CHECK(mbedtls_mpi_write_binary(&Q->X, pkey, plen));
|
||||
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, plen));
|
||||
|
||||
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, 3));
|
||||
|
||||
CBOR_CHECK(mbedtls_mpi_write_binary(&Q->Y, pkey, plen));
|
||||
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, plen));
|
||||
}
|
||||
else {
|
||||
size_t olen = 0;
|
||||
CBOR_CHECK(mbedtls_ecp_point_write_binary(grp, Q, MBEDTLS_ECP_PF_COMPRESSED, &olen, pkey,
|
||||
sizeof(pkey)));
|
||||
CBOR_CHECK(cbor_encode_byte_string(mapEncoder, pkey, olen));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(mapEncoderParent, mapEncoder));
|
||||
err:
|
||||
return error;
|
||||
}
|
||||
CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
|
||||
CborEncoder *mapEncoder) {
|
||||
int crv = mbedtls_curve_to_fido(key->grp.id), alg = 0;
|
||||
if (key->grp.id == MBEDTLS_ECP_DP_SECP256R1) {
|
||||
alg = FIDO2_ALG_ES256;
|
||||
}
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
alg = FIDO2_ALG_ES384;
|
||||
}
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
alg = FIDO2_ALG_ES512;
|
||||
}
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_SECP256K1) {
|
||||
alg = FIDO2_ALG_ES256K;
|
||||
}
|
||||
else if (key->grp.id == MBEDTLS_ECP_DP_CURVE25519) {
|
||||
alg = FIDO2_ALG_ECDH_ES_HKDF_256;
|
||||
}
|
||||
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
|
||||
}
|
||||
CborError COSE_key_shared(mbedtls_ecdh_context *key,
|
||||
CborEncoder *mapEncoderParent,
|
||||
CborEncoder *mapEncoder) {
|
||||
int crv = mbedtls_curve_to_fido(key->ctx.mbed_ecdh.grp.id), alg = FIDO2_ALG_ECDH_ES_HKDF_256;
|
||||
return COSE_key_params(crv, alg, &key->ctx.mbed_ecdh.grp, &key->ctx.mbed_ecdh.Q, mapEncoderParent, mapEncoder);
|
||||
}
|
||||
CborError COSE_public_key(int alg, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder) {
|
||||
CborError error = CborNoError;
|
||||
CBOR_CHECK(cbor_encoder_create_map(mapEncoderParent, mapEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(mapEncoder, -alg));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(mapEncoder, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(mapEncoderParent, mapEncoder));
|
||||
err:
|
||||
return error;
|
||||
}
|
||||
CborError COSE_read_key(CborValue *f, int64_t *kty, int64_t *alg, int64_t *crv, CborByteString *kax, CborByteString *kay) {
|
||||
int64_t kkey = 0;
|
||||
CborError error = CborNoError;
|
||||
CBOR_PARSE_MAP_START(*f, 0)
|
||||
{
|
||||
CBOR_FIELD_GET_INT(kkey, 0);
|
||||
if (kkey == 1) {
|
||||
CBOR_FIELD_GET_INT(*kty, 0);
|
||||
}
|
||||
else if (kkey == 3) {
|
||||
CBOR_FIELD_GET_INT(*alg, 0);
|
||||
}
|
||||
else if (kkey == -1) {
|
||||
CBOR_FIELD_GET_INT(*crv, 0);
|
||||
}
|
||||
else if (kkey == -2) {
|
||||
CBOR_FIELD_GET_BYTES(*kax, 0);
|
||||
}
|
||||
else if (kkey == -3) {
|
||||
CBOR_FIELD_GET_BYTES(*kay, 0);
|
||||
}
|
||||
else {
|
||||
CBOR_ADVANCE(0);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(*f, 0);
|
||||
err:
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ESP_PLATFORM
|
||||
#include "common.h"
|
||||
#else
|
||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
||||
#endif
|
||||
#include "mbedtls/ecp.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
@@ -23,20 +27,23 @@
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "kek.h"
|
||||
|
||||
uint8_t permissions_rp_id = 0, permission_set = 0;
|
||||
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
|
||||
uint32_t max_usage_time_period = 600*1000;
|
||||
uint32_t max_usage_time_period = 600 * 1000;
|
||||
bool needs_power_cycle = false;
|
||||
mbedtls_ecdh_context hkey;
|
||||
bool hkey_init = false;
|
||||
static mbedtls_ecdh_context hkey;
|
||||
static bool hkey_init = false;
|
||||
|
||||
int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||
paut.user_present = userIsPresent;
|
||||
@@ -48,54 +55,65 @@ int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||
}
|
||||
|
||||
void clearUserPresentFlag() {
|
||||
if (paut.in_use == true)
|
||||
if (paut.in_use == true) {
|
||||
paut.user_present = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clearUserVerifiedFlag() {
|
||||
if (paut.in_use == true)
|
||||
if (paut.in_use == true) {
|
||||
paut.user_verified = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clearPinUvAuthTokenPermissionsExceptLbw() {
|
||||
if (paut.in_use == true)
|
||||
paut.permissions = FIDO2_PERMISSION_LBW;
|
||||
if (paut.in_use == true) {
|
||||
paut.permissions = CTAP_PERMISSION_LBW;
|
||||
}
|
||||
}
|
||||
|
||||
void stopUsingPinUvAuthToken() {
|
||||
permissions_rp_id = 0;
|
||||
paut.permissions = 0;
|
||||
usage_timer = 0;
|
||||
paut.in_use = false;
|
||||
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
|
||||
paut.has_rp_id = false;
|
||||
initial_usage_time_limit = 0;
|
||||
paut.user_present = paut.user_verified = false;
|
||||
user_present_time_limit = 0;
|
||||
}
|
||||
|
||||
bool getUserPresentFlagValue() {
|
||||
if (paut.in_use != true)
|
||||
if (paut.in_use != true) {
|
||||
paut.user_present = false;
|
||||
}
|
||||
return paut.user_present;
|
||||
}
|
||||
|
||||
bool getUserVerifiedFlagValue() {
|
||||
if (paut.in_use != true)
|
||||
bool getUserVerifiedFlagValue() {
|
||||
if (paut.in_use != true) {
|
||||
paut.user_verified = false;
|
||||
}
|
||||
return paut.user_verified;
|
||||
}
|
||||
}
|
||||
|
||||
int regenerate() {
|
||||
if (hkey_init == true)
|
||||
if (hkey_init == true) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
}
|
||||
|
||||
mbedtls_ecdh_init(&hkey);
|
||||
hkey_init = true;
|
||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
&hkey.ctx.mbed_ecdh.Q,
|
||||
random_gen,
|
||||
NULL);
|
||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -103,17 +121,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
int ret = 0;
|
||||
uint8_t buf[32];
|
||||
ret = mbedtls_mpi_write_binary(z, buf, sizeof(buf));
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
||||
buf,
|
||||
sizeof(buf),
|
||||
sharedSecret);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *)"CTAP2 HMAC key", 14, sharedSecret, 32);
|
||||
if (ret != 0)
|
||||
ret = mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 HMAC key",
|
||||
14,
|
||||
sharedSecret,
|
||||
32);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *)"CTAP2 AES key", 13, sharedSecret+32, 32);
|
||||
}
|
||||
return mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 AES key",
|
||||
13,
|
||||
sharedSecret + 32,
|
||||
32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -121,7 +160,12 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
|
||||
mbedtls_mpi z;
|
||||
mbedtls_mpi_init(&z);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
|
||||
&z,
|
||||
Q,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
random_gen,
|
||||
NULL);
|
||||
ret = kdf(protocol, &z, sharedSecret);
|
||||
mbedtls_mpi_free(&z);
|
||||
return ret;
|
||||
@@ -130,7 +174,7 @@ int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
|
||||
int resetPinUvAuthToken() {
|
||||
uint8_t t[32];
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
|
||||
file_put_data(ef_authtoken, t, sizeof(t));
|
||||
paut.permissions = 0;
|
||||
paut.data = file_get_data(ef_authtoken);
|
||||
paut.len = file_get_size(ef_authtoken);
|
||||
@@ -139,60 +183,70 @@ int resetPinUvAuthToken() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_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) {
|
||||
memcpy(out, in, in_len);
|
||||
return aes_encrypt(key, NULL, 32*8, HSM_AES_MODE_CBC, out, in_len);
|
||||
return aes_encrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
random_gen(NULL, out, IV_SIZE);
|
||||
memcpy(out + IV_SIZE, in, in_len);
|
||||
return aes_encrypt(key+32, out, 32*8, HSM_AES_MODE_CBC, out+IV_SIZE, in_len);
|
||||
return aes_encrypt(key + 32, out, 32 * 8, PICO_KEYS_AES_MODE_CBC, out + IV_SIZE, in_len);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out) {
|
||||
int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out) {
|
||||
if (protocol == 1) {
|
||||
memcpy(out, in, in_len);
|
||||
return aes_decrypt(key, NULL, 32*8, HSM_AES_MODE_CBC, out, in_len);
|
||||
return aes_decrypt(key, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, out, in_len);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
memcpy(out, in, in_len);
|
||||
return aes_encrypt(key+32, out, 32*8, HSM_AES_MODE_CBC, out+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 -1;
|
||||
}
|
||||
|
||||
int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
|
||||
int authenticate(uint8_t protocol,
|
||||
const uint8_t *key,
|
||||
const uint8_t *data,
|
||||
size_t len,
|
||||
uint8_t *sign) {
|
||||
uint8_t hmac[32];
|
||||
int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||
if (ret != 0)
|
||||
int ret =
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
memcpy(sign, hmac, 16);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
memcpy(sign, hmac, 32);
|
||||
}
|
||||
else
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
|
||||
int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign) {
|
||||
uint8_t hmac[32];
|
||||
//if (paut.in_use == false)
|
||||
// return -2;
|
||||
int ret = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
if (protocol == 1)
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return memcmp(sign, hmac, 16);
|
||||
else if (protocol == 2)
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
return memcmp(sign, hmac, 32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -206,13 +260,17 @@ int getPublicKey() {
|
||||
}
|
||||
|
||||
int pinUvAuthTokenUsageTimerObserver() {
|
||||
if (usage_timer == 0)
|
||||
if (usage_timer == 0) {
|
||||
return -1;
|
||||
if (usage_timer+max_usage_time_period > board_millis()) {
|
||||
if (user_present_time_limit == 0 || user_present_time_limit+TRANSPORT_TIME_LIMIT < board_millis())
|
||||
}
|
||||
if (usage_timer + max_usage_time_period > board_millis()) {
|
||||
if (user_present_time_limit == 0 ||
|
||||
user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||
clearUserPresentFlag();
|
||||
}
|
||||
if (paut.in_use == true) {
|
||||
if (initial_usage_time_limit == 0 || initial_usage_time_limit+TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||
if (initial_usage_time_limit == 0 ||
|
||||
initial_usage_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||
stopUsingPinUvAuthToken();
|
||||
return 1;
|
||||
}
|
||||
@@ -222,6 +280,21 @@ int pinUvAuthTokenUsageTimerObserver() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_mkek_encrypted(const uint8_t *dhash) {
|
||||
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
|
||||
hash_multi(dhash, 16, session_pin); // Only for storing MKEK
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
int ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return CTAP2_ERR_PIN_AUTH_INVALID;
|
||||
}
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
uint8_t new_pin_mismatches = 0;
|
||||
|
||||
int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
@@ -232,20 +305,24 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CborEncoder encoder, mapEncoder;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = {0}, newPinEnc = {0}, pinHashEnc = {0}, kax = {0}, kay = {0};
|
||||
CborCharString rpId = {0};
|
||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
|
||||
kay = { 0 };
|
||||
CborCharString rpId = { 0 };
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
if (hkey_init == false)
|
||||
if (hkey_init == false) {
|
||||
initialize();
|
||||
}
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 2 && val_c != val_u)
|
||||
if (val_c <= 2 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
@@ -254,28 +331,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_FIELD_GET_UINT(subcommand, 1);
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
int64_t key = 0;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_FIELD_GET_INT(key, 2);
|
||||
if (key == 1) {
|
||||
CBOR_FIELD_GET_INT(kty, 2);
|
||||
}
|
||||
else if (key == 3) {
|
||||
CBOR_FIELD_GET_INT(alg, 2);
|
||||
}
|
||||
else if (key == -1) {
|
||||
CBOR_FIELD_GET_INT(crv, 2);
|
||||
}
|
||||
else if (key == -2) {
|
||||
CBOR_FIELD_GET_BYTES(kax, 2);
|
||||
}
|
||||
else if (key == -3) {
|
||||
CBOR_FIELD_GET_BYTES(kay, 2);
|
||||
}
|
||||
else
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
CBOR_CHECK(COSE_read_key(&_f1, &kty, &alg, &crv, &kax, &kay));
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
@@ -295,13 +351,14 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
if (subcommand == 0x0)
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
if (subcommand == 0x0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
else if (subcommand == 0x1) { //getPINRetries
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, needs_power_cycle ? 2 : 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t)*file_get_data(ef_pin)));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, (uint64_t) *file_get_data(ef_pin)));
|
||||
if (needs_power_cycle) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
@@ -313,36 +370,30 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
|
||||
uint8_t pkey[32];
|
||||
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
|
||||
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(COSE_key_shared(&hkey, &mapEncoder, &mapEncoder2));
|
||||
}
|
||||
else if (pinUvAuthProtocol == 0)
|
||||
else if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
else
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x3) { //setPIN
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0)
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 ||
|
||||
newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (file_has_data(ef_pin))
|
||||
}
|
||||
if (file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
if (newPinEnc.len != 64)
|
||||
}
|
||||
if ((pinUvAuthProtocol == 1 && newPinEnc.len != 64) ||
|
||||
(pinUvAuthProtocol == 2 && newPinEnc.len != 64 + IV_SIZE)) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
@@ -350,45 +401,72 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t sharedSecret[64];
|
||||
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (verify(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, pinUvAuthParam.data) != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, pinUvAuthParam.data) != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t paddedNewPin[64];
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (paddedNewPin[63] != 0)
|
||||
}
|
||||
if (paddedNewPin[63] != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
uint8_t pin_len = 0;
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin)) {
|
||||
pin_len++;
|
||||
if (pin_len < 4)
|
||||
}
|
||||
uint8_t minPin = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin)) {
|
||||
minPin = *file_get_data(ef_minpin);
|
||||
}
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
uint8_t hsh[33];
|
||||
}
|
||||
uint8_t hsh[34], dhash[32];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
|
||||
flash_write_data_to_file(ef_pin, hsh, 1+16);
|
||||
hsh[1] = pin_len;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
low_flash_available();
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
goto err; //No return
|
||||
}
|
||||
else if (subcommand == 0x4) { //changePIN
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0 || pinHashEnc.present == false)
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 ||
|
||||
newPinEnc.present == false || pinUvAuthParam.present == false || alg == 0 ||
|
||||
pinHashEnc.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (!file_has_data(ef_pin))
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
if (*file_get_data(ef_pin) == 0)
|
||||
}
|
||||
if (*file_get_data(ef_pin) == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
if (newPinEnc.len != 64 || pinHashEnc.len != 16)
|
||||
}
|
||||
if ((pinUvAuthProtocol == 1 && (newPinEnc.len != 64 || pinHashEnc.len != 16)) ||
|
||||
(pinUvAuthProtocol == 2 &&
|
||||
(newPinEnc.len != 64 + IV_SIZE || pinHashEnc.len != 16 + IV_SIZE))) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
@@ -396,72 +474,138 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t sharedSecret[64];
|
||||
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t tmp[64 + 16];
|
||||
memcpy(tmp, newPinEnc.data, 64);
|
||||
memcpy(tmp + 64, pinHashEnc.data, 16);
|
||||
if (verify(pinUvAuthProtocol, sharedSecret, tmp, sizeof(tmp), pinUvAuthParam.data) != 0) {
|
||||
uint8_t tmp[80 + 32];
|
||||
memcpy(tmp, newPinEnc.data, newPinEnc.len);
|
||||
memcpy(tmp + newPinEnc.len, pinHashEnc.data, pinHashEnc.len);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, sharedSecret, tmp, (uint16_t)(newPinEnc.len + pinHashEnc.len), pinUvAuthParam.data) != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t retries = *file_get_data(ef_pin) - 1;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64];
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
low_flash_available();
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
if (++new_pin_mismatches == 3) {
|
||||
if (++new_pin_mismatches >= 3) {
|
||||
needs_power_cycle = true;
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||
}
|
||||
else
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
retries = MAX_PIN_RETRIES;
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
new_pin_mismatches = 0;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (paddedNewPin[63] != 0)
|
||||
if (paddedNewPin[63] != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t pin_len = 0;
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin)) {
|
||||
pin_len++;
|
||||
if (pin_len < 4)
|
||||
}
|
||||
uint8_t minPin = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin)) {
|
||||
minPin = *file_get_data(ef_minpin);
|
||||
}
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
uint8_t hsh[33];
|
||||
}
|
||||
uint8_t hsh[34];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
|
||||
flash_write_data_to_file(ef_pin, hsh, 1+16);
|
||||
hsh[1] = pin_len;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
|
||||
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
ret = load_mkek(mkek);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(dhash, 16, session_pin);
|
||||
ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
|
||||
memcpy(tmpf, file_get_data(ef_minpin), file_get_size(ef_minpin));
|
||||
tmpf[1] = 0;
|
||||
file_put_data(ef_minpin, tmpf, file_get_size(ef_minpin));
|
||||
free(tmpf);
|
||||
}
|
||||
low_flash_available();
|
||||
resetPinUvAuthToken();
|
||||
goto err; // No return
|
||||
}
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getUVRgetPinUvAuthTokenUsingPinWithPermissionsetries
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || alg == 0 || pinHashEnc.present == false)
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || alg == 0 ||
|
||||
pinHashEnc.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if ((subcommand == 0x9 && permissions == 0) || (subcommand == 0x5 && (permissions != 0 || rpId.present == true)))
|
||||
}
|
||||
if (subcommand == 0x5 && (permissions != 0 || rpId.present == true)) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (*file_get_data(ef_pin) == 0)
|
||||
}
|
||||
if (subcommand == 0x9) {
|
||||
if (permissions == 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
}
|
||||
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
}
|
||||
if (*file_get_data(ef_pin) == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
@@ -469,51 +613,79 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t sharedSecret[64];
|
||||
int ret = ecdh(pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
int ret = ecdh((uint8_t)pinUvAuthProtocol, &hkey.ctx.mbed_ecdh.Qp, sharedSecret);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t retries = *file_get_data(ef_pin) - 1;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
uint8_t paddedNewPin[64];
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pinHashEnc.data, (uint16_t)pinHashEnc.len, paddedNewPin);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
low_flash_available();
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
if (++new_pin_mismatches >= 3) {
|
||||
if (++new_pin_mismatches >= 3) {
|
||||
needs_power_cycle = true;
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_BLOCKED);
|
||||
}
|
||||
else
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
retries = MAX_PIN_RETRIES;
|
||||
|
||||
ret = check_mkek_encrypted(paddedNewPin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
new_pin_mismatches = 0;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
|
||||
low_flash_available();
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
paut.permissions = permissions;
|
||||
if (rpId.present == true)
|
||||
memcpy(paut.rp_id_hash, rpId.data, 32);
|
||||
else
|
||||
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
|
||||
uint8_t pinUvAuthToken_enc[32];
|
||||
encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
||||
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;
|
||||
}
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
|
||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
|
||||
}
|
||||
else
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
err:
|
||||
@@ -524,10 +696,11 @@ err:
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(rpId);
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = resp_size;
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
280
src/fido/cbor_config.c
Normal file
280
src/fido/cbor_config.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "file.h"
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
|
||||
int cbor_config(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
|
||||
CborCharString minPinLengthRPIDs[32] = { 0 };
|
||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||
CborEncoder encoder;
|
||||
//CborEncoder mapEncoder;
|
||||
uint8_t *raw_subpara = NULL;
|
||||
const bool *forceChangePin = NULL;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 1 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(subcommand, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
uint64_t subpara = 0;
|
||||
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
if (subcommand == 0x7f) { // Config Aut
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x03) { // Extensions
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(newMinPinLength, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_PARSE_ARRAY_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(minPinLengthRPIDs[minPinLengthRPIDs_len], 3);
|
||||
minPinLengthRPIDs_len++;
|
||||
if (minPinLengthRPIDs_len >= 32) {
|
||||
CBOR_ERROR(CTAP2_ERR_KEY_STORE_FULL);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_ARRAY_END(_f2, 3);
|
||||
}
|
||||
else if (subpara == 0x03) {
|
||||
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x1B) { // PHY
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_UINT(vendorParam, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
|
||||
if (pinUvAuthParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
|
||||
memset(verify_payload, 0xff, 32);
|
||||
verify_payload[32] = 0x0d;
|
||||
verify_payload[33] = (uint8_t)subcommand;
|
||||
memcpy(verify_payload + 34, raw_subpara, raw_subpara_len);
|
||||
error = verify((uint8_t)pinUvAuthProtocol, paut.data, verify_payload, (uint16_t)(32 + 1 + 1 + raw_subpara_len), pinUvAuthParam.data);
|
||||
free(verify_payload);
|
||||
if (error != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
if (!(paut.permissions & CTAP_PERMISSION_ACFG)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
if (subcommand == 0x7f) {
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
|
||||
if (!file_has_data(ef_keydev_enc)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
if (has_keydev_dec == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
file_put_data(ef_keydev, 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
|
||||
low_flash_available();
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
|
||||
if (!file_has_data(ef_keydev)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
if (mse.init == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t key_dev_enc[12 + 32 + 16];
|
||||
random_gen(NULL, key_dev_enc, 12);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
|
||||
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
file_put_data(ef_keydev_enc, 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, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
else if (subcommand == 0x03) {
|
||||
uint8_t currentMinPinLen = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin)) {
|
||||
currentMinPinLen = *file_get_data(ef_minpin);
|
||||
}
|
||||
if (newMinPinLength == 0) {
|
||||
newMinPinLength = currentMinPinLen;
|
||||
}
|
||||
else if (newMinPinLength > 0 && newMinPinLength < currentMinPinLen) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
if (forceChangePin == ptrue && !file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
}
|
||||
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
|
||||
forceChangePin = ptrue;
|
||||
}
|
||||
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
||||
dataf[0] = (uint8_t)newMinPinLength;
|
||||
dataf[1] = forceChangePin == ptrue ? 1 : 0;
|
||||
for (size_t m = 0; m < minPinLengthRPIDs_len; m++) {
|
||||
mbedtls_sha256((uint8_t *) minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, dataf + 2 + m * 32, 0);
|
||||
}
|
||||
file_put_data(ef_minpin, dataf, (uint16_t)(2 + minPinLengthRPIDs_len * 32));
|
||||
low_flash_available();
|
||||
free(dataf);
|
||||
goto err; //No return
|
||||
}
|
||||
else if (subcommand == 0x01) {
|
||||
set_opts(get_opts() | FIDO2_OPT_EA);
|
||||
goto err;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (subcommand == 0x1B) {
|
||||
if (vendorParam == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
|
||||
phy_data.vid = (vendorParam >> 16) & 0xFFFF;
|
||||
phy_data.pid = vendorParam & 0xFFFF;
|
||||
phy_data.vidpid_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
|
||||
phy_data.led_gpio = (uint8_t)vendorParam;
|
||||
phy_data.led_gpio_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
|
||||
phy_data.led_brightness = (uint8_t)vendorParam;
|
||||
phy_data.led_brightness_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
|
||||
phy_data.opts = (uint16_t)vendorParam;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
uint8_t tmp[PHY_MAX_SIZE];
|
||||
uint16_t tmp_len = 0;
|
||||
memset(tmp, 0, sizeof(tmp));
|
||||
if (phy_serialize_data(&phy_data, tmp, &tmp_len) != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
file_put_data(ef_phy, tmp, tmp_len);
|
||||
low_flash_available();
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
//CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
//resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorAutCt);
|
||||
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||
}
|
||||
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
450
src/fido/cbor_cred_mgmt.c
Normal file
450
src/fido/cbor_cred_mgmt.c
Normal file
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* 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 "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
uint8_t rp_counter = 1;
|
||||
uint8_t rp_total = 0;
|
||||
uint8_t cred_counter = 1;
|
||||
uint8_t cred_total = 0;
|
||||
CborByteString rpIdHashx = { 0 };
|
||||
|
||||
int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, rpIdHash = { 0 };
|
||||
PublicKeyCredentialDescriptor credentialId = { 0 };
|
||||
PublicKeyCredentialUserEntity user = { 0 };
|
||||
size_t resp_size = 0;
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
uint8_t *raw_subpara = NULL;
|
||||
size_t raw_subpara_len = 0;
|
||||
bool asserted = false, is_preview = *(data - 1) == 0x41; // Backwards compatibility
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 1 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(subcommand, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
uint64_t subpara = 0;
|
||||
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_BYTES(rpIdHash, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(3);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", credentialId.id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", credentialId.type);
|
||||
if (strcmp(_fd3, "transports") == 0) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
|
||||
transports_len], 4);
|
||||
credentialId.transports_len++;
|
||||
}
|
||||
CBOR_PARSE_ARRAY_END(_f3, 4);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
}
|
||||
else if (subpara == 0x03) {
|
||||
CBOR_PARSE_MAP_START(_f1, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(3);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", user.id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "name", user.parent.name);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "displayName", user.displayName);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 3);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
else if (val_u == 0x04) { // pubKeyCredParams
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
if (subcommand != 0x03 && subcommand != 0x05) {
|
||||
if (pinUvAuthParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
if (subcommand == 0x01) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t existing = 0;
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
if (file_has_data(search_dynamic_file((uint16_t)(EF_CRED + i)))) {
|
||||
existing++;
|
||||
}
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, existing));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_RESIDENT_CREDENTIALS - existing));
|
||||
}
|
||||
else if (subcommand == 0x02 || subcommand == 0x03) {
|
||||
file_t *rp_ef = NULL;
|
||||
if (subcommand == 0x02) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
rp_counter = 1;
|
||||
rp_total = 0;
|
||||
}
|
||||
else {
|
||||
if (rp_counter > rp_total) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
}
|
||||
uint8_t skip = 0;
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *tef = search_dynamic_file((uint16_t)(EF_RP + i));
|
||||
if (file_has_data(tef) && *file_get_data(tef) > 0) {
|
||||
if (++skip == rp_counter) {
|
||||
if (rp_ef == NULL) {
|
||||
rp_ef = tef;
|
||||
}
|
||||
if (subcommand == 0x03) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subcommand == 0x02) {
|
||||
rp_total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rp_ef == NULL) {
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
}
|
||||
rp_counter++;
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x02 ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, (char *) file_get_data(rp_ef) + 33,
|
||||
file_get_size(rp_ef) - 33));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(rp_ef) + 1, 32));
|
||||
if (subcommand == 0x02) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, rp_total));
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x04 || subcommand == 0x05) {
|
||||
if (subcommand == 0x04 && rpIdHash.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (subcommand == 0x04) {
|
||||
*(raw_subpara - 1) = 0x04;
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
cred_counter = 1;
|
||||
cred_total = 0;
|
||||
}
|
||||
else {
|
||||
if (cred_counter > cred_total) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
rpIdHash = rpIdHashx;
|
||||
}
|
||||
file_t *cred_ef = NULL;
|
||||
uint8_t skip = 0;
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *tef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(tef) && memcmp(file_get_data(tef), rpIdHash.data, 32) == 0) {
|
||||
if (++skip == cred_counter) {
|
||||
if (cred_ef == NULL) {
|
||||
cred_ef = tef;
|
||||
}
|
||||
if (subcommand == 0x05) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (subcommand == 0x04) {
|
||||
cred_total++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!file_has_data(cred_ef)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
}
|
||||
|
||||
Credential cred = { 0 };
|
||||
if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
if (fido_load_key((int)cred.curve, cred.id.data, &key) != 0) {
|
||||
credential_free(&cred);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
cred_counter++;
|
||||
|
||||
uint8_t l = 4;
|
||||
if (subcommand == 0x04) {
|
||||
l++;
|
||||
}
|
||||
if (cred.extensions.present == true) {
|
||||
if (cred.extensions.credProtect > 0) {
|
||||
l++;
|
||||
}
|
||||
if (cred.extensions.largeBlobKey == ptrue) {
|
||||
l++;
|
||||
}
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
|
||||
l = 0;
|
||||
if (cred.userId.present == true) {
|
||||
l++;
|
||||
}
|
||||
if (cred.userName.present == true) {
|
||||
l++;
|
||||
}
|
||||
if (cred.userDisplayName.present == true) {
|
||||
l++;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, l));
|
||||
if (cred.userId.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.userId.data, cred.userId.len));
|
||||
}
|
||||
if (cred.userName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
||||
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userName.data, cred.userName.len));
|
||||
}
|
||||
if (cred.userDisplayName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "displayName"));
|
||||
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, cred.userDisplayName.data, cred.userDisplayName.len));
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
|
||||
CBOR_CHECK(COSE_key(&key, &mapEncoder, &mapEncoder2));
|
||||
|
||||
if (subcommand == 0x04) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x09));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred_total));
|
||||
}
|
||||
if (cred_counter <= cred_total) {
|
||||
asserted = true;
|
||||
rpIdHashx = rpIdHash;
|
||||
}
|
||||
if (cred.extensions.present == true) {
|
||||
if (cred.extensions.credProtect > 0) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect));
|
||||
}
|
||||
if (cred.extensions.largeBlobKey == ptrue) {
|
||||
uint8_t largeBlobKey[32];
|
||||
int ret = credential_derive_large_blob_key(cred.id.data, cred.id.len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, cred.extensions.thirdPartyPayment == ptrue));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||
}
|
||||
credential_free(&cred);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
}
|
||||
else if (subcommand == 0x06) {
|
||||
if (credentialId.id.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
*(raw_subpara - 1) = 0x06;
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (delete_file(ef) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
for (int j = 0; j < MAX_RESIDENT_CREDENTIALS; j++) {
|
||||
file_t *rp_ef = search_dynamic_file((uint16_t)(EF_RP + j));
|
||||
if (file_has_data(rp_ef) && memcmp(file_get_data(rp_ef) + 1, rp_id_hash, 32) == 0) {
|
||||
uint8_t *rp_data = (uint8_t *) calloc(1, file_get_size(rp_ef));
|
||||
memcpy(rp_data, file_get_data(rp_ef), file_get_size(rp_ef));
|
||||
rp_data[0] -= 1;
|
||||
if (rp_data[0] == 0) {
|
||||
delete_file(rp_ef);
|
||||
}
|
||||
else {
|
||||
file_put_data(rp_ef, rp_data, file_get_size(rp_ef));
|
||||
}
|
||||
free(rp_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
low_flash_available();
|
||||
goto err; //no error
|
||||
}
|
||||
}
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
}
|
||||
else if (subcommand == 0x07) {
|
||||
if (credentialId.id.present == false || user.id.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
*(raw_subpara - 1) = 0x07;
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
Credential cred = { 0 };
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
|
||||
credential_free(&cred);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t newcred[MAX_CRED_ID_LENGTH];
|
||||
size_t newcred_len = 0;
|
||||
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
|
||||
&user.displayName, &cred.opts, &cred.extensions,
|
||||
cred.use_sign_count, (int)cred.alg,
|
||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||
credential_free(&cred);
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
credential_free(&cred);
|
||||
if (credential_store(newcred, newcred_len, rp_id_hash) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
low_flash_available();
|
||||
goto err; //no error
|
||||
}
|
||||
}
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
|
||||
if (asserted == false) {
|
||||
CBOR_FREE_BYTE_STRING(rpIdHash);
|
||||
}
|
||||
CBOR_FREE_BYTE_STRING(user.id);
|
||||
CBOR_FREE_BYTE_STRING(user.displayName);
|
||||
CBOR_FREE_BYTE_STRING(user.parent.name);
|
||||
CBOR_FREE_BYTE_STRING(credentialId.type);
|
||||
CBOR_FREE_BYTE_STRING(credentialId.id);
|
||||
for (size_t n = 0; n < credentialId.transports_len; n++) {
|
||||
CBOR_FREE_BYTE_STRING(credentialId.transports[n]);
|
||||
}
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
@@ -17,20 +17,24 @@
|
||||
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "credential.h"
|
||||
#include <math.h>
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "random.h"
|
||||
|
||||
int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||
|
||||
bool residentx = false;
|
||||
Credential credsx[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
Credential credsx[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
uint8_t credentialCounter = 1;
|
||||
uint8_t numberOfCredentialsx = 0;
|
||||
uint8_t flagsx = 0;
|
||||
@@ -39,18 +43,23 @@ uint8_t *datax = NULL;
|
||||
size_t lenx = 0;
|
||||
|
||||
int cbor_get_next_assertion(const uint8_t *data, size_t len) {
|
||||
(void) data;
|
||||
(void) len;
|
||||
CborError error = CborNoError;
|
||||
if (credentialCounter >= numberOfCredentialsx)
|
||||
if (credentialCounter >= numberOfCredentialsx) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
if (timerx+30*1000 < board_millis())
|
||||
}
|
||||
if (timerx + 30 * 1000 < board_millis()) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
CBOR_CHECK(cbor_get_assertion(datax, lenx, true));
|
||||
timerx = board_millis();
|
||||
credentialCounter++;
|
||||
err:
|
||||
if (error != CborNoError) {
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++)
|
||||
if (error != CborNoError || credentialCounter == numberOfCredentialsx) {
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
credential_free(&credsx[i]);
|
||||
}
|
||||
if (datax) {
|
||||
free(datax);
|
||||
datax = NULL;
|
||||
@@ -61,8 +70,9 @@ err:
|
||||
flagsx = 0;
|
||||
credentialCounter = 0;
|
||||
numberOfCredentialsx = 0;
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
@@ -71,31 +81,35 @@ err:
|
||||
int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
size_t resp_size = 0;
|
||||
uint64_t pinUvAuthProtocol = 0, hmacSecretPinUvAuthProtocol = 1;
|
||||
CredOptions options = {0};
|
||||
CredExtensions extensions = {0};
|
||||
CredOptions options = { 0 };
|
||||
CredExtensions extensions = { 0 };
|
||||
CborParser parser;
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = {0}, clientDataHash = {0};
|
||||
CborCharString rpId = {0};
|
||||
PublicKeyCredentialDescriptor allowList[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
CborByteString pinUvAuthParam = { 0 }, clientDataHash = { 0 };
|
||||
CborCharString rpId = { 0 };
|
||||
PublicKeyCredentialDescriptor allowList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
size_t allowList_len = 0, creds_len = 0;
|
||||
uint8_t *aut_data = NULL;
|
||||
bool asserted = false;
|
||||
bool asserted = false, up = true, uv = false;
|
||||
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;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 2 && val_c != val_u)
|
||||
if (val_c <= 2 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_TEXT(rpId, 1);
|
||||
@@ -104,14 +118,17 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_FIELD_GET_BYTES(clientDataHash, 1);
|
||||
}
|
||||
else if (val_u == 0x03) { // excludeList
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2) {
|
||||
PublicKeyCredentialDescriptor *pc = &allowList[allowList_len];
|
||||
CBOR_PARSE_MAP_START(_f2, 3) {
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2)
|
||||
{
|
||||
PublicKeyCredentialDescriptor *pc = &allowList[allowList_len];
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(3);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", pc->id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pc->type);
|
||||
if (strcmp(_fd3, "transports") == 0) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(pc->transports[pc->transports_len], 4);
|
||||
pc->transports_len++;
|
||||
}
|
||||
@@ -125,36 +142,17 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
else if (val_u == 0x04) { // extensions
|
||||
extensions.present = true;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
if (strcmp(_fd2, "hmac-secret") == 0) {
|
||||
extensions.hmac_secret = ptrue;
|
||||
uint64_t ukey = 0;
|
||||
CBOR_PARSE_MAP_START(_f2, 3) {
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_UINT(ukey, 3);
|
||||
if (ukey == 0x01) {
|
||||
int64_t kkey = 0;
|
||||
CBOR_PARSE_MAP_START(_f3, 4) {
|
||||
CBOR_FIELD_GET_INT(kkey, 4);
|
||||
if (kkey == 1) {
|
||||
CBOR_FIELD_GET_INT(kty, 4);
|
||||
}
|
||||
else if (kkey == 3) {
|
||||
CBOR_FIELD_GET_INT(alg, 4);
|
||||
}
|
||||
else if (kkey == -1) {
|
||||
CBOR_FIELD_GET_INT(crv, 4);
|
||||
}
|
||||
else if (kkey == -2) {
|
||||
CBOR_FIELD_GET_BYTES(kax, 4);
|
||||
}
|
||||
else if (kkey == -3) {
|
||||
CBOR_FIELD_GET_BYTES(kay, 4);
|
||||
}
|
||||
else
|
||||
CBOR_ADVANCE(4);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f3, 4);
|
||||
CBOR_CHECK(COSE_read_key(&_f3, &kty, &alg, &crv, &kax, &kay));
|
||||
}
|
||||
else if (ukey == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(salt_enc, 3);
|
||||
@@ -165,20 +163,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
else if (ukey == 0x04) {
|
||||
CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
|
||||
}
|
||||
else
|
||||
else {
|
||||
CBOR_ADVANCE(3);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
continue;
|
||||
}
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x05) { // options
|
||||
options.present = true;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", options.rk);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "up", options.up);
|
||||
@@ -196,9 +198,13 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
if (rpId.present == false || clientDataHash.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t flags = 0;
|
||||
uint8_t rp_id_hash[32];
|
||||
mbedtls_sha256((uint8_t *)rpId.data, rpId.len, rp_id_hash, 0);
|
||||
uint8_t rp_id_hash[32] = {0};
|
||||
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, rp_id_hash, 0);
|
||||
|
||||
bool resident = false;
|
||||
uint8_t numberOfCredentials = 0;
|
||||
@@ -206,18 +212,23 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (next == false) {
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
|
||||
if (check_user_presence() == false)
|
||||
if (check_user_presence() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
if (!file_has_data(ef_pin))
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
else
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (pinUvAuthProtocol == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.present) {
|
||||
@@ -231,69 +242,108 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
//else if (options.up == NULL) //5.7
|
||||
//rup = ptrue;
|
||||
//rup = ptrue;
|
||||
if (options.uv != NULL) {
|
||||
uv = *options.uv;
|
||||
}
|
||||
if (options.up != NULL) {
|
||||
up = *options.up;
|
||||
}
|
||||
}
|
||||
|
||||
if (pinUvAuthParam.present == true) { //6.1
|
||||
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
|
||||
if (ret != CborNoError)
|
||||
int ret = verify((uint8_t)pinUvAuthProtocol, paut.data, clientDataHash.data, (uint16_t)clientDataHash.len, pinUvAuthParam.data);
|
||||
if (ret != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (getUserVerifiedFlagValue() == false)
|
||||
}
|
||||
if (getUserVerifiedFlagValue() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (!(paut.permissions & CTAP_PERMISSION_GA)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
// Check pinUvAuthToken permissions. See 6.2.2.4
|
||||
}
|
||||
if (extensions.present == true && extensions.hmac_secret == ptrue) {
|
||||
if (kax.present == false || kay.present == false || crv == 0 || alg == 0 || salt_enc.present == false || salt_auth.present == false)
|
||||
if (kax.present == false || kay.present == false || crv == 0 || alg == 0 ||
|
||||
salt_enc.present == false || salt_auth.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (salt_enc.len != 32 && salt_enc.len != 64)
|
||||
}
|
||||
if (salt_enc.len != 32 + (hmacSecretPinUvAuthProtocol - 1) * IV_SIZE &&
|
||||
salt_enc.len != 64 + (hmacSecretPinUvAuthProtocol - 1) * IV_SIZE) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
if (allowList_len > 0)
|
||||
{
|
||||
for (int e = 0; e < allowList_len; e++) {
|
||||
if (allowList[e].type.present == false || allowList[e].id.present == false)
|
||||
if (allowList_len > 0) {
|
||||
for (size_t e = 0; e < allowList_len; e++) {
|
||||
if (allowList[e].type.present == false || allowList[e].id.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0)
|
||||
}
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
CBOR_FREE_BYTE_STRING(allowList[e].id);
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
else
|
||||
else {
|
||||
creds_len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_CRED + i);
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0)
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
|
||||
if (ret != 0)
|
||||
}
|
||||
int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
|
||||
if (ret != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
else
|
||||
}
|
||||
else {
|
||||
creds_len++;
|
||||
}
|
||||
}
|
||||
resident = true;
|
||||
}
|
||||
for (int i = 0; i < creds_len; i++) {
|
||||
for (size_t i = 0; i < creds_len; i++) {
|
||||
if (creds[i].present == true) {
|
||||
if (creds[i].extensions.present == true) {
|
||||
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV))
|
||||
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && resident == true && !(flags & FIDO2_AUT_FLAG_UV))
|
||||
}
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
|
||||
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
else
|
||||
creds[numberOfCredentials++] = creds[i];
|
||||
}
|
||||
else {
|
||||
if (numberOfCredentials != i) {
|
||||
creds[numberOfCredentials++] = creds[i];
|
||||
}
|
||||
else {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (numberOfCredentials != i) {
|
||||
creds[numberOfCredentials++] = creds[i];
|
||||
}
|
||||
else {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
}
|
||||
else
|
||||
creds[numberOfCredentials++] = creds[i];
|
||||
}
|
||||
}
|
||||
if (numberOfCredentials == 0)
|
||||
if (numberOfCredentials == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numberOfCredentials; i++) {
|
||||
for (int j = i + 1; j < numberOfCredentials; j++) {
|
||||
@@ -308,14 +358,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (options.up == ptrue || options.present == false || options.up == NULL) { //9.1
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (getUserPresentFlagValue() == false) {
|
||||
if (check_user_presence() == false)
|
||||
if (check_user_presence() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!(flags & FIDO2_AUT_FLAG_UP)) {
|
||||
if (check_user_presence() == false)
|
||||
if (check_user_presence() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
flags |= FIDO2_AUT_FLAG_UP;
|
||||
@@ -324,7 +376,11 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
}
|
||||
|
||||
if (!(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
if (extensions.largeBlobKey == pfalse) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (up == false && uv == false) {
|
||||
selcred = &creds[0];
|
||||
}
|
||||
else {
|
||||
@@ -332,10 +388,11 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (numberOfCredentials > 1) {
|
||||
asserted = true;
|
||||
residentx = resident;
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++)
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
credsx[i] = creds[i];
|
||||
}
|
||||
numberOfCredentialsx = numberOfCredentials;
|
||||
datax = (uint8_t *)calloc(1, len);
|
||||
datax = (uint8_t *) calloc(1, len);
|
||||
memcpy(datax, data, len);
|
||||
lenx = len;
|
||||
flagsx = flags;
|
||||
@@ -350,37 +407,49 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
flags = flagsx;
|
||||
selcred = &credsx[credentialCounter];
|
||||
}
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = fido_load_key(selcred->curve, selcred->id.data, &ekey);
|
||||
if (ret != 0) {
|
||||
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
|
||||
int ret = 0;
|
||||
uint8_t largeBlobKey[32] = {0};
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||
ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ext_len = 0;
|
||||
uint8_t ext [512];
|
||||
uint8_t ext[512] = {0};
|
||||
if (extensions.present == true) {
|
||||
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
|
||||
int l = 0;
|
||||
if (options.up == pfalse)
|
||||
if (options.up == pfalse) {
|
||||
extensions.hmac_secret = NULL;
|
||||
if (extensions.hmac_secret != NULL)
|
||||
}
|
||||
if (extensions.hmac_secret != NULL) {
|
||||
l++;
|
||||
if (extensions.credProtect != 0)
|
||||
}
|
||||
if (credBlob == ptrue) {
|
||||
l++;
|
||||
}
|
||||
if (extensions.thirdPartyPayment != NULL) {
|
||||
l++;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
if (credBlob == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||
if (selcred->extensions.credBlob.present == true) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data,
|
||||
selcred->extensions.credBlob.len));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
|
||||
}
|
||||
}
|
||||
if (extensions.hmac_secret != NULL) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||
|
||||
uint8_t sharedSecret[64];
|
||||
uint8_t sharedSecret[64] = {0};
|
||||
mbedtls_ecp_point Qp;
|
||||
mbedtls_ecp_point_init(&Qp);
|
||||
mbedtls_mpi_lset(&Qp.Z, 1);
|
||||
@@ -392,39 +461,51 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
int ret = ecdh(hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
|
||||
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(hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, salt_enc.len, salt_auth.data) != 0) {
|
||||
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];
|
||||
ret = decrypt(hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, salt_enc.len, salt_dec);
|
||||
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], *crd = NULL;
|
||||
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)
|
||||
if (flags & FIDO2_AUT_FLAG_UV) {
|
||||
crd = cred_random + 32;
|
||||
else
|
||||
}
|
||||
else {
|
||||
crd = cred_random;
|
||||
uint8_t out1[64], hmac_res[64];
|
||||
}
|
||||
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 (salt_enc.len == 64)
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec+32, 32, out1+32);
|
||||
encrypt(hmacSecretPinUvAuthProtocol, sharedSecret, out1, salt_enc.len, hmac_res);
|
||||
if ((uint8_t)salt_enc.len == 64 + poff) {
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
|
||||
}
|
||||
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
||||
}
|
||||
if (extensions.thirdPartyPayment != NULL) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "thirdPartyPayment"));
|
||||
if (selcred->extensions.thirdPartyPayment == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||
}
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||
@@ -434,31 +515,50 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
uint32_t ctr = get_sign_counter();
|
||||
|
||||
size_t aut_data_len = 32 + 1 + 4 + ext_len;
|
||||
aut_data = (uint8_t *)calloc(1, aut_data_len + clientDataHash.len);
|
||||
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
|
||||
uint8_t *pa = aut_data;
|
||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||
*pa++ = flags;
|
||||
*pa++ = ctr >> 24;
|
||||
*pa++ = ctr >> 16;
|
||||
*pa++ = ctr >> 8;
|
||||
*pa++ = ctr & 0xff;
|
||||
pa += put_uint32_t_be(ctr, pa);
|
||||
memcpy(pa, ext, ext_len); pa += ext_len;
|
||||
if (pa-aut_data != aut_data_len)
|
||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
|
||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
|
||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
|
||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
||||
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey);
|
||||
if (ret != 0) {
|
||||
if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
}
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
|
||||
}
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
}
|
||||
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
||||
size_t olen = 0;
|
||||
ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL);
|
||||
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
|
||||
uint8_t lfields = 3;
|
||||
if (selcred->opts.present == true && selcred->opts.rk == ptrue)
|
||||
if (selcred->opts.present == true && selcred->opts.rk == ptrue) {
|
||||
lfields++;
|
||||
if (numberOfCredentials > 1 && next == false)
|
||||
}
|
||||
if (numberOfCredentials > 1 && next == false) {
|
||||
lfields++;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
}
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||
lfields++;
|
||||
}
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
@@ -476,39 +576,75 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
|
||||
if (selcred->opts.present == true && selcred->opts.rk == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 1));
|
||||
uint8_t lu = 1;
|
||||
if (numberOfCredentials > 1 && allowList_len == 0) {
|
||||
if (selcred->userName.present == true) {
|
||||
lu++;
|
||||
}
|
||||
if (selcred->userDisplayName.present == true) {
|
||||
lu++;
|
||||
}
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
|
||||
selcred->userId.len));
|
||||
if (numberOfCredentials > 1 && allowList_len == 0) {
|
||||
if (selcred->userName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, selcred->userName.data));
|
||||
}
|
||||
if (selcred->userDisplayName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "displayName"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, selcred->userDisplayName.data));
|
||||
}
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
}
|
||||
if (numberOfCredentials > 1 && next == false) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
|
||||
}
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
}
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
err:
|
||||
ctr++;
|
||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||
low_flash_available();
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(rpId);
|
||||
CBOR_FREE_BYTE_STRING(kax);
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(salt_enc);
|
||||
CBOR_FREE_BYTE_STRING(salt_auth);
|
||||
if (asserted == false) {
|
||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(rpId);
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
credential_free(&creds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int m = 0; m < allowList_len; m++) {
|
||||
for (size_t m = 0; m < MAX_CREDENTIAL_COUNT_IN_LIST; m++) {
|
||||
CBOR_FREE_BYTE_STRING(allowList[m].type);
|
||||
CBOR_FREE_BYTE_STRING(allowList[m].id);
|
||||
for (int n = 0; n < allowList[m].transports_len; n++) {
|
||||
for (size_t n = 0; n < 8; n++) {
|
||||
CBOR_FREE_BYTE_STRING(allowList[m].transports[n]);
|
||||
}
|
||||
}
|
||||
if (aut_data)
|
||||
if (aut_data) {
|
||||
free(aut_data);
|
||||
}
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = resp_size;
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
@@ -23,10 +24,10 @@
|
||||
#include "version.h"
|
||||
|
||||
int cbor_get_info() {
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder;
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 9));
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
@@ -36,16 +37,22 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 5));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 8));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
|
||||
@@ -53,14 +60,23 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "authnrCfg"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "clientPin"));
|
||||
if (file_has_data(ef_pin))
|
||||
if (file_has_data(ef_pin)) {
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
else
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobs"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "pinUvAuthToken"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "setMinPINLength"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_MSG_SIZE));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, 1)); // PIN protocols
|
||||
@@ -73,16 +89,50 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
|
||||
CBOR_CHECK(COSE_public_key(FIDO2_ALG_ES256, &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_ES256K, &arrayEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_LARGE_BLOB_SIZE)); // maxSerializedLargeBlobArray
|
||||
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D));
|
||||
if (file_has_data(ef_minpin)) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, *file_get_data(ef_minpin))); // minPINLength
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
|
||||
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0E));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
err:
|
||||
if (error != CborNoError)
|
||||
err:
|
||||
if (error != CborNoError) {
|
||||
return -CTAP2_ERR_INVALID_CBOR;
|
||||
res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
|
||||
}
|
||||
res_APDU_size = (uint16_t)cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
169
src/fido/cbor_large_blobs.c
Normal file
169
src/fido/cbor_large_blobs.c
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
static uint64_t expectedLength = 0, expectedNextOffset = 0;
|
||||
uint8_t temp_lba[MAX_LARGE_BLOB_SIZE];
|
||||
|
||||
int cbor_large_blobs(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborEncoder encoder, mapEncoder;
|
||||
CborError error = CborNoError;
|
||||
uint64_t get = 0, offset = UINT64_MAX, length = 0, pinUvAuthProtocol = 0;
|
||||
CborByteString set = { 0 }, pinUvAuthParam = { 0 };
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 0 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(get, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(set, 1);
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(offset, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_UINT(length, 1);
|
||||
}
|
||||
else if (val_u == 0x05) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
else if (val_u == 0x06) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
if (offset == UINT64_MAX) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (get == 0 && set.present == false) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (get != 0 && set.present == true) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
if (get > 0) {
|
||||
if (length != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (length > MAX_FRAGMENT_LENGTH) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
|
||||
}
|
||||
if (offset > file_get_size(ef_largeblob)) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_largeblob) + offset,
|
||||
MIN(get, file_get_size(ef_largeblob) - offset)));
|
||||
}
|
||||
else {
|
||||
if (set.len > MAX_FRAGMENT_LENGTH) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
|
||||
}
|
||||
if (offset == 0) {
|
||||
if (length == 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (length > MAX_LARGE_BLOB_SIZE) {
|
||||
CBOR_ERROR(CTAP2_ERR_LARGE_BLOB_STORAGE_FULL);
|
||||
}
|
||||
if (length < 17) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
expectedLength = length;
|
||||
expectedNextOffset = 0;
|
||||
}
|
||||
else {
|
||||
if (length != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
if (offset != expectedNextOffset) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_SEQ);
|
||||
}
|
||||
if (pinUvAuthParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
uint8_t verify_data[70] = { 0 };
|
||||
memset(verify_data, 0xff, 32);
|
||||
verify_data[32] = 0x0C;
|
||||
put_uint32_t_le(offset, verify_data + 34);
|
||||
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (!(paut.permissions & CTAP_PERMISSION_LBW)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (offset + set.len > expectedLength) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (offset == 0) {
|
||||
memset(temp_lba, 0, sizeof(temp_lba));
|
||||
}
|
||||
memcpy(temp_lba + expectedNextOffset, set.data, set.len);
|
||||
expectedNextOffset += set.len;
|
||||
if (expectedNextOffset == expectedLength) {
|
||||
uint8_t sha[32];
|
||||
mbedtls_sha256(temp_lba, expectedLength - 16, sha, 0);
|
||||
if (expectedLength > 17 && memcmp(sha, temp_lba + expectedLength - 16, 16) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
||||
}
|
||||
file_put_data(ef_largeblob, temp_lba, (uint16_t)expectedLength);
|
||||
low_flash_available();
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(set);
|
||||
if (error != CborNoError) {
|
||||
return -CTAP2_ERR_INVALID_CBOR;
|
||||
}
|
||||
res_APDU_size = (uint16_t)cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
return 0;
|
||||
}
|
||||
@@ -15,54 +15,58 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "hsm.h"
|
||||
#include <math.h>
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "random.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString clientDataHash = {0}, pinUvAuthParam = {0};
|
||||
PublicKeyCredentialRpEntity rp = {0};
|
||||
PublicKeyCredentialUserEntity user = {0};
|
||||
PublicKeyCredentialParameters pubKeyCredParams[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
CborByteString clientDataHash = { 0 }, pinUvAuthParam = { 0 };
|
||||
PublicKeyCredentialRpEntity rp = { 0 };
|
||||
PublicKeyCredentialUserEntity user = { 0 };
|
||||
PublicKeyCredentialParameters pubKeyCredParams[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
size_t pubKeyCredParams_len = 0;
|
||||
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
size_t excludeList_len = 0;
|
||||
CredOptions options = {0};
|
||||
CredOptions options = { 0 };
|
||||
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
|
||||
uint8_t *aut_data = NULL;
|
||||
size_t resp_size = 0;
|
||||
CredExtensions extensions = {0};
|
||||
CredExtensions extensions = { 0 };
|
||||
//options.present = true;
|
||||
options.up = ptrue;
|
||||
//options.up = ptrue;
|
||||
options.uv = pfalse;
|
||||
options.rk = pfalse;
|
||||
//options.rk = pfalse;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 4 && val_c != val_u)
|
||||
if (val_c <= 4 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) { // clientDataHash
|
||||
CBOR_FIELD_GET_BYTES(clientDataHash, 1);
|
||||
}
|
||||
else if (val_u == 0x02) { // rp
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "id", rp.id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", rp.parent.name);
|
||||
@@ -70,18 +74,22 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x03) { // user
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "id", user.id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "name", user.parent.name);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(2, "displayName", user.displayName);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x04) { // pubKeyCredParams
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2) {
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2)
|
||||
{
|
||||
PublicKeyCredentialParameters *pk = &pubKeyCredParams[pubKeyCredParams_len];
|
||||
CBOR_PARSE_MAP_START(_f2, 3) {
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(3);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pk->type);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_INT(3, "alg", pk->alg);
|
||||
@@ -92,14 +100,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_PARSE_ARRAY_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x05) { // excludeList
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2) {
|
||||
PublicKeyCredentialDescriptor *pc = &excludeList[excludeList_len];
|
||||
CBOR_PARSE_MAP_START(_f2, 3) {
|
||||
CBOR_PARSE_ARRAY_START(_f1, 2)
|
||||
{
|
||||
PublicKeyCredentialDescriptor *pc = &excludeList[excludeList_len];
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(3);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(3, "id", pc->id);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_TEXT(3, "type", pc->type);
|
||||
if (strcmp(_fd3, "transports") == 0) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(pc->transports[pc->transports_len], 4);
|
||||
pc->transports_len++;
|
||||
}
|
||||
@@ -113,17 +124,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
}
|
||||
else if (val_u == 0x06) { // extensions
|
||||
extensions.present = true;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x07) { // options
|
||||
options.present = true;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "rk", options.rk);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "up", options.up);
|
||||
@@ -145,52 +162,80 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
uint8_t flags = FIDO2_AUT_FLAG_AT;
|
||||
uint8_t rp_id_hash[32];
|
||||
mbedtls_sha256((uint8_t *)rp.id.data, rp.id.len, rp_id_hash, 0);
|
||||
|
||||
int curve = -1, alg = 0;
|
||||
if (pubKeyCredParams_len == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
|
||||
for (int i = 0; i < pubKeyCredParams_len; i++) {
|
||||
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0)
|
||||
continue;
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256)
|
||||
curve = FIDO2_CURVE_P256;
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384)
|
||||
curve = FIDO2_CURVE_P384;
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512)
|
||||
curve = FIDO2_CURVE_P521;
|
||||
else if (pubKeyCredParams[i].alg == 0) // no present
|
||||
curve = -1;
|
||||
else
|
||||
curve = 0;
|
||||
if (curve > 0) {
|
||||
alg = pubKeyCredParams[i].alg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (curve == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||
else if (curve == -1)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
uint8_t rp_id_hash[32] = {0};
|
||||
mbedtls_sha256((uint8_t *) rp.id.data, rp.id.len, rp_id_hash, 0);
|
||||
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (pinUvAuthParam.len == 0 || pinUvAuthParam.data == NULL) {
|
||||
if (check_user_presence() == false)
|
||||
if (check_user_presence() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
if (!file_has_data(ef_pin))
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
else
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (pinUvAuthProtocol == 0)
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int curve = -1, alg = 0;
|
||||
if (pubKeyCredParams_len == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < pubKeyCredParams_len; i++) {
|
||||
if (pubKeyCredParams[i].type.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
if (pubKeyCredParams[i].alg == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
|
||||
}
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P256;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P384;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P521;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P256K1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
|
||||
// pass
|
||||
}
|
||||
//else {
|
||||
// CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
|
||||
//}
|
||||
if (curve > 0 && alg == 0) {
|
||||
alg = (int)pubKeyCredParams[i].alg;
|
||||
}
|
||||
}
|
||||
if (curve <= 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_ALGORITHM);
|
||||
}
|
||||
|
||||
if (options.present) {
|
||||
if (options.uv == ptrue) { //5.3
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
@@ -199,78 +244,128 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
//else if (options.up == NULL) //5.7
|
||||
//rup = ptrue;
|
||||
//rup = ptrue;
|
||||
}
|
||||
if (pinUvAuthParam.present == false && options.uv != ptrue && file_has_data(ef_pin)) { //8.1
|
||||
if (pinUvAuthParam.present == false && options.uv == pfalse && file_has_data(ef_pin)) { //8.1
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (enterpriseAttestation > 0) {
|
||||
if (!(get_opts() & FIDO2_OPT_EA)) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (enterpriseAttestation != 1 && enterpriseAttestation != 2) { //9.2.1
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
//Unfinished. See 6.1.2.9
|
||||
}
|
||||
if (pinUvAuthParam.present == true) { //11.1
|
||||
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
|
||||
if (ret != CborNoError)
|
||||
int ret = verify((uint8_t)pinUvAuthProtocol, paut.data, clientDataHash.data, (uint16_t)clientDataHash.len, pinUvAuthParam.data);
|
||||
if (ret != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (getUserVerifiedFlagValue() == false)
|
||||
}
|
||||
if (!(paut.permissions & CTAP_PERMISSION_MC)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (getUserVerifiedFlagValue() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
// Check pinUvAuthToken permissions. See 6.1.2.11
|
||||
if (paut.has_rp_id == false) {
|
||||
memcpy(paut.rp_id_hash, rp_id_hash, 32);
|
||||
paut.has_rp_id = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int e = 0; e < excludeList_len; e++) { //12.1
|
||||
if (excludeList[e].type.present == false || excludeList[e].id.present == false)
|
||||
for (size_t e = 0; e < excludeList_len; e++) { //12.1
|
||||
if (excludeList[e].type.present == false || excludeList[e].id.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (strcmp(excludeList[e].type.data, "public-key") != 0)
|
||||
}
|
||||
if (strcmp(excludeList[e].type.data, (char *)"public-key") != 0) {
|
||||
continue;
|
||||
Credential ecred;
|
||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || flags & FIDO2_AUT_FLAG_UV))
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
Credential ecred = {0};
|
||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
|
||||
&ecred) == 0 &&
|
||||
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
|
||||
(flags & FIDO2_AUT_FLAG_UV))) {
|
||||
credential_free(&ecred);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
credential_free(&ecred);
|
||||
}
|
||||
|
||||
if (options.up == ptrue) { //14.1
|
||||
if (extensions.largeBlobKey == pfalse ||
|
||||
(extensions.largeBlobKey == ptrue && options.rk != ptrue)) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (options.up == ptrue || options.up == NULL) { //14.1
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (getUserPresentFlagValue() == false) {
|
||||
if (check_user_presence() == false)
|
||||
if (check_user_presence() == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
}
|
||||
}
|
||||
}
|
||||
flags |= FIDO2_AUT_FLAG_UP;
|
||||
clearUserPresentFlag();
|
||||
clearUserVerifiedFlag();
|
||||
clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
if (options.up == ptrue) {
|
||||
clearUserPresentFlag();
|
||||
clearUserVerifiedFlag();
|
||||
clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
}
|
||||
}
|
||||
|
||||
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
|
||||
|
||||
uint8_t cred_id[MAX_CRED_ID_LENGTH];
|
||||
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
|
||||
size_t cred_id_len = 0;
|
||||
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options,
|
||||
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
|
||||
cred_id, &cred_id_len));
|
||||
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = fido_load_key(curve, cred_id, &ekey);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
|
||||
if (getUserVerifiedFlagValue())
|
||||
if (getUserVerifiedFlagValue()) {
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
}
|
||||
size_t ext_len = 0;
|
||||
uint8_t ext [512];
|
||||
uint8_t ext[512] = {0};
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
if (extensions.present == true) {
|
||||
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
|
||||
int l = 0;
|
||||
if (extensions.hmac_secret != NULL)
|
||||
uint8_t minPinLen = 0;
|
||||
if (extensions.hmac_secret != NULL) {
|
||||
l++;
|
||||
if (extensions.credProtect != 0)
|
||||
}
|
||||
if (extensions.credProtect != 0) {
|
||||
l++;
|
||||
}
|
||||
if (extensions.minPinLength != NULL) {
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin)) {
|
||||
uint8_t *minpin_data = file_get_data(ef_minpin);
|
||||
for (int o = 2; o < file_get_size(ef_minpin); o += 32) {
|
||||
if (memcmp(minpin_data + o, rp_id_hash, 32) == 0) {
|
||||
minPinLen = minpin_data[0];
|
||||
if (minPinLen > 0) {
|
||||
l++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extensions.credBlob.present == true) {
|
||||
l++;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credBlob.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
|
||||
}
|
||||
if (extensions.credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
@@ -280,70 +375,107 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
|
||||
}
|
||||
if (minPinLen > 0) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||
flags |= FIDO2_AUT_FLAG_ED;
|
||||
}
|
||||
uint8_t pkey[66];
|
||||
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
|
||||
if (cinfo == NULL)
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = fido_load_key(curve, cred_id, &ekey);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
size_t olen = 0, pkey_len = ceil((float)cinfo->bit_size/8);
|
||||
}
|
||||
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
|
||||
if (cinfo == NULL) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
size_t olen = 0;
|
||||
uint32_t ctr = get_sign_counter();
|
||||
uint8_t cbor_buf[1024];
|
||||
uint8_t cbor_buf[1024] = {0};
|
||||
cbor_encoder_init(&encoder, cbor_buf, sizeof(cbor_buf), 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 5));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 3));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, -alg));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, curve));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 2));
|
||||
mbedtls_mpi_write_binary(&ekey.Q.X, pkey, pkey_len);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, pkey_len));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder, 3));
|
||||
mbedtls_mpi_write_binary(&ekey.Q.Y, pkey, pkey_len);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pkey, pkey_len));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
|
||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
|
||||
|
||||
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
|
||||
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;
|
||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||
*pa++ = flags;
|
||||
*pa++ = ctr >> 24;
|
||||
*pa++ = ctr >> 16;
|
||||
*pa++ = ctr >> 8;
|
||||
*pa++ = ctr & 0xff;
|
||||
pa += put_uint32_t_be(ctr, pa);
|
||||
memcpy(pa, aaguid, 16); pa += 16;
|
||||
*pa++ = cred_id_len >> 8;
|
||||
*pa++ = cred_id_len & 0xff;
|
||||
memcpy(pa, cred_id, cred_id_len); pa += cred_id_len;
|
||||
memcpy(pa, cbor_buf, rs); pa += rs;
|
||||
memcpy(pa, ext, ext_len); pa += ext_len;
|
||||
if (pa-aut_data != aut_data_len)
|
||||
pa += put_uint16_t_be(cred_id_len, pa);
|
||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
||||
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
|
||||
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
|
||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
|
||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
|
||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
|
||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
||||
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
|
||||
}
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
}
|
||||
ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash);
|
||||
|
||||
bool self_attestation = true;
|
||||
if (ka && ka->use_self_attestation == pfalse) {
|
||||
if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
|
||||
uint8_t key[32] = {0};
|
||||
if (load_keydev(key) != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, key, 32);
|
||||
mbedtls_platform_zeroize(key, sizeof(key));
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
self_attestation = false;
|
||||
}
|
||||
ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL);
|
||||
ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 3));
|
||||
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) {
|
||||
file_put_data(ef_phy, user.id.data, user.id.len);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t largeBlobKey[32] = {0};
|
||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||
ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
|
||||
@@ -351,54 +483,77 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
||||
if (self_attestation == false) {
|
||||
if (self_attestation == false || is_nitrokey) {
|
||||
CborEncoder arrEncoder;
|
||||
file_t *ef_cert = NULL;
|
||||
if (enterpriseAttestation == 2) {
|
||||
ef_cert = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
}
|
||||
if (!file_has_data(ef_cert)) {
|
||||
ef_cert = ef_certdev;
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "x5c"));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder2, &arrEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_certdev), file_get_size(ef_certdev)));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_cert), file_get_size(ef_cert)));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder2, &arrEncoder));
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2));
|
||||
|
||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
}
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
if (options.rk == ptrue) {
|
||||
if (credential_store(cred_id, cred_id_len, rp_id_hash) != 0)
|
||||
if (credential_store(cred_id, cred_id_len, rp_id_hash) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_KEY_STORE_FULL);
|
||||
}
|
||||
}
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||
ctr++;
|
||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||
low_flash_available();
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(clientDataHash);
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(rp.id);
|
||||
CBOR_FREE_BYTE_STRING(rp.parent.name);
|
||||
CBOR_FREE_BYTE_STRING(user.id);
|
||||
CBOR_FREE_BYTE_STRING(user.displayName);
|
||||
CBOR_FREE_BYTE_STRING(user.parent.name);
|
||||
for (int n = 0; n < pubKeyCredParams_len; n++) {
|
||||
if (extensions.present == true) {
|
||||
CBOR_FREE_BYTE_STRING(extensions.credBlob);
|
||||
}
|
||||
for (size_t n = 0; n < MAX_CREDENTIAL_COUNT_IN_LIST; n++) {
|
||||
CBOR_FREE_BYTE_STRING(pubKeyCredParams[n].type);
|
||||
}
|
||||
|
||||
for (int m = 0; m < excludeList_len; m++) {
|
||||
for (size_t m = 0; m < MAX_CREDENTIAL_COUNT_IN_LIST; m++) {
|
||||
CBOR_FREE_BYTE_STRING(excludeList[m].type);
|
||||
CBOR_FREE_BYTE_STRING(excludeList[m].id);
|
||||
for (int n = 0; n < excludeList[m].transports_len; n++) {
|
||||
for (size_t n = 0; n < excludeList[m].transports_len; n++) {
|
||||
CBOR_FREE_BYTE_STRING(excludeList[m].transports[n]);
|
||||
}
|
||||
}
|
||||
if (aut_data)
|
||||
if (aut_data) {
|
||||
free(aut_data);
|
||||
}
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = resp_size;
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,27 +18,18 @@
|
||||
#ifndef _CBOR_MAKE_CREDENTIAL_H_
|
||||
#define _CBOR_MAKE_CREDENTIAL_H_
|
||||
|
||||
#include "common.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include <stdlib.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
typedef struct PublicKeyCredentialEntity
|
||||
{
|
||||
typedef struct PublicKeyCredentialEntity {
|
||||
CborCharString name;
|
||||
} PublicKeyCredentialEntity;
|
||||
|
||||
typedef struct PublicKeyCredentialRpEntity
|
||||
{
|
||||
typedef struct PublicKeyCredentialRpEntity {
|
||||
PublicKeyCredentialEntity parent;
|
||||
CborCharString id;
|
||||
} PublicKeyCredentialRpEntity;
|
||||
|
||||
typedef struct PublicKeyCredentialUserEntity
|
||||
{
|
||||
typedef struct PublicKeyCredentialUserEntity {
|
||||
PublicKeyCredentialEntity parent;
|
||||
CborByteString id;
|
||||
CborCharString displayName;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
@@ -16,23 +15,31 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "file.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#ifdef ESP_PLATFORM
|
||||
#include "esp_compat.h"
|
||||
#endif
|
||||
#include "fs/phy.h"
|
||||
|
||||
extern void scan_all();
|
||||
|
||||
int cbor_reset() {
|
||||
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET==1
|
||||
if (board_millis() > 10000)
|
||||
#ifndef ENABLE_EMULATION
|
||||
#if defined(ENABLE_POWER_ON_RESET) && ENABLE_POWER_ON_RESET == 1
|
||||
if (!(phy_data.opts & PHY_OPT_DISABLE_POWER_RESET) && board_millis() > 10000) {
|
||||
return CTAP2_ERR_NOT_ALLOWED;
|
||||
}
|
||||
#endif
|
||||
if (wait_button_pressed() == true)
|
||||
if (wait_button_pressed() == true) {
|
||||
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
||||
}
|
||||
#endif
|
||||
initialize_flash(true);
|
||||
init_fido(true);
|
||||
init_fido();
|
||||
return 0;
|
||||
}
|
||||
|
||||
26
src/fido/cbor_selection.c
Normal file
26
src/fido/cbor_selection.c
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 "ctap.h"
|
||||
|
||||
int cbor_selection() {
|
||||
if (wait_button_pressed() == true) {
|
||||
return CTAP2_ERR_USER_ACTION_TIMEOUT;
|
||||
}
|
||||
return CTAP2_OK;
|
||||
}
|
||||
315
src/fido/cbor_vendor.c
Normal file
315
src/fido/cbor_vendor.c
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/hkdf.h"
|
||||
#include "mbedtls/x509_csr.h"
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
|
||||
mse_t mse = { .init = false };
|
||||
|
||||
int mse_decrypt_ct(uint8_t *data, size_t len) {
|
||||
mbedtls_chachapoly_context chatx;
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12);
|
||||
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorParam = { 0 }, kax = { 0 }, kay = { 0 };
|
||||
size_t resp_size = 0;
|
||||
uint64_t vendorCmd = 0, pinUvAuthProtocol = 0;
|
||||
int64_t kty = 0, alg = 0, crv = 0;
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 1 && val_c != val_u) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (val_u < val_c) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
}
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCmd, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
uint64_t subpara = 0;
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_BYTES(vendorParam, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_CHECK(COSE_read_key(&_f2, &kty, &alg, &crv, &kax, &kay));
|
||||
}
|
||||
else {
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
|
||||
if (cmd == CTAP_VENDOR_BACKUP) {
|
||||
if (vendorCmd == 0x01) {
|
||||
if (has_keydev_dec == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc)));
|
||||
}
|
||||
else if (vendorCmd == 0x02) {
|
||||
if (vendorParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
uint8_t zeros[32];
|
||||
memset(zeros, 0, sizeof(zeros));
|
||||
file_put_data(ef_keydev_enc, vendorParam.data, (uint16_t)vendorParam.len);
|
||||
file_put_data(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_MSE) {
|
||||
if (vendorCmd == 0x01) { // KeyAgreement
|
||||
if (kax.present == false || kay.present == false || alg == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
mbedtls_ecdh_context hkey;
|
||||
mbedtls_ecdh_init(&hkey);
|
||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
|
||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t buf[MBEDTLS_ECP_MAX_BYTES];
|
||||
size_t olen = 0;
|
||||
ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Qp, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, mse.Qpt,sizeof(mse.Qpt));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
mbedtls_platform_zeroize(buf, sizeof(buf));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, olen, mse.Qpt, sizeof(mse.Qpt), mse.key_enc, sizeof(mse.key_enc));
|
||||
mbedtls_platform_zeroize(buf, sizeof(buf));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
mse.init = true;
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(COSE_key_shared(&hkey, &mapEncoder, &mapEncoder2));
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
}
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_UNLOCK) {
|
||||
if (mse.init == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorParam.data, vendorParam.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
if (!file_has_data(ef_keydev_enc)) {
|
||||
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
||||
}
|
||||
|
||||
uint8_t *keyenc = file_get_data(ef_keydev_enc);
|
||||
size_t keyenc_len = file_get_size(ef_keydev_enc);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorParam.data);
|
||||
ret = mbedtls_chachapoly_auth_decrypt(&chatx, sizeof(keydev_dec), keyenc, NULL, 0, keyenc + keyenc_len - 16, keyenc + 12, keydev_dec);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
has_keydev_dec = true;
|
||||
goto err;
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_EA) {
|
||||
if (vendorCmd == 0x01) {
|
||||
uint8_t buffer[1024];
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
ret = mbedtls_ecp_mul(&ekey.grp, &ekey.Q, &ekey.d, &ekey.grp.G, random_gen, NULL);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
mbedtls_x509write_csr ctx;
|
||||
mbedtls_x509write_csr_init(&ctx);
|
||||
snprintf((char *) buffer, sizeof(buffer), "C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %s", pico_serial_str);
|
||||
mbedtls_x509write_csr_set_subject_name(&ctx, (char *) buffer);
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_pk_init(&key);
|
||||
mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
|
||||
key.pk_ctx = &ekey;
|
||||
mbedtls_x509write_csr_set_key(&ctx, &key);
|
||||
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
|
||||
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
|
||||
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
if (ret <= 0) {
|
||||
mbedtls_x509write_csr_free(&ctx);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
|
||||
}
|
||||
else if (vendorCmd == 0x02) {
|
||||
if (vendorParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (ef_ee_ea) {
|
||||
file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len);
|
||||
}
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
|
||||
if (vendorCmd == 0x01) {
|
||||
uint16_t opts = 0;
|
||||
if (file_has_data(ef_phy)) {
|
||||
uint8_t *data = file_get_data(ef_phy);
|
||||
opts = get_uint16_t_be(data + PHY_OPTS);
|
||||
}
|
||||
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 {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorParam);
|
||||
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = (uint16_t)resp_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbor_vendor(const uint8_t *data, size_t len) {
|
||||
if (len == 0) {
|
||||
return CTAP1_ERR_INVALID_LEN;
|
||||
}
|
||||
if (data[0] >= CTAP_VENDOR_BACKUP) {
|
||||
return cbor_vendor_generic(data[0], data + 1, len - 1);
|
||||
}
|
||||
return CTAP2_ERR_INVALID_CBOR;
|
||||
}
|
||||
@@ -16,59 +16,60 @@
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "mbedtls/ecdsa.h"
|
||||
#include "random.h"
|
||||
#include "files.h"
|
||||
#include "credential.h"
|
||||
|
||||
int cmd_authenticate() {
|
||||
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *)apdu.data;
|
||||
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *)res_APDU;
|
||||
//if (scan_files(true) != CCID_OK)
|
||||
CTAP_AUTHENTICATE_REQ *req = (CTAP_AUTHENTICATE_REQ *) apdu.data;
|
||||
CTAP_AUTHENTICATE_RESP *resp = (CTAP_AUTHENTICATE_RESP *) res_APDU;
|
||||
//if (scan_files(true) != PICOKEY_OK)
|
||||
// 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();
|
||||
if (req->keyHandleLen < KEY_HANDLE_LEN)
|
||||
}
|
||||
if (req->keyHandleLen < KEY_HANDLE_LEN) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
if (P1(apdu) == CTAP_AUTH_ENFORCE && wait_button_pressed() == true)
|
||||
}
|
||||
if (P1(apdu) == CTAP_AUTH_ENFORCE && wait_button_pressed() == true) {
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = 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);
|
||||
if (credential_verify(tmp_kh, req->keyHandleLen, req->appId) == 0) {
|
||||
DEBUG_DATA(req->keyHandle, req->keyHandleLen);
|
||||
ret = fido_load_key(FIDO2_CURVE_P256, req->keyHandle, &key);
|
||||
}
|
||||
else {
|
||||
ret = derive_key(req->appId, false, req->keyHandle, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||
if (verify_key(req->appId, req->keyHandle, &key) != 0) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
free(tmp_kh);
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
}
|
||||
free(tmp_kh);
|
||||
if (ret != CCID_OK) {
|
||||
if (ret != PICOKEY_OK) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
if (verify_key(req->appId, req->keyHandle, &key) != 0) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (P1(apdu) == CTAP_AUTH_CHECK_ONLY) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
resp->flags = 0;
|
||||
resp->flags |= P1(apdu) == CTAP_AUTH_ENFORCE ? CTAP_AUTH_FLAG_TUP : 0x0;
|
||||
uint32_t ctr = get_sign_counter();
|
||||
resp->ctr[0] = ctr >> 24;
|
||||
resp->ctr[1] = ctr >> 16;
|
||||
resp->ctr[2] = ctr >> 8;
|
||||
resp->ctr[3] = ctr & 0xff;
|
||||
put_uint32_t_be(ctr, resp->ctr);
|
||||
uint8_t hash[32], sig_base[CTAP_APPID_SIZE + 1 + 4 + CTAP_CHAL_SIZE];
|
||||
memcpy(sig_base, req->appId, CTAP_APPID_SIZE);
|
||||
memcpy(sig_base+CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
|
||||
memcpy(sig_base + CTAP_APPID_SIZE, &resp->flags, sizeof(uint8_t));
|
||||
memcpy(sig_base + CTAP_APPID_SIZE + 1, resp->ctr, 4);
|
||||
memcpy(sig_base + CTAP_APPID_SIZE + 1 + 4, req->chal, CTAP_CHAL_SIZE);
|
||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sig_base, sizeof(sig_base), hash);
|
||||
@@ -77,14 +78,15 @@ int cmd_authenticate() {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
size_t olen = 0;
|
||||
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *)resp->sig, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
||||
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->sig, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
res_APDU_size = 1 + 4 + olen;
|
||||
}
|
||||
res_APDU_size = 1 + 4 + (uint16_t)olen;
|
||||
|
||||
ctr++;
|
||||
flash_write_data_to_file(ef_counter, (uint8_t *)&ctr, sizeof(ctr));
|
||||
file_put_data(ef_counter, (uint8_t *) &ctr, sizeof(ctr));
|
||||
low_flash_available();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
@@ -16,58 +16,132 @@
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "mbedtls/ecdsa.h"
|
||||
#include "random.h"
|
||||
#include "files.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "management.h"
|
||||
|
||||
const uint8_t u2f_aid[] = {
|
||||
7,
|
||||
0xA0, 0x00, 0x00, 0x05, 0x27, 0x10, 0x02
|
||||
};
|
||||
|
||||
int u2f_unload();
|
||||
int u2f_process_apdu();
|
||||
|
||||
int u2f_select(app_t *a, uint8_t force) {
|
||||
(void) force;
|
||||
if (cap_supported(CAP_U2F)) {
|
||||
a->process_apdu = u2f_process_apdu;
|
||||
a->unload = u2f_unload;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
INITIALIZER ( u2f_ctor ) {
|
||||
register_app(u2f_select, u2f_aid);
|
||||
}
|
||||
|
||||
int u2f_unload() {
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
const uint8_t *bogus_firefox = (const uint8_t *) "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
const uint8_t *bogus_chrome = (const uint8_t *) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
|
||||
extern int ctap_error(uint8_t error);
|
||||
int cmd_register() {
|
||||
CTAP_REGISTER_REQ *req = (CTAP_REGISTER_REQ *)apdu.data;
|
||||
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *)res_APDU;
|
||||
CTAP_REGISTER_REQ *req = (CTAP_REGISTER_REQ *) apdu.data;
|
||||
CTAP_REGISTER_RESP *resp = (CTAP_REGISTER_RESP *) res_APDU;
|
||||
resp->registerId = CTAP_REGISTER_ID;
|
||||
resp->keyHandleLen = KEY_HANDLE_LEN;
|
||||
//if (scan_files(true) != CCID_OK)
|
||||
//if (scan_files(true) != PICOKEY_OK)
|
||||
// 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();
|
||||
if (wait_button_pressed() == true)
|
||||
}
|
||||
if (wait_button_pressed() == true) {
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
if (memcmp(req->appId, bogus_firefox,
|
||||
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
|
||||
#ifndef ENABLE_EMULATION
|
||||
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
|
||||
#else
|
||||
{ return SW_DATA_INVALID(); }
|
||||
#endif
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||
if (ret != CCID_OK) {
|
||||
if (ret != PICOKEY_OK) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
size_t olen = 0;
|
||||
ret = mbedtls_ecp_point_write_binary(&key.grp, &key.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (uint8_t *)&resp->pubKey, CTAP_EC_POINT_SIZE);
|
||||
ret = mbedtls_ecp_point_write_binary(&key.grp, &key.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, (uint8_t *) &resp->pubKey, CTAP_EC_POINT_SIZE);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
size_t ef_certdev_size = file_get_size(ef_certdev);
|
||||
uint16_t ef_certdev_size = file_get_size(ef_certdev);
|
||||
memcpy(resp->keyHandleCertSig + KEY_HANDLE_LEN, file_get_data(ef_certdev), ef_certdev_size);
|
||||
uint8_t hash[32], sign_base[1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN + CTAP_EC_POINT_SIZE];
|
||||
sign_base[0] = CTAP_REGISTER_HASH_ID;
|
||||
memcpy(sign_base + 1, req->appId, CTAP_APPID_SIZE);
|
||||
memcpy(sign_base + 1 + CTAP_APPID_SIZE, req->chal, CTAP_CHAL_SIZE);
|
||||
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE, resp->keyHandleCertSig, KEY_HANDLE_LEN);
|
||||
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN, (uint8_t *)&resp->pubKey, CTAP_EC_POINT_SIZE);
|
||||
memcpy(sign_base + 1 + CTAP_APPID_SIZE + CTAP_CHAL_SIZE + KEY_HANDLE_LEN, (uint8_t *) &resp->pubKey, CTAP_EC_POINT_SIZE);
|
||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), sign_base, sizeof(sign_base), hash);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
mbedtls_ecdsa_init(&key);
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), 32);
|
||||
if (ret != CCID_OK) {
|
||||
uint8_t key_dev[32] = {0};
|
||||
ret = load_keydev(key_dev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, key_dev, 32);
|
||||
mbedtls_platform_zeroize(key_dev, sizeof(key_dev));
|
||||
if (ret != PICOKEY_OK) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
ret = mbedtls_ecdsa_write_signature(&key, MBEDTLS_MD_SHA256, hash, 32, (uint8_t *)resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
||||
ret = mbedtls_ecdsa_write_signature(&key,MBEDTLS_MD_SHA256, hash, 32, (uint8_t *) resp->keyHandleCertSig + KEY_HANDLE_LEN + ef_certdev_size, CTAP_MAX_EC_SIG_SIZE, &olen, random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
res_APDU_size = sizeof(CTAP_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN + ef_certdev_size + olen;
|
||||
}
|
||||
res_APDU_size = sizeof(CTAP_REGISTER_RESP) - sizeof(resp->keyHandleCertSig) + KEY_HANDLE_LEN + ef_certdev_size + (uint16_t)olen;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
extern int cmd_register();
|
||||
extern int cmd_authenticate();
|
||||
extern int cmd_version();
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ CTAP_REGISTER, cmd_register },
|
||||
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
||||
{ CTAP_VERSION, cmd_version },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int u2f_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
if (cap_supported(CAP_U2F)) {
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
@@ -15,12 +15,11 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "apdu.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
int cmd_version() {
|
||||
memcpy(res_APDU, "U2F_V2", strlen("U2F_V2"));
|
||||
res_APDU_size = strlen("U2F_V2");
|
||||
res_APDU_size = (uint16_t)strlen("U2F_V2");
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
@@ -15,38 +15,53 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "credential.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
#include "files.h"
|
||||
#include "file.h"
|
||||
#include "hsm.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
int credential_derive_chacha_key(uint8_t *outk);
|
||||
|
||||
int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
|
||||
if (cred_id_len < 4+12+16)
|
||||
return -1;
|
||||
uint8_t key[32], *iv = cred_id + 4, *cipher = cred_id + 4 + 12, *tag = cred_id + cred_id_len - 16;
|
||||
if (cred_id_len < 4 + 12 + 16) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t key[32], *iv = cred_id + 4, *cipher = cred_id + 4 + 12,
|
||||
*tag = cred_id + cred_id_len - 16;
|
||||
memset(key, 0, sizeof(key));
|
||||
credential_derive_chacha_key(key);
|
||||
mbedtls_chachapoly_context chatx;
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, key);
|
||||
return mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
|
||||
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_t *cred_id_len) {
|
||||
int credential_create(CborCharString *rpId,
|
||||
CborByteString *userId,
|
||||
CborCharString *userName,
|
||||
CborCharString *userDisplayName,
|
||||
CredOptions *opts,
|
||||
CredExtensions *extensions,
|
||||
bool use_sign_count,
|
||||
int alg,
|
||||
int curve,
|
||||
uint8_t *cred_id,
|
||||
size_t *cred_id_len) {
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
uint8_t rp_id_hash[32];
|
||||
mbedtls_sha256((uint8_t *)rpId->data, rpId->len, rp_id_hash, 0);
|
||||
cbor_encoder_init(&encoder, cred_id+4+12, MAX_CRED_ID_LENGTH-(4+12+16), 0);
|
||||
mbedtls_sha256((uint8_t *) rpId->data, rpId->len, rp_id_hash, 0);
|
||||
cbor_encoder_init(&encoder, cred_id + 4 + 12, MAX_CRED_ID_LENGTH - (4 + 12 + 16), 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, CborIndefiniteLength));
|
||||
CBOR_APPEND_KEY_UINT_VAL_STRING(mapEncoder, 0x01, *rpId);
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
@@ -58,6 +73,11 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
if (extensions->present == true) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength));
|
||||
if (extensions->credBlob.present == true &&
|
||||
extensions->credBlob.len < MAX_CREDBLOB_LENGTH) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, extensions->credBlob.data, extensions->credBlob.len));
|
||||
}
|
||||
if (extensions->credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, extensions->credProtect));
|
||||
@@ -66,6 +86,14 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, *extensions->hmac_secret));
|
||||
}
|
||||
if (extensions->largeBlobKey == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "largeBlobKey"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true));
|
||||
}
|
||||
if (extensions->thirdPartyPayment == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "thirdPartyPayment"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true));
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
|
||||
@@ -90,22 +118,26 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
memset(key, 0, sizeof(key));
|
||||
credential_derive_chacha_key(key);
|
||||
uint8_t iv[12];
|
||||
random_gen(NULL, iv, sizeof(12));
|
||||
random_gen(NULL, iv, sizeof(iv));
|
||||
mbedtls_chachapoly_context chatx;
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, key);
|
||||
int ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, rs, iv, rp_id_hash, 32, cred_id + 4 + 12, cred_id + 4 + 12, cred_id + 4 + 12 + rs);
|
||||
int ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, rs, iv, rp_id_hash, 32,
|
||||
cred_id + 4 + 12,
|
||||
cred_id + 4 + 12,
|
||||
cred_id + 4 + 12 + rs);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
memcpy(cred_id, "\xf1\xd0\x02\x00", 4);
|
||||
memcpy(cred_id, CRED_PROTO, 4);
|
||||
memcpy(cred_id + 4, iv, 12);
|
||||
|
||||
err:
|
||||
err:
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
@@ -113,13 +145,18 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
|
||||
int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred) {
|
||||
int ret = 0;
|
||||
CborError error;
|
||||
uint8_t *copy_cred_id = (uint8_t *)calloc(1, cred_id_len);
|
||||
CborError error = CborNoError;
|
||||
uint8_t *copy_cred_id = (uint8_t *) calloc(1, cred_id_len);
|
||||
if (!cred) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CREDENTIAL);
|
||||
}
|
||||
memset(cred, 0, sizeof(Credential));
|
||||
memcpy(copy_cred_id, cred_id, cred_id_len);
|
||||
ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash);
|
||||
if (ret != 0) { // U2F?
|
||||
if (cred_id_len != KEY_HANDLE_LEN || verify_key(rp_id_hash, cred_id, NULL) != 0)
|
||||
if (cred_id_len != KEY_HANDLE_LEN || verify_key(rp_id_hash, cred_id, NULL) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CREDENTIAL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
CborParser parser;
|
||||
@@ -127,8 +164,10 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
||||
memset(cred, 0, sizeof(Credential));
|
||||
cred->curve = FIDO2_CURVE_P256;
|
||||
cred->alg = FIDO2_ALG_ES256;
|
||||
CBOR_CHECK(cbor_parser_init(copy_cred_id + 4 + 12, cred_id_len - (4 + 12 + 16), 0, &parser, &map));
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
CBOR_CHECK(cbor_parser_init(copy_cred_id + 4 + 12, cred_id_len - (4 + 12 + 16), 0, &parser,
|
||||
&map));
|
||||
CBOR_PARSE_MAP_START(map, 1)
|
||||
{
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_u == 0x01) {
|
||||
@@ -137,6 +176,12 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_BYTES(cred->userId, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_TEXT(cred->userName, 1);
|
||||
}
|
||||
else if (val_u == 0x05) {
|
||||
CBOR_FIELD_GET_TEXT(cred->userDisplayName, 1);
|
||||
}
|
||||
else if (val_u == 0x06) {
|
||||
CBOR_FIELD_GET_UINT(cred->creation, 1);
|
||||
}
|
||||
@@ -147,6 +192,9 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", cred->extensions.largeBlobKey);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", cred->extensions.thirdPartyPayment);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -176,51 +224,60 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
||||
}
|
||||
}
|
||||
cred->id.present = true;
|
||||
cred->id.data = (uint8_t *)calloc(1, cred_id_len);
|
||||
cred->id.data = (uint8_t *) calloc(1, cred_id_len);
|
||||
memcpy(cred->id.data, cred_id, cred_id_len);
|
||||
cred->id.len = cred_id_len;
|
||||
cred->present = true;
|
||||
err:
|
||||
err:
|
||||
free(copy_cred_id);
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
}
|
||||
return error;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void credential_free(Credential *cred) {
|
||||
CBOR_FREE_BYTE_STRING(cred->rpId);
|
||||
CBOR_FREE_BYTE_STRING(cred->userId);
|
||||
CBOR_FREE_BYTE_STRING(cred->userName);
|
||||
CBOR_FREE_BYTE_STRING(cred->userDisplayName);
|
||||
CBOR_FREE_BYTE_STRING(cred->id);
|
||||
cred->present = false;
|
||||
cred->extensions.present = false;
|
||||
cred->opts.present = false;
|
||||
if (cred) {
|
||||
CBOR_FREE_BYTE_STRING(cred->rpId);
|
||||
CBOR_FREE_BYTE_STRING(cred->userId);
|
||||
CBOR_FREE_BYTE_STRING(cred->userName);
|
||||
CBOR_FREE_BYTE_STRING(cred->userDisplayName);
|
||||
CBOR_FREE_BYTE_STRING(cred->id);
|
||||
if (cred->extensions.present) {
|
||||
CBOR_FREE_BYTE_STRING(cred->extensions.credBlob);
|
||||
}
|
||||
cred->present = false;
|
||||
cred->extensions.present = false;
|
||||
cred->opts.present = false;
|
||||
}
|
||||
}
|
||||
|
||||
int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) {
|
||||
int sloti = -1;
|
||||
Credential cred = {0};
|
||||
Credential cred = { 0 };
|
||||
int ret = 0;
|
||||
bool new_record = true;
|
||||
ret = credential_load(cred_id, cred_id_len, rp_id_hash, &cred);
|
||||
if (ret != 0) {
|
||||
credential_free(&cred);
|
||||
return ret;
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_CRED + i);
|
||||
Credential rcred = {0};
|
||||
Credential rcred = { 0 };
|
||||
if (!file_has_data(ef)) {
|
||||
if (sloti == -1)
|
||||
if (sloti == -1) {
|
||||
sloti = i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0)
|
||||
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef)-32, rp_id_hash, &rcred);
|
||||
}
|
||||
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
|
||||
if (ret != 0) {
|
||||
credential_free(&rcred);
|
||||
continue;
|
||||
@@ -228,18 +285,58 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
if (memcmp(rcred.userId.data, cred.userId.data, MIN(rcred.userId.len, cred.userId.len)) == 0) {
|
||||
sloti = i;
|
||||
credential_free(&rcred);
|
||||
new_record = false;
|
||||
break;
|
||||
}
|
||||
credential_free(&rcred);
|
||||
}
|
||||
if (sloti == -1)
|
||||
if (sloti == -1) {
|
||||
return -1;
|
||||
credential_free(&cred);
|
||||
uint8_t *data = (uint8_t *)calloc(1, cred_id_len+32);
|
||||
}
|
||||
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
|
||||
memcpy(data, rp_id_hash, 32);
|
||||
memcpy(data + 32, cred_id, cred_id_len);
|
||||
file_t *ef = file_new(EF_CRED+sloti);
|
||||
flash_write_data_to_file(ef, data, cred_id_len + 32);
|
||||
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
|
||||
file_put_data(ef, data, (uint16_t)cred_id_len + 32);
|
||||
free(data);
|
||||
|
||||
if (new_record == true) { //increase rps
|
||||
sloti = -1;
|
||||
for (uint16_t i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
ef = search_dynamic_file(EF_RP + i);
|
||||
if (!file_has_data(ef)) {
|
||||
if (sloti == -1) {
|
||||
sloti = i;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 1, rp_id_hash, 32) == 0) {
|
||||
sloti = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sloti == -1) {
|
||||
return -1;
|
||||
}
|
||||
ef = search_dynamic_file((uint16_t)(EF_RP + sloti));
|
||||
if (file_has_data(ef)) {
|
||||
data = (uint8_t *) calloc(1, file_get_size(ef));
|
||||
memcpy(data, file_get_data(ef), file_get_size(ef));
|
||||
data[0] += 1;
|
||||
file_put_data(ef, data, file_get_size(ef));
|
||||
free(data);
|
||||
}
|
||||
else {
|
||||
ef = file_new((uint16_t)(EF_RP + sloti));
|
||||
data = (uint8_t *) calloc(1, 1 + 32 + cred.rpId.len);
|
||||
data[0] = 1;
|
||||
memcpy(data + 1, rp_id_hash, 32);
|
||||
memcpy(data + 1 + 32, cred.rpId.data, cred.rpId.len);
|
||||
file_put_data(ef, data, (uint16_t)(1 + 32 + cred.rpId.len));
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
credential_free(&cred);
|
||||
low_flash_available();
|
||||
return 0;
|
||||
}
|
||||
@@ -247,26 +344,43 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
||||
memset(outk, 0, 64);
|
||||
int r = 0;
|
||||
if ((r = load_keydev(outk)) != 0)
|
||||
if ((r = load_keydev(outk)) != 0) {
|
||||
return r;
|
||||
}
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"\xf1\xd0\x02\x00", 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"hmac-secret", 11, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "hmac-secret", 11, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int credential_derive_chacha_key(uint8_t *outk) {
|
||||
memset(outk, 0, 64);
|
||||
memset(outk, 0, 32);
|
||||
int r = 0;
|
||||
if ((r = load_keydev(outk)) != 0)
|
||||
if ((r = load_keydev(outk)) != 0) {
|
||||
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_SHA256);
|
||||
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"\xf1\xd0\x02\x00", 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"Encryption key", 14, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "Encryption key", 14, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
||||
memset(outk, 0, 32);
|
||||
int r = 0;
|
||||
if ((r = load_keydev(outk)) != 0) {
|
||||
return r;
|
||||
}
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "largeBlobKey", 12, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,14 @@ typedef struct CredOptions {
|
||||
typedef struct CredExtensions {
|
||||
const bool *hmac_secret;
|
||||
uint64_t credProtect;
|
||||
const bool *minPinLength;
|
||||
CborByteString credBlob;
|
||||
const bool *largeBlobKey;
|
||||
const bool *thirdPartyPayment;
|
||||
bool present;
|
||||
} CredExtensions;
|
||||
|
||||
typedef struct Credential
|
||||
{
|
||||
typedef struct Credential {
|
||||
CborCharString rpId;
|
||||
CborByteString userId;
|
||||
CborCharString userName;
|
||||
@@ -53,11 +56,29 @@ typedef struct Credential
|
||||
#define CRED_PROT_UV_OPTIONAL_WITH_LIST 0x02
|
||||
#define CRED_PROT_UV_REQUIRED 0x03
|
||||
|
||||
#define CRED_PROTO "\xf1\xd0\x02\x01"
|
||||
|
||||
extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
|
||||
extern int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_t *cred_id_len);
|
||||
extern int credential_create(CborCharString *rpId,
|
||||
CborByteString *userId,
|
||||
CborCharString *userName,
|
||||
CborCharString *userDisplayName,
|
||||
CredOptions *opts,
|
||||
CredExtensions *extensions,
|
||||
bool use_sign_count,
|
||||
int alg,
|
||||
int curve,
|
||||
uint8_t *cred_id,
|
||||
size_t *cred_id_len);
|
||||
extern void credential_free(Credential *cred);
|
||||
extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
|
||||
extern int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred);
|
||||
extern int credential_load(const uint8_t *cred_id,
|
||||
size_t cred_id_len,
|
||||
const uint8_t *rp_id_hash,
|
||||
Credential *cred);
|
||||
extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
|
||||
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
|
||||
size_t cred_id_len,
|
||||
uint8_t *outk);
|
||||
|
||||
#endif // _CREDENTIAL_H_
|
||||
|
||||
@@ -18,14 +18,9 @@
|
||||
#ifndef _CTAP_H_
|
||||
#define _CTAP_H_
|
||||
|
||||
#ifdef _MSC_VER // Windows
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long int uint64_t;
|
||||
#else
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -104,6 +99,51 @@ typedef struct {
|
||||
uint8_t sig[CTAP_MAX_EC_SIG_SIZE]; // Signature
|
||||
} CTAP_AUTHENTICATE_RESP;
|
||||
|
||||
// CTAP CBOR commands
|
||||
|
||||
#define CTAP_MAKE_CREDENTIAL 0x01
|
||||
#define CTAP_GET_ASSERTION 0x02
|
||||
#define CTAP_GET_INFO 0x04
|
||||
#define CTAP_CLIENT_PIN 0x06
|
||||
#define CTAP_RESET 0x07
|
||||
#define CTAP_GET_NEXT_ASSERTION 0x08
|
||||
#define CTAP_CREDENTIAL_MGMT 0x0A
|
||||
#define CTAP_SELECTION 0x0B
|
||||
#define CTAP_LARGE_BLOBS 0x0C
|
||||
#define CTAP_CONFIG 0x0D
|
||||
|
||||
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
||||
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
||||
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
|
||||
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
|
||||
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
|
||||
#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f
|
||||
|
||||
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
|
||||
|
||||
#define CTAP_VENDOR_BACKUP 0x01
|
||||
#define CTAP_VENDOR_MSE 0x02
|
||||
#define CTAP_VENDOR_UNLOCK 0x03
|
||||
#define CTAP_VENDOR_EA 0x04
|
||||
#define CTAP_VENDOR_PHY_OPTS 0x05
|
||||
#define CTAP_VENDOR_MEMORY 0x06
|
||||
|
||||
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
||||
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
||||
#define CTAP_PERMISSION_CM 0x04 // CredentialManagement
|
||||
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
|
||||
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
|
||||
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
|
||||
|
||||
typedef struct mse {
|
||||
uint8_t Qpt[65];
|
||||
uint8_t key_enc[12 + 32];
|
||||
bool init;
|
||||
} mse_t;
|
||||
extern mse_t mse;
|
||||
|
||||
extern int mse_decrypt_ct(uint8_t *, size_t);
|
||||
|
||||
// Command status responses
|
||||
|
||||
#define CTAP_SW_NO_ERROR 0x9000 // SW_NO_ERROR
|
||||
@@ -112,6 +152,7 @@ typedef struct {
|
||||
#define CTAP_SW_COMMAND_NOT_ALLOWED 0x6986 // SW_COMMAND_NOT_ALLOWED
|
||||
#define CTAP_SW_INS_NOT_SUPPORTED 0x6D00 // SW_INS_NOT_SUPPORTED
|
||||
|
||||
#define CTAP2_OK 0x00
|
||||
#define CTAP2_ERR_CBOR_UNEXPECTED_TYPE 0x11
|
||||
#define CTAP2_ERR_INVALID_CBOR 0x12
|
||||
#define CTAP2_ERR_MISSING_PARAMETER 0x14
|
||||
|
||||
@@ -18,20 +18,18 @@
|
||||
#ifndef _CTAP2_CBOR_H_
|
||||
#define _CTAP2_CBOR_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include <stdio.h>
|
||||
#include "cbor.h"
|
||||
#ifndef ESP_PLATFORM
|
||||
#include "common.h"
|
||||
#else
|
||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
||||
#endif
|
||||
#include "mbedtls/ecp.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
|
||||
extern uint8_t *driver_prepare_response();
|
||||
extern void driver_exec_finished(size_t size_next);
|
||||
extern int cbor_process(const uint8_t *data, size_t len);
|
||||
extern int cbor_reset();
|
||||
extern int cbor_get_info();
|
||||
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_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||
extern int cbor_get_next_assertion(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 bool _btrue, _bfalse;
|
||||
@@ -55,17 +53,17 @@ extern const bool _btrue, _bfalse;
|
||||
if (x) \
|
||||
{ \
|
||||
free(x); \
|
||||
x = NULL;\
|
||||
x = NULL; \
|
||||
} \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_ERROR(e) \
|
||||
do \
|
||||
{ \
|
||||
error = e; \
|
||||
printf("Cbor ERROR [%s:%d]: %d\n", __FILE__, __LINE__, e); \
|
||||
printf("Cbor ERROR [%s:%d]: %x\n", __FILE__, __LINE__, e); \
|
||||
goto err; \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_ASSERT(c) \
|
||||
do \
|
||||
@@ -76,7 +74,7 @@ extern const bool _btrue, _bfalse;
|
||||
printf("Cbor ASSERT [%s:%d]: %s\n", __FILE__, __LINE__, #c); \
|
||||
goto err; \
|
||||
} \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define PINUVAUTHTOKEN_MC 0x1
|
||||
#define PINUVAUTHTOKEN_GA 0x2
|
||||
@@ -103,20 +101,20 @@ typedef struct CborCharString {
|
||||
do \
|
||||
{ \
|
||||
if ((v).nofree != true) \
|
||||
CBOR_FREE((v).data); \
|
||||
CBOR_FREE((v).data); \
|
||||
else \
|
||||
(v).data = NULL; \
|
||||
(v).data = NULL; \
|
||||
(v).len = 0; \
|
||||
(v).present = false; \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_PARSE_MAP_START(_p,_n) \
|
||||
#define CBOR_PARSE_MAP_START(_p, _n) \
|
||||
CBOR_ASSERT(cbor_value_is_map(&(_p)) == true); \
|
||||
CborValue _f##_n; \
|
||||
CBOR_CHECK(cbor_value_enter_container(&(_p), &(_f##_n))); \
|
||||
while (cbor_value_at_end(&(_f##_n)) == false)
|
||||
|
||||
#define CBOR_PARSE_ARRAY_START(_p,_n) \
|
||||
#define CBOR_PARSE_ARRAY_START(_p, _n) \
|
||||
CBOR_ASSERT(cbor_value_is_array(&(_p)) == true); \
|
||||
CborValue _f##_n; \
|
||||
CBOR_CHECK(cbor_value_enter_container(&(_p), &(_f##_n))); \
|
||||
@@ -127,14 +125,14 @@ typedef struct CborCharString {
|
||||
CBOR_ASSERT(cbor_value_is_unsigned_integer(&(_f##_n)) == true); \
|
||||
CBOR_CHECK(cbor_value_get_uint64(&(_f##_n), &(v))); \
|
||||
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_FIELD_GET_INT(v, _n) \
|
||||
do { \
|
||||
CBOR_ASSERT(cbor_value_is_integer(&(_f##_n)) == true); \
|
||||
CBOR_CHECK(cbor_value_get_int64(&(_f##_n), &(v))); \
|
||||
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_FIELD_GET_BYTES(v, _n) \
|
||||
do { \
|
||||
@@ -157,11 +155,11 @@ typedef struct CborCharString {
|
||||
CBOR_CHECK(cbor_value_get_boolean(&(_f##_n), &val)); \
|
||||
v = (val == true ? ptrue : pfalse); \
|
||||
CBOR_CHECK(cbor_value_advance_fixed(&(_f##_n))); \
|
||||
} while(0)
|
||||
} while (0)
|
||||
|
||||
#define CBOR_FIELD_GET_KEY_TEXT(_n) \
|
||||
CBOR_ASSERT(cbor_value_is_text_string(&(_f##_n)) == true); \
|
||||
char _fd##_n[64]; \
|
||||
char _fd##_n[64] = {0}; \
|
||||
size_t _fdl##_n = sizeof(_fd##_n); \
|
||||
CBOR_CHECK(cbor_value_copy_text_string(&(_f##_n), _fd##_n, &_fdl##_n, &(_f##_n)))
|
||||
|
||||
@@ -183,26 +181,26 @@ typedef struct CborCharString {
|
||||
|
||||
#define CBOR_FIELD_KEY_TEXT_VAL_INT(_n, _t, _v) \
|
||||
if (strcmp(_fd##_n, _t) == 0) { \
|
||||
CBOR_FIELD_GET_INT(_v, _n);\
|
||||
CBOR_FIELD_GET_INT(_v, _n); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
#define CBOR_FIELD_KEY_TEXT_VAL_UINT(_n, _t, _v) \
|
||||
if (strcmp(_fd##_n, _t) == 0) { \
|
||||
CBOR_FIELD_GET_UINT(_v, _n);\
|
||||
CBOR_FIELD_GET_UINT(_v, _n); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
#define CBOR_FIELD_KEY_TEXT_VAL_BOOL(_n, _t, _v) \
|
||||
if (strcmp(_fd##_n, _t) == 0) { \
|
||||
CBOR_FIELD_GET_BOOL(_v, _n);\
|
||||
CBOR_FIELD_GET_BOOL(_v, _n); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
#define CBOR_PARSE_MAP_END(_p,_n) \
|
||||
#define CBOR_PARSE_MAP_END(_p, _n) \
|
||||
CBOR_CHECK(cbor_value_leave_container(&(_p), &(_f##_n)))
|
||||
|
||||
#define CBOR_PARSE_ARRAY_END(_p,_n) CBOR_PARSE_MAP_END(_p, _n)
|
||||
#define CBOR_PARSE_ARRAY_END(_p, _n) CBOR_PARSE_MAP_END(_p, _n)
|
||||
|
||||
#define CBOR_ADVANCE(_n) CBOR_CHECK(cbor_value_advance(&_f##_n));
|
||||
|
||||
@@ -211,39 +209,51 @@ typedef struct CborCharString {
|
||||
if ((v).data && (v).len > 0) { \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_byte_string(&(p), (v).data, (v).len)); \
|
||||
} } while(0)
|
||||
} } while (0)
|
||||
|
||||
#define CBOR_APPEND_KEY_UINT_VAL_STRING(p, k, v) \
|
||||
do { \
|
||||
if ((v).data && (v).len > 0) { \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&(p), (v).data)); \
|
||||
} } while(0)
|
||||
} } while (0)
|
||||
|
||||
|
||||
#define CBOR_APPEND_KEY_UINT_VAL_UINT(p, k, v) \
|
||||
do { \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (v))); \
|
||||
} while(0)
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (v))); \
|
||||
} while (0)
|
||||
|
||||
#define CBOR_APPEND_KEY_UINT_VAL_INT(p, k, v) \
|
||||
do { \
|
||||
CBOR_CHECK(cbor_encode_int(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_int(&(p), (v))); \
|
||||
} while(0)
|
||||
CBOR_CHECK(cbor_encode_int(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_int(&(p), (v))); \
|
||||
} while (0)
|
||||
|
||||
#define CBOR_APPEND_KEY_UINT_VAL_BOOL(p, k, v) \
|
||||
do { \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_boolean(&(p), (v))); \
|
||||
} while(0)
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_boolean(&(p), (v))); \
|
||||
} while (0)
|
||||
|
||||
#define CBOR_APPEND_KEY_UINT_VAL_PBOOL(p, k, v) \
|
||||
do { \
|
||||
if (v != NULL) {\
|
||||
if (v != NULL) { \
|
||||
CBOR_CHECK(cbor_encode_uint(&(p), (k))); \
|
||||
CBOR_CHECK(cbor_encode_boolean(&(p), v == ptrue ? true : false)); \
|
||||
} } while(0)
|
||||
} } while (0)
|
||||
|
||||
extern CborError COSE_key(mbedtls_ecp_keypair *, CborEncoder *, CborEncoder *);
|
||||
extern CborError COSE_key_shared(mbedtls_ecdh_context *key,
|
||||
CborEncoder *mapEncoderParent,
|
||||
CborEncoder *mapEncoder);
|
||||
extern CborError COSE_public_key(int alg, CborEncoder *mapEncoderParent, CborEncoder *mapEncoder);
|
||||
extern CborError COSE_read_key(CborValue *f,
|
||||
int64_t *kty,
|
||||
int64_t *alg,
|
||||
int64_t *crv,
|
||||
CborByteString *kax,
|
||||
CborByteString *kay);
|
||||
|
||||
#endif //_CTAP2_CBOR_H_
|
||||
|
||||
360
src/fido/fido.c
360
src/fido/fido.c
@@ -15,92 +15,151 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "fido.h"
|
||||
#include "hsm.h"
|
||||
#include "kek.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
#include "file.h"
|
||||
#include "usb.h"
|
||||
#include "random.h"
|
||||
#include "bsp/board.h"
|
||||
#include "mbedtls/ecdsa.h"
|
||||
#include "mbedtls/x509_crt.h"
|
||||
#include "mbedtls/hkdf.h"
|
||||
#include "pk_wrap.h"
|
||||
#include "crypto_utils.h"
|
||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
||||
#include "ccid/ccid.h"
|
||||
#endif
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include "management.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "version.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "otp.h"
|
||||
|
||||
void init_fido(bool);
|
||||
int fido_process_apdu();
|
||||
int fido_unload();
|
||||
|
||||
pinUvAuthToken_t paut = {0};
|
||||
uint8_t PICO_PRODUCT = 2; // Pico FIDO
|
||||
|
||||
pinUvAuthToken_t paut = { 0 };
|
||||
|
||||
uint8_t keydev_dec[32];
|
||||
bool has_keydev_dec = false;
|
||||
uint8_t session_pin[32] = { 0 };
|
||||
|
||||
const uint8_t fido_aid[] = {
|
||||
8,
|
||||
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
|
||||
};
|
||||
|
||||
app_t *fido_select(app_t *a) {
|
||||
a->aid = fido_aid;
|
||||
a->process_apdu = fido_process_apdu;
|
||||
a->unload = fido_unload;
|
||||
current_app = a;
|
||||
//init_fido(false);
|
||||
return a;
|
||||
const uint8_t atr_fido[] = {
|
||||
23,
|
||||
0x3b, 0xfd, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe, 0x15, 0x80, 0x73, 0xc0, 0x21, 0xc0, 0x57, 0x59,
|
||||
0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x40
|
||||
};
|
||||
|
||||
uint8_t fido_get_version_major() {
|
||||
return PICO_FIDO_VERSION_MAJOR;
|
||||
}
|
||||
uint8_t fido_get_version_minor() {
|
||||
return PICO_FIDO_VERSION_MINOR;
|
||||
}
|
||||
|
||||
void __attribute__ ((constructor)) fido_ctor() {
|
||||
register_app(fido_select);
|
||||
//fido_select(&apps[0]);
|
||||
int fido_select(app_t *a, uint8_t force) {
|
||||
(void) force;
|
||||
if (cap_supported(CAP_FIDO2)) {
|
||||
a->process_apdu = fido_process_apdu;
|
||||
a->unload = fido_unload;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
extern uint8_t (*get_version_major)();
|
||||
extern uint8_t (*get_version_minor)();
|
||||
|
||||
INITIALIZER ( fido_ctor ) {
|
||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
||||
ccid_atr = atr_fido;
|
||||
#endif
|
||||
get_version_major = fido_get_version_major;
|
||||
get_version_minor = fido_get_version_minor;
|
||||
register_app(fido_select, fido_aid);
|
||||
}
|
||||
|
||||
int fido_unload() {
|
||||
return CCID_OK;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
||||
if (curve == FIDO2_CURVE_P256)
|
||||
if (curve == FIDO2_CURVE_P256) {
|
||||
return MBEDTLS_ECP_DP_SECP256R1;
|
||||
else if (curve == FIDO2_CURVE_P384)
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_P384) {
|
||||
return MBEDTLS_ECP_DP_SECP384R1;
|
||||
else if (curve == FIDO2_CURVE_P521)
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_P521) {
|
||||
return MBEDTLS_ECP_DP_SECP521R1;
|
||||
else if (curve == FIDO2_CURVE_P256K1)
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_P256K1) {
|
||||
return MBEDTLS_ECP_DP_SECP256K1;
|
||||
else if (curve == FIDO2_CURVE_X25519)
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_X25519) {
|
||||
return MBEDTLS_ECP_DP_CURVE25519;
|
||||
else if (curve == FIDO2_CURVE_X448)
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_X448) {
|
||||
return MBEDTLS_ECP_DP_CURVE448;
|
||||
}
|
||||
return MBEDTLS_ECP_DP_NONE;
|
||||
}
|
||||
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
||||
if (id == MBEDTLS_ECP_DP_SECP256R1) {
|
||||
return FIDO2_CURVE_P256;
|
||||
}
|
||||
else if (id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
return FIDO2_CURVE_P384;
|
||||
}
|
||||
else if (id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
return FIDO2_CURVE_P521;
|
||||
}
|
||||
else if (id == MBEDTLS_ECP_DP_SECP256K1) {
|
||||
return FIDO2_CURVE_P256K1;
|
||||
}
|
||||
else if (id == MBEDTLS_ECP_DP_CURVE25519) {
|
||||
return MBEDTLS_ECP_DP_CURVE25519;
|
||||
}
|
||||
else if (id == MBEDTLS_ECP_DP_CURVE448) {
|
||||
return FIDO2_CURVE_X448;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key) {
|
||||
mbedtls_ecp_group_id mbedtls_curve = fido_curve_to_mbedtls(curve);
|
||||
if (mbedtls_curve == MBEDTLS_ECP_DP_NONE)
|
||||
if (mbedtls_curve == MBEDTLS_ECP_DP_NONE) {
|
||||
return CTAP2_ERR_UNSUPPORTED_ALGORITHM;
|
||||
}
|
||||
uint8_t key_path[KEY_PATH_LEN];
|
||||
memcpy(key_path, cred_id, KEY_PATH_LEN);
|
||||
*(uint32_t *)key_path = 0x80000000 | 10022;
|
||||
for (int i = 1; i < KEY_PATH_ENTRIES; i++)
|
||||
*(uint32_t *)(key_path+i*sizeof(uint32_t)) |= 0x80000000;
|
||||
*(uint32_t *) key_path = 0x80000000 | 10022;
|
||||
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
|
||||
}
|
||||
return derive_key(NULL, false, key_path, mbedtls_curve, key);
|
||||
}
|
||||
|
||||
int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size, bool core1) {
|
||||
int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffer_size) {
|
||||
mbedtls_x509write_cert ctx;
|
||||
mbedtls_x509write_crt_init(&ctx);
|
||||
mbedtls_x509write_crt_set_version(&ctx, MBEDTLS_X509_CRT_VERSION_3);
|
||||
mbedtls_x509write_crt_set_validity(&ctx, "20220901000000", "20320831235959" );
|
||||
mbedtls_x509write_crt_set_validity(&ctx, "20220901000000", "20720831235959");
|
||||
mbedtls_x509write_crt_set_issuer_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
||||
mbedtls_x509write_crt_set_subject_name(&ctx, "C=ES,O=Pico HSM,CN=Pico FIDO");
|
||||
mbedtls_mpi serial;
|
||||
mbedtls_mpi_init(&serial);
|
||||
mbedtls_mpi_fill_random(&serial, 32, core1 ? random_gen : random_gen_core0, NULL);
|
||||
mbedtls_x509write_crt_set_serial(&ctx, &serial);
|
||||
uint8_t serial[16];
|
||||
random_gen(NULL, serial, sizeof(serial));
|
||||
mbedtls_x509write_crt_set_serial_raw(&ctx, serial, sizeof(serial));
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_pk_init(&key);
|
||||
mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
|
||||
@@ -111,22 +170,41 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
||||
mbedtls_x509write_crt_set_basic_constraints(&ctx, 0, 0);
|
||||
mbedtls_x509write_crt_set_subject_key_identifier(&ctx);
|
||||
mbedtls_x509write_crt_set_authority_key_identifier(&ctx);
|
||||
mbedtls_x509write_crt_set_key_usage(&ctx, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_CERT_SIGN);
|
||||
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, core1 ? random_gen : random_gen_core0, NULL);
|
||||
mbedtls_x509write_crt_set_key_usage(&ctx,
|
||||
MBEDTLS_X509_KU_DIGITAL_SIGNATURE |
|
||||
MBEDTLS_X509_KU_KEY_CERT_SIGN);
|
||||
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, random_gen, NULL);
|
||||
mbedtls_x509write_crt_free(&ctx);
|
||||
/* pk cannot be freed, as it is freed later */
|
||||
//mbedtls_pk_free(&key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int load_keydev(uint8_t *key) {
|
||||
if (!ef_keydev || file_get_size(ef_keydev) == 0)
|
||||
return CCID_ERR_MEMORY_FATAL;
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
//return mkek_decrypt(key, file_get_size(ef_keydev));
|
||||
return CCID_OK;
|
||||
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
|
||||
return PICOKEY_ERR_MEMORY_FATAL;
|
||||
}
|
||||
|
||||
if (has_keydev_dec == true) {
|
||||
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
||||
}
|
||||
else {
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
|
||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *key) {
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
uint32_t k = *(uint32_t *)&keyHandle[i*sizeof(uint32_t)];
|
||||
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
|
||||
if (!(k & 0x80000000)) {
|
||||
return -1;
|
||||
}
|
||||
@@ -135,17 +213,20 @@ int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_con
|
||||
if (key == NULL) {
|
||||
mbedtls_ecdsa_init(&ctx);
|
||||
key = &ctx;
|
||||
if (derive_key(appId, false, (uint8_t *)keyHandle, MBEDTLS_ECP_DP_SECP256R1, &ctx) != 0) {
|
||||
if (derive_key(appId, false, (uint8_t *) keyHandle, MBEDTLS_ECP_DP_SECP256R1, &ctx) != 0) {
|
||||
mbedtls_ecdsa_free(&ctx);
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
uint8_t hmac[32], d[32];
|
||||
int ret = mbedtls_ecp_write_key(key, d, sizeof(d));
|
||||
if (key == NULL)
|
||||
size_t olen = 0;
|
||||
int ret = mbedtls_ecp_write_key_ext(key, &olen, d, sizeof(d));
|
||||
if (key == &ctx) {
|
||||
mbedtls_ecdsa_free(&ctx);
|
||||
if (ret != 0)
|
||||
}
|
||||
if (ret != 0) {
|
||||
return -2;
|
||||
}
|
||||
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
|
||||
memcpy(key_base, appId, CTAP_APPID_SIZE);
|
||||
memcpy(key_base + CTAP_APPID_SIZE, keyHandle, KEY_PATH_LEN);
|
||||
@@ -155,18 +236,20 @@ int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_con
|
||||
}
|
||||
|
||||
int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int curve, mbedtls_ecdsa_context *key) {
|
||||
uint8_t outk[64] = {0};
|
||||
uint8_t outk[67] = { 0 }; //SECP521R1 key is 66 bytes length
|
||||
int r = 0;
|
||||
memset(outk, 0, sizeof(outk));
|
||||
if ((r = load_keydev(outk)) != CCID_OK)
|
||||
if ((r = load_keydev(outk)) != PICOKEY_OK) {
|
||||
printf("Error loading keydev: %d\n", r);
|
||||
return r;
|
||||
}
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
if (new_key == true) {
|
||||
uint32_t val = 0;
|
||||
random_gen(NULL, (uint8_t *) &val, sizeof(val));
|
||||
val |= 0x80000000;
|
||||
memcpy(&key_handle[i*sizeof(uint32_t)], &val, sizeof(uint32_t));
|
||||
memcpy(&key_handle[i * sizeof(uint32_t)], &val, sizeof(uint32_t));
|
||||
}
|
||||
r = mbedtls_hkdf(md_info, &key_handle[i * sizeof(uint32_t)], sizeof(uint32_t), outk, 32, outk + 32, 32, outk, sizeof(outk));
|
||||
if (r != 0) {
|
||||
@@ -178,8 +261,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
uint8_t key_base[CTAP_APPID_SIZE + KEY_PATH_LEN];
|
||||
memcpy(key_base, app_id, CTAP_APPID_SIZE);
|
||||
memcpy(key_base + CTAP_APPID_SIZE, key_handle, KEY_PATH_LEN);
|
||||
if ((r = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, key_base, sizeof(key_base), key_handle + 32)) != 0)
|
||||
{
|
||||
if ((r = mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, key_base, sizeof(key_base), key_handle + 32)) != 0) {
|
||||
mbedtls_platform_zeroize(outk, sizeof(outk));
|
||||
return r;
|
||||
}
|
||||
@@ -187,38 +269,51 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
if (key != NULL) {
|
||||
mbedtls_ecp_group_load(&key->grp, curve);
|
||||
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(curve);
|
||||
if (cinfo == NULL)
|
||||
if (cinfo == NULL) {
|
||||
return 1;
|
||||
r = mbedtls_ecp_read_key(curve, key, outk, ceil((float)cinfo->bit_size/8));
|
||||
}
|
||||
if (cinfo->bit_size % 8 != 0) {
|
||||
outk[0] >>= 8 - (cinfo->bit_size % 8);
|
||||
}
|
||||
r = mbedtls_ecp_read_key(curve, key, outk, (size_t)ceil((float) cinfo->bit_size / 8));
|
||||
mbedtls_platform_zeroize(outk, sizeof(outk));
|
||||
if (r != 0)
|
||||
if (r != 0) {
|
||||
return r;
|
||||
}
|
||||
return mbedtls_ecp_mul(&key->grp, &key->Q, &key->d, &key->grp.G, random_gen, NULL);
|
||||
}
|
||||
mbedtls_platform_zeroize(outk, sizeof(outk));
|
||||
return r;
|
||||
}
|
||||
|
||||
int scan_files(bool core1) {
|
||||
int scan_files() {
|
||||
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
||||
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
||||
ef_mkek = search_by_fid(EF_MKEK, NULL, SPECIFY_EF);
|
||||
if (ef_keydev) {
|
||||
if (!file_has_data(ef_keydev)) {
|
||||
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
|
||||
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
|
||||
mbedtls_ecdsa_context ecdsa;
|
||||
mbedtls_ecdsa_init(&ecdsa);
|
||||
uint8_t index = 0;
|
||||
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, core1 ? random_gen : random_gen_core0, &index);
|
||||
int ret = mbedtls_ecdsa_genkey(&ecdsa, MBEDTLS_ECP_DP_SECP256R1, random_gen, &index);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
return ret;
|
||||
}
|
||||
uint8_t kdata[32];
|
||||
int key_size = mbedtls_mpi_size(&ecdsa.d);
|
||||
mbedtls_mpi_write_binary(&ecdsa.d, kdata, key_size);
|
||||
ret = flash_write_data_to_file(ef_keydev, kdata, key_size);
|
||||
uint8_t kdata[64];
|
||||
size_t key_size = 0;
|
||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (otp_key_1) {
|
||||
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
|
||||
}
|
||||
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
|
||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
if (ret != CCID_OK) {
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
}
|
||||
printf(" done!\n");
|
||||
@@ -227,23 +322,48 @@ int scan_files(bool core1) {
|
||||
else {
|
||||
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
|
||||
}
|
||||
if (ef_mkek) { // No encrypted MKEK
|
||||
if (!file_has_data(ef_mkek)) {
|
||||
uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE];
|
||||
random_gen(NULL, mkek, sizeof(mkek));
|
||||
file_put_data(ef_mkek, mkek, sizeof(mkek));
|
||||
int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != 0) {
|
||||
printf("FATAL ERROR: MKEK encryption failed!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: MKEK not found in memory!\r\n");
|
||||
}
|
||||
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
|
||||
if (ef_certdev) {
|
||||
if (!file_has_data(ef_certdev)) {
|
||||
uint8_t cert[4096];
|
||||
uint8_t cert[2048], outk[32];
|
||||
memset(outk, 0, sizeof(outk));
|
||||
int ret = 0;
|
||||
if ((ret = load_keydev(outk)) != 0) {
|
||||
return ret;
|
||||
}
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
if (ret != 0)
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, outk, sizeof(outk));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return ret;
|
||||
}
|
||||
ret = mbedtls_ecp_mul(&key.grp, &key.Q, &key.d, &key.grp.G, random_gen, NULL);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return ret;
|
||||
ret = x509_create_cert(&key, cert, sizeof(cert), core1);
|
||||
}
|
||||
ret = x509_create_cert(&key, cert, sizeof(cert));
|
||||
mbedtls_ecdsa_free(&key);
|
||||
if (ret <= 0)
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
flash_write_data_to_file(ef_certdev, cert + sizeof(cert) - ret, ret);
|
||||
}
|
||||
file_put_data(ef_certdev, cert + sizeof(cert) - ret, (uint16_t)ret);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -253,22 +373,26 @@ int scan_files(bool core1) {
|
||||
if (ef_counter) {
|
||||
if (!file_has_data(ef_counter)) {
|
||||
uint32_t v = 0;
|
||||
flash_write_data_to_file(ef_counter, (uint8_t *)&v, sizeof(v));
|
||||
file_put_data(ef_counter, (uint8_t *) &v, sizeof(v));
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: Global counter not found in memory!\r\n");
|
||||
}
|
||||
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
|
||||
if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
|
||||
uint8_t pin_data[34] = { 0 }, dhash[32];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 18);
|
||||
double_hash_pin(pin_data + 2, 16, dhash);
|
||||
memcpy(pin_data + 2, dhash, 32);
|
||||
file_put_data(ef_pin, pin_data, 34);
|
||||
}
|
||||
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
|
||||
if (ef_authtoken) {
|
||||
if (!file_has_data(ef_authtoken)) {
|
||||
uint8_t t[32];
|
||||
if (core1)
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
else
|
||||
random_gen_core0(NULL, t, sizeof(t));
|
||||
flash_write_data_to_file(ef_authtoken, t, sizeof(t));
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
file_put_data(ef_authtoken, t, sizeof(t));
|
||||
}
|
||||
paut.data = file_get_data(ef_authtoken);
|
||||
paut.len = file_get_size(ef_authtoken);
|
||||
@@ -276,73 +400,105 @@ int scan_files(bool core1) {
|
||||
else {
|
||||
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
||||
}
|
||||
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_largeblob)) {
|
||||
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
|
||||
}
|
||||
|
||||
low_flash_available();
|
||||
return CCID_OK;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
void scan_all(bool core1) {
|
||||
void scan_all() {
|
||||
scan_flash();
|
||||
scan_files(core1);
|
||||
scan_files();
|
||||
}
|
||||
|
||||
void init_fido(bool core1) {
|
||||
scan_all(core1);
|
||||
extern void init_otp();
|
||||
void init_fido() {
|
||||
scan_all();
|
||||
init_otp();
|
||||
}
|
||||
|
||||
bool wait_button_pressed() {
|
||||
uint32_t val = EV_PRESS_BUTTON;
|
||||
#if defined(ENABLE_UP_BUTTON) && ENABLE_UP_BUTTON==1
|
||||
#ifndef ENABLE_EMULATION
|
||||
queue_try_add(&card_to_usb_q, &val);
|
||||
do {
|
||||
queue_remove_blocking(&usb_to_card_q, &val);
|
||||
} while (val != EV_BUTTON_PRESSED && val != EV_BUTTON_TIMEOUT);
|
||||
#endif
|
||||
return (val == EV_BUTTON_TIMEOUT);
|
||||
return val == EV_BUTTON_TIMEOUT;
|
||||
}
|
||||
|
||||
uint32_t user_present_time_limit = 0;
|
||||
|
||||
bool check_user_presence() {
|
||||
#if defined(ENABLE_UP_BUTTON) && ENABLE_UP_BUTTON==1
|
||||
if (user_present_time_limit == 0 || user_present_time_limit+TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||
if (wait_button_pressed() == true) //timeout
|
||||
if (user_present_time_limit == 0 || user_present_time_limit + TRANSPORT_TIME_LIMIT < board_millis()) {
|
||||
if (wait_button_pressed() == true) { //timeout
|
||||
return false;
|
||||
}
|
||||
//user_present_time_limit = board_millis();
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t get_sign_counter() {
|
||||
uint8_t *caddr = file_get_data(ef_counter);
|
||||
return (*caddr) | (*(caddr + 1) << 8) | (*(caddr + 2) << 16) | (*(caddr + 3) << 24);
|
||||
return get_uint32_t_le(caddr);
|
||||
}
|
||||
|
||||
typedef struct cmd
|
||||
{
|
||||
uint8_t ins;
|
||||
int (*cmd_handler)();
|
||||
} cmd_t;
|
||||
uint8_t get_opts() {
|
||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef)) {
|
||||
return *file_get_data(ef);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_opts(uint8_t opts) {
|
||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||
file_put_data(ef, &opts, sizeof(uint8_t));
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
extern int cmd_register();
|
||||
extern int cmd_authenticate();
|
||||
extern int cmd_version();
|
||||
extern int cbor_parse(int, uint8_t *, size_t);
|
||||
|
||||
#define CTAP_CBOR 0x10
|
||||
|
||||
int cmd_cbor() {
|
||||
uint8_t *old_buf = res_APDU;
|
||||
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
res_APDU = old_buf;
|
||||
res_APDU_size += 1;
|
||||
memcpy(res_APDU, ctap_resp->init.data, res_APDU_size);
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ CTAP_REGISTER, cmd_register },
|
||||
{ CTAP_AUTHENTICATE, cmd_authenticate },
|
||||
{ CTAP_VERSION, cmd_version },
|
||||
{ 0x00, 0x0}
|
||||
{ CTAP_CBOR, cmd_cbor },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int fido_process_apdu() {
|
||||
if (CLA(apdu) != 0x00)
|
||||
if (CLA(apdu) != 0x00 && CLA(apdu) != 0x80) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++)
|
||||
{
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
if (cap_supported(CAP_U2F)) {
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
|
||||
@@ -18,11 +18,21 @@
|
||||
#ifndef _FIDO_H_
|
||||
#define _FIDO_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#ifndef ESP_PLATFORM
|
||||
#include "common.h"
|
||||
#else
|
||||
#define MBEDTLS_ALLOW_PRIVATE_ACCESS
|
||||
#endif
|
||||
|
||||
#include "mbedtls/ecdsa.h"
|
||||
#include "ctap_hid.h"
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "hid/ctap_hid.h"
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define CTAP_PUBKEY_LEN (65)
|
||||
#define KEY_PATH_LEN (32)
|
||||
@@ -30,17 +40,21 @@
|
||||
#define SHA256_DIGEST_LENGTH (32)
|
||||
#define KEY_HANDLE_LEN (KEY_PATH_LEN + SHA256_DIGEST_LENGTH)
|
||||
|
||||
extern int scan_files(bool);
|
||||
extern int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int, mbedtls_ecdsa_context *key);
|
||||
extern int scan_files();
|
||||
extern int derive_key(const uint8_t *app_id,
|
||||
bool new_key,
|
||||
uint8_t *key_handle,
|
||||
int,
|
||||
mbedtls_ecdsa_context *key);
|
||||
extern int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecdsa_context *);
|
||||
extern bool wait_button_pressed();
|
||||
extern CTAPHID_FRAME *ctap_req, *ctap_resp;
|
||||
extern void init_fido(bool);
|
||||
extern void init_fido();
|
||||
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
|
||||
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
|
||||
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecdsa_context *key);
|
||||
extern int load_keydev(uint8_t *key);
|
||||
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out);
|
||||
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, size_t in_len, uint8_t *out);
|
||||
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
|
||||
|
||||
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
|
||||
@@ -48,6 +62,10 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
|
||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
|
||||
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
|
||||
#define FIDO2_ALG_ES256K -47
|
||||
#define FIDO2_ALG_RS256 -257
|
||||
#define FIDO2_ALG_RS384 -258
|
||||
#define FIDO2_ALG_RS512 -259
|
||||
|
||||
#define FIDO2_CURVE_P256 1
|
||||
#define FIDO2_CURVE_P384 2
|
||||
@@ -63,12 +81,7 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
||||
#define FIDO2_AUT_FLAG_AT 0x40
|
||||
#define FIDO2_AUT_FLAG_ED 0x80
|
||||
|
||||
#define FIDO2_PERMISSION_MC 0x1
|
||||
#define FIDO2_PERMISSION_GA 0x2
|
||||
#define FIDO2_PERMISSION_CM 0x4
|
||||
#define FIDO2_PERMISSION_BE 0x8
|
||||
#define FIDO2_PERMISSION_LBW 0x10
|
||||
#define FIDO2_PERMISSION_ACFG 0x20
|
||||
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
|
||||
|
||||
#define MAX_PIN_RETRIES 8
|
||||
extern bool getUserPresentFlagValue();
|
||||
@@ -78,9 +91,15 @@ extern void clearUserVerifiedFlag();
|
||||
extern void clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
extern void send_keepalive();
|
||||
extern uint32_t get_sign_counter();
|
||||
extern uint8_t get_opts();
|
||||
extern void set_opts(uint8_t);
|
||||
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
|
||||
#define MAX_CRED_ID_LENGTH 1024
|
||||
#define MAX_RESIDENT_CREDENTIALS 256
|
||||
#define MAX_CREDBLOB_LENGTH 128
|
||||
#define MAX_MSG_SIZE 1024
|
||||
#define MAX_FRAGMENT_LENGTH (MAX_MSG_SIZE - 64)
|
||||
#define MAX_LARGE_BLOB_SIZE 2048
|
||||
|
||||
typedef struct known_app {
|
||||
const uint8_t *rp_id_hash;
|
||||
@@ -91,7 +110,7 @@ typedef struct known_app {
|
||||
|
||||
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();
|
||||
|
||||
@@ -101,6 +120,7 @@ typedef struct pinUvAuthToken {
|
||||
bool in_use;
|
||||
uint8_t permissions;
|
||||
uint8_t rp_id_hash[32];
|
||||
bool has_rp_id;
|
||||
bool user_present;
|
||||
bool user_verified;
|
||||
} pinUvAuthToken_t;
|
||||
@@ -108,6 +128,8 @@ typedef struct pinUvAuthToken {
|
||||
extern uint32_t user_present_time_limit;
|
||||
|
||||
extern pinUvAuthToken_t paut;
|
||||
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign);
|
||||
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
|
||||
|
||||
extern uint8_t session_pin[32];
|
||||
|
||||
#endif //_FIDO_H
|
||||
|
||||
@@ -15,23 +15,32 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "file.h"
|
||||
#include "files.h"
|
||||
|
||||
file_t file_entries[] = {
|
||||
{.fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = {0}}, // MF
|
||||
{.fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key
|
||||
{.fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // End Entity Certificate Device
|
||||
{.fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global counter
|
||||
{.fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // PIN
|
||||
{.fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // AUTH TOKEN
|
||||
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end
|
||||
{ .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = { 0 } }, // MF
|
||||
{ .fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key
|
||||
{ .fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL,.type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Device Key Enc
|
||||
{ .fid = EF_MKEK, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MKEK
|
||||
{ .fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Certificate Device
|
||||
{ .fid = EF_EE_DEV_EA, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // End Entity Enterprise Attestation Certificate
|
||||
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
|
||||
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
|
||||
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
|
||||
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
|
||||
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
|
||||
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
|
||||
{ .fid = EF_OTP_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } },
|
||||
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_NOT_KNOWN, .data = NULL, .ef_structure = 0, .acl = { 0 } } //end
|
||||
};
|
||||
|
||||
const file_t *MF = &file_entries[0];
|
||||
const file_t *file_last = &file_entries[sizeof(file_entries)/sizeof(file_t)-1];
|
||||
const file_t *file_last = &file_entries[sizeof(file_entries) / sizeof(file_t) - 1];
|
||||
file_t *ef_keydev = NULL;
|
||||
file_t *ef_certdev = NULL;
|
||||
file_t *ef_counter = NULL;
|
||||
file_t *ef_pin = NULL;
|
||||
file_t *ef_authtoken = NULL;
|
||||
file_t *ef_keydev_enc = NULL;
|
||||
file_t *ef_largeblob = NULL;
|
||||
file_t *ef_mkek = NULL;
|
||||
|
||||
@@ -21,16 +21,32 @@
|
||||
#include "file.h"
|
||||
|
||||
#define EF_KEY_DEV 0xCC00
|
||||
#define EF_KEY_DEV_ENC 0xCC01
|
||||
#define EF_MKEK 0xCC0F
|
||||
#define EF_EE_DEV 0xCE00
|
||||
#define EF_EE_DEV_EA 0xCE01
|
||||
#define EF_COUNTER 0xC000
|
||||
#define EF_OPTS 0xC001
|
||||
#define EF_PIN 0x1080
|
||||
#define EF_AUTHTOKEN 0x1090
|
||||
#define EF_MINPINLEN 0x1100
|
||||
#define EF_DEV_CONF 0x1122
|
||||
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
||||
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
||||
#define EF_LARGEBLOB 0x1101 // Large Blob Array
|
||||
#define EF_OATH_CRED 0xBA00 // OATH Creds at 0xBA00 - 0xBAFE
|
||||
#define EF_OATH_CODE 0xBAFF
|
||||
#define EF_OTP_SLOT1 0xBB00
|
||||
#define EF_OTP_SLOT2 0xBB01
|
||||
#define EF_OTP_PIN 0x10A0 // Nitrokey OTP PIN
|
||||
|
||||
extern file_t *ef_keydev;
|
||||
extern file_t *ef_certdev;
|
||||
extern file_t *ef_counter;
|
||||
extern file_t *ef_pin;
|
||||
extern file_t *ef_authtoken;
|
||||
extern file_t *ef_keydev_enc;
|
||||
extern file_t *ef_largeblob;
|
||||
extern file_t *ef_mkek;
|
||||
|
||||
#endif //_FILES_H_
|
||||
|
||||
137
src/fido/kek.c
Normal file
137
src/fido/kek.c
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
46
src/fido/kek.h
Normal file
46
src/fido/kek.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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
|
||||
@@ -15,280 +15,359 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "fido.h"
|
||||
#include "ctap2_cbor.h"
|
||||
|
||||
static const known_app_t kapps[] = {
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x96\x89\x78\xa2\x99\x53\xde\x52\xd3\xef\x0f\x0c\x71\xb7\xb7\xb6\xb1\xaf\x9f\x08\xe2\x57\x89\x6a\x8d\x81\x26\x91\x85\x30\x29\x3b",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x96\x89\x78\xa2\x99\x53\xde\x52\xd3\xef\x0f\x0c\x71\xb7\xb7\xb6\xb1\xaf\x9f\x08\xe2\x57\x89\x6a\x8d\x81\x26\x91\x85\x30\x29\x3b",
|
||||
.label = "aws.amazon.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xc3\x40\x8c\x04\x47\x88\xae\xa5\xb3\xdf\x30\x89\x52\xfd\x8c\xa3\xc7\x0e\x21\xfe\xf4\xf6\xc1\xc2\x37\x4c\xaa\x1d\xf9\xb2\x8d\xdd",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xc3\x40\x8c\x04\x47\x88\xae\xa5\xb3\xdf\x30\x89\x52\xfd\x8c\xa3\xc7\x0e\x21\xfe\xf4\xf6\xc1\xc2\x37\x4c\xaa\x1d\xf9\xb2\x8d\xdd",
|
||||
.label = "www.binance.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = ptrue,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x20\xf6\x61\xb1\x94\x0c\x34\x70\xac\x54\xfa\x2e\xb4\x99\x90\xfd\x33\xb5\x6d\xe8\xde\x60\x18\x70\xff\x02\xa8\x06\x0f\x3b\x7c\x58",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x20\xf6\x61\xb1\x94\x0c\x34\x70\xac\x54\xfa\x2e\xb4\x99\x90\xfd\x33\xb5\x6d\xe8\xde\x60\x18\x70\xff\x02\xa8\x06\x0f\x3b\x7c\x58",
|
||||
.label = "binance.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = ptrue,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x12\x74\x3b\x92\x12\x97\xb7\x7f\x11\x35\xe4\x1f\xde\xdd\x4a\x84\x6a\xfe\x82\xe1\xf3\x69\x32\xa9\x91\x2f\x3b\x0d\x8d\xfb\x7d\x0e",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x12\x74\x3b\x92\x12\x97\xb7\x7f\x11\x35\xe4\x1f\xde\xdd\x4a\x84\x6a\xfe\x82\xe1\xf3\x69\x32\xa9\x91\x2f\x3b\x0d\x8d\xfb\x7d\x0e",
|
||||
//U2F key for Bitbucket
|
||||
.label = "bitbucket.org",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x30\x2f\xd5\xb4\x49\x2a\x07\xb9\xfe\xbb\x30\xe7\x32\x69\xec\xa5\x01\x20\x5c\xcf\xe0\xc2\x0b\xf7\xb4\x72\xfa\x2d\x31\xe2\x1e\x63",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x30\x2f\xd5\xb4\x49\x2a\x07\xb9\xfe\xbb\x30\xe7\x32\x69\xec\xa5\x01\x20\x5c\xcf\xe0\xc2\x0b\xf7\xb4\x72\xfa\x2d\x31\xe2\x1e\x63",
|
||||
//U2F key for Bitfinex
|
||||
.label = "www.bitfinex.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xa3\x4d\x30\x9f\xfa\x28\xc1\x24\x14\xb8\xba\x6c\x07\xee\x1e\xfa\xe1\xa8\x5e\x8a\x04\x61\x48\x59\xa6\x7c\x04\x93\xb6\x95\x61\x90",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xa3\x4d\x30\x9f\xfa\x28\xc1\x24\x14\xb8\xba\x6c\x07\xee\x1e\xfa\xe1\xa8\x5e\x8a\x04\x61\x48\x59\xa6\x7c\x04\x93\xb6\x95\x61\x90",
|
||||
//U2F key for Bitwarden
|
||||
.label = "vault.bitwarden.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x19\x81\x5c\xb9\xa5\xfb\x25\xd8\x05\xde\xbd\x7b\x32\x53\x7e\xd5\x78\x63\x9b\x3e\xd1\x08\xec\x7c\x5b\xb9\xe8\xf0\xdf\xb1\x68\x73",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x19\x81\x5c\xb9\xa5\xfb\x25\xd8\x05\xde\xbd\x7b\x32\x53\x7e\xd5\x78\x63\x9b\x3e\xd1\x08\xec\x7c\x5b\xb9\xe8\xf0\xdf\xb1\x68\x73",
|
||||
//WebAuthn key for Cloudflare
|
||||
.label = "dash.cloudflare.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xe2\x7d\x61\xb4\xe9\x9d\xe0\xed\x98\x16\x3c\xb3\x8b\x7a\xf9\x33\xc6\x66\x5e\x55\x09\xe8\x49\x08\x37\x05\x58\x13\x77\x8e\x23\x6a",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xe2\x7d\x61\xb4\xe9\x9d\xe0\xed\x98\x16\x3c\xb3\x8b\x7a\xf9\x33\xc6\x66\x5e\x55\x09\xe8\x49\x08\x37\x05\x58\x13\x77\x8e\x23\x6a",
|
||||
//WebAuthn key for Coinbase
|
||||
.label = "coinbase.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x68\x20\x19\x15\xd7\x4c\xb4\x2a\xf5\xb3\xcc\x5c\x95\xb9\x55\x3e\x3e\x3a\x83\xb4\xd2\xa9\x3b\x45\xfb\xad\xaa\x84\x69\xff\x8e\x6e",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x68\x20\x19\x15\xd7\x4c\xb4\x2a\xf5\xb3\xcc\x5c\x95\xb9\x55\x3e\x3e\x3a\x83\xb4\xd2\xa9\x3b\x45\xfb\xad\xaa\x84\x69\xff\x8e\x6e",
|
||||
//U2F key for Dashlane
|
||||
.label = "www.dashlane.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xc5\x0f\x8a\x7b\x70\x8e\x92\xf8\x2e\x7a\x50\xe2\xbd\xc5\x5d\x8f\xd9\x1a\x22\xfe\x6b\x29\xc0\xcd\xf7\x80\x55\x30\x84\x2a\xf5\x81",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xc5\x0f\x8a\x7b\x70\x8e\x92\xf8\x2e\x7a\x50\xe2\xbd\xc5\x5d\x8f\xd9\x1a\x22\xfe\x6b\x29\xc0\xcd\xf7\x80\x55\x30\x84\x2a\xf5\x81",
|
||||
//U2F key for Dropbox
|
||||
.label = "www.dropbox.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x82\xf4\xa8\xc9\x5f\xec\x94\xb2\x6b\xaf\x9e\x37\x25\x0e\x95\x63\xd9\xa3\x66\xc7\xbe\x26\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x82\xf4\xa8\xc9\x5f\xec\x94\xb2\x6b\xaf\x9e\x37\x25\x0e\x95\x63\xd9\xa3\x66\xc7\xbe\x26\x1c\xa4\xdd\x01\x01\xf4\xd5\xef\xcb\x83",
|
||||
//WebAuthn key for Dropbox
|
||||
.label = "www.dropbox.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xf3\xe2\x04\x2f\x94\x60\x7d\xa0\xa9\xc1\xf3\xb9\x5e\x0d\x2f\x2b\xb2\xe0\x69\xc5\xbb\x4f\xa7\x64\xaf\xfa\x64\x7d\x84\x7b\x7e\xd6",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xf3\xe2\x04\x2f\x94\x60\x7d\xa0\xa9\xc1\xf3\xb9\x5e\x0d\x2f\x2b\xb2\xe0\x69\xc5\xbb\x4f\xa7\x64\xaf\xfa\x64\x7d\x84\x7b\x7e\xd6",
|
||||
//U2F key for Duo
|
||||
.label = "duosecurity.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x31\x19\x33\x28\xf8\xe2\x1d\xfb\x6c\x99\xf3\x22\xd2\x2d\x7b\x0b\x50\x87\x78\xe6\x4f\xfb\xba\x86\xe5\x22\x93\x37\x90\x31\xb8\x74",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x31\x19\x33\x28\xf8\xe2\x1d\xfb\x6c\x99\xf3\x22\xd2\x2d\x7b\x0b\x50\x87\x78\xe6\x4f\xfb\xba\x86\xe5\x22\x93\x37\x90\x31\xb8\x74",
|
||||
//WebAuthn key for Facebook
|
||||
.label = "facebook.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x69\x66\xab\xe3\x67\x4e\xa2\xf5\x30\x79\xeb\x71\x01\x97\x84\x8c\x9b\xe6\xf3\x63\x99\x2f\xd0\x29\xe9\x89\x84\x47\xcb\x9f\x00\x84",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x69\x66\xab\xe3\x67\x4e\xa2\xf5\x30\x79\xeb\x71\x01\x97\x84\x8c\x9b\xe6\xf3\x63\x99\x2f\xd0\x29\xe9\x89\x84\x47\xcb\x9f\x00\x84",
|
||||
//U2F key for FastMail
|
||||
.label = "www.fastmail.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x3f\xcb\x82\x82\xb8\x46\x76\xeb\xee\x71\x40\xe3\x9e\xca\xe1\x6e\xeb\x19\x90\x64\xc7\xc7\xe4\x43\x2e\x28\xc9\xb5\x7e\x4b\x60\x39",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x3f\xcb\x82\x82\xb8\x46\x76\xeb\xee\x71\x40\xe3\x9e\xca\xe1\x6e\xeb\x19\x90\x64\xc7\xc7\xe4\x43\x2e\x28\xc9\xb5\x7e\x4b\x60\x39",
|
||||
.label = "fastmail.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x9d\x61\x44\x2f\x5c\xe1\x33\xbd\x46\x54\x4f\xc4\x2f\x0a\x6d\x54\xc0\xde\xb8\x88\x40\xca\xc2\xb6\xae\xfa\x65\x14\xf8\x93\x49\xe9",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x9d\x61\x44\x2f\x5c\xe1\x33\xbd\x46\x54\x4f\xc4\x2f\x0a\x6d\x54\xc0\xde\xb8\x88\x40\xca\xc2\xb6\xae\xfa\x65\x14\xf8\x93\x49\xe9",
|
||||
.label = "fedoraproject.org",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xa4\xe2\x2d\xca\xfe\xa7\xe9\x0e\x12\x89\x50\x11\x39\x89\xfc\x45\x97\x8d\xc9\xfb\x87\x76\x75\x60\x51\x6c\x1c\x69\xdf\xdf\xd1\x96",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xa4\xe2\x2d\xca\xfe\xa7\xe9\x0e\x12\x89\x50\x11\x39\x89\xfc\x45\x97\x8d\xc9\xfb\x87\x76\x75\x60\x51\x6c\x1c\x69\xdf\xdf\xd1\x96",
|
||||
.label = "gandi.net",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x54\xce\x65\x1e\xd7\x15\xb4\xaa\xa7\x55\xee\xce\xbd\x4e\xa0\x95\x08\x15\xb3\x34\xbd\x07\xd1\x09\x89\x3e\x96\x30\x18\xcd\xdb\xd9",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x54\xce\x65\x1e\xd7\x15\xb4\xaa\xa7\x55\xee\xce\xbd\x4e\xa0\x95\x08\x15\xb3\x34\xbd\x07\xd1\x09\x89\x3e\x96\x30\x18\xcd\xdb\xd9",
|
||||
//WebAuthn key for Gandi
|
||||
.label = "gandi.net",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x86\x06\xc1\x68\xe5\x1f\xc1\x31\xe5\x46\xad\x57\xa1\x9f\x32\x97\xb1\x1e\x0e\x5c\xe8\x3e\x8e\x89\x31\xb2\x85\x08\x11\xcf\xa8\x81",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x86\x06\xc1\x68\xe5\x1f\xc1\x31\xe5\x46\xad\x57\xa1\x9f\x32\x97\xb1\x1e\x0e\x5c\xe8\x3e\x8e\x89\x31\xb2\x85\x08\x11\xcf\xa8\x81",
|
||||
//WebAuthn key for Gemini
|
||||
.label = "gemini.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = ptrue,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x70\x61\x7d\xfe\xd0\x65\x86\x3a\xf4\x7c\x15\x55\x6c\x91\x79\x88\x80\x82\x8c\xc4\x07\xfd\xf7\x0a\xe8\x50\x11\x56\x94\x65\xa0\x75",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x70\x61\x7d\xfe\xd0\x65\x86\x3a\xf4\x7c\x15\x55\x6c\x91\x79\x88\x80\x82\x8c\xc4\x07\xfd\xf7\x0a\xe8\x50\x11\x56\x94\x65\xa0\x75",
|
||||
//U2F key for GitHub
|
||||
.label = "github.com",
|
||||
.use_sign_count = ptrue,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x3a\xeb\x00\x24\x60\x38\x1c\x6f\x25\x8e\x83\x95\xd3\x02\x6f\x57\x1f\x0d\x9a\x76\x48\x8d\xcd\x83\x76\x39\xb1\x3a\xed\x31\x65\x60",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x3a\xeb\x00\x24\x60\x38\x1c\x6f\x25\x8e\x83\x95\xd3\x02\x6f\x57\x1f\x0d\x9a\x76\x48\x8d\xcd\x83\x76\x39\xb1\x3a\xed\x31\x65\x60",
|
||||
//WebAuthn key for GitHub
|
||||
.label = "github.com",
|
||||
.use_sign_count = ptrue,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xe7\xbe\x96\xa5\x1b\xd0\x19\x2a\x72\x84\x0d\x2e\x59\x09\xf7\x2b\xa8\x2a\x2f\xe9\x3f\xaa\x62\x4f\x03\x39\x6b\x30\xe4\x94\xc8\x04",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xe7\xbe\x96\xa5\x1b\xd0\x19\x2a\x72\x84\x0d\x2e\x59\x09\xf7\x2b\xa8\x2a\x2f\xe9\x3f\xaa\x62\x4f\x03\x39\x6b\x30\xe4\x94\xc8\x04",
|
||||
//U2F key for GitLab
|
||||
.label = "gitlab.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xa5\x46\x72\xb2\x22\xc4\xcf\x95\xe1\x51\xed\x8d\x4d\x3c\x76\x7a\x6c\xc3\x49\x43\x59\x43\x79\x4e\x88\x4f\x3d\x02\x3a\x82\x29\xfd",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xa5\x46\x72\xb2\x22\xc4\xcf\x95\xe1\x51\xed\x8d\x4d\x3c\x76\x7a\x6c\xc3\x49\x43\x59\x43\x79\x4e\x88\x4f\x3d\x02\x3a\x82\x29\xfd",
|
||||
//U2F key for Google
|
||||
.label = "google.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xd4\xc9\xd9\x02\x73\x26\x27\x1a\x89\xce\x51\xfc\xaf\x32\x8e\xd6\x73\xf1\x7b\xe3\x34\x69\xff\x97\x9e\x8a\xb8\xdd\x50\x1e\x66\x4f",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xd4\xc9\xd9\x02\x73\x26\x27\x1a\x89\xce\x51\xfc\xaf\x32\x8e\xd6\x73\xf1\x7b\xe3\x34\x69\xff\x97\x9e\x8a\xb8\xdd\x50\x1e\x66\x4f",
|
||||
//WebAuthn key for Google
|
||||
.label = "google.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x53\xa1\x5b\xa4\x2a\x7c\x03\x25\xb8\xdb\xee\x28\x96\x34\xa4\x8f\x58\xae\xa3\x24\x66\x45\xd5\xff\x41\x8f\x9b\xb8\x81\x98\x85\xa9",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x53\xa1\x5b\xa4\x2a\x7c\x03\x25\xb8\xdb\xee\x28\x96\x34\xa4\x8f\x58\xae\xa3\x24\x66\x45\xd5\xff\x41\x8f\x9b\xb8\x81\x98\x85\xa9",
|
||||
//U2F key for Keeper
|
||||
.label = "keepersecurity.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xd6\x5f\x00\x5e\xf4\xde\xa9\x32\x0c\x99\x73\x05\x3c\x95\xff\x60\x20\x11\x5d\x5f\xec\x1b\x7f\xee\x41\xa5\x78\xe1\x8d\xf9\xca\x8c",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xd6\x5f\x00\x5e\xf4\xde\xa9\x32\x0c\x99\x73\x05\x3c\x95\xff\x60\x20\x11\x5d\x5f\xec\x1b\x7f\xee\x41\xa5\x78\xe1\x8d\xf9\xca\x8c",
|
||||
//U2F key for Keeper
|
||||
.label = "keepersecurity.eu",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x3f\x37\x50\x85\x33\x2c\xac\x4f\xad\xf9\xe5\xdd\x28\xcd\x54\x69\x8f\xab\x98\x4b\x75\xd9\xc3\x6a\x07\x2c\xb1\x60\x77\x3f\x91\x52",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x3f\x37\x50\x85\x33\x2c\xac\x4f\xad\xf9\xe5\xdd\x28\xcd\x54\x69\x8f\xab\x98\x4b\x75\xd9\xc3\x6a\x07\x2c\xb1\x60\x77\x3f\x91\x52",
|
||||
//WebAuthn key for Kraken
|
||||
.label = "kraken.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xf8\x3f\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbb\x76\xf6\xb2\xf5\x60\x60\x17\x66\x72\x68\xe5\xb9\xc4\x5e",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xf8\x3f\xc3\xa1\xb2\x89\xa0\xde\xc5\xc1\xc8\xaa\x07\xe9\xb5\xdd\x9c\xbb\x76\xf6\xb2\xf5\x60\x60\x17\x66\x72\x68\xe5\xb9\xc4\x5e",
|
||||
//WebAuthn key for login.gov
|
||||
.label = "secure.login.gov",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x35\x6c\x9e\xd4\xa0\x93\x21\xb9\x69\x5f\x1e\xaf\x91\x82\x03\xf1\xb5\x5f\x68\x9d\xa6\x1f\xbc\x96\x18\x4c\x15\x7d\xda\x68\x0c\x81",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x35\x6c\x9e\xd4\xa0\x93\x21\xb9\x69\x5f\x1e\xaf\x91\x82\x03\xf1\xb5\x5f\x68\x9d\xa6\x1f\xbc\x96\x18\x4c\x15\x7d\xda\x68\x0c\x81",
|
||||
//WebAuthn key for Microsoft
|
||||
.label = "login.microsoft.com",
|
||||
.use_sign_count = pfalse,
|
||||
.use_self_attestation = pfalse,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xab\x2d\xaf\x07\x43\xde\x78\x2a\x70\x18\x9a\x0f\x5e\xfc\x30\x90\x2f\x92\x5b\x9f\x9a\x18\xc5\xd7\x14\x1b\x7b\x12\xf8\xa0\x10\x0c",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xab\x2d\xaf\x07\x43\xde\x78\x2a\x70\x18\x9a\x0f\x5e\xfc\x30\x90\x2f\x92\x5b\x9f\x9a\x18\xc5\xd7\x14\x1b\x7b\x12\xf8\xa0\x10\x0c",
|
||||
//WebAuthn key for mojeID
|
||||
.label = "mojeid.cz",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x85\x71\x01\x36\x1b\x20\xa9\x54\x4c\xdb\x9b\xef\x65\x85\x8b\x6b\xac\x70\x13\x55\x0d\x8f\x84\xf7\xef\xee\x25\x2b\x96\xfa\x7c\x1e",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x85\x71\x01\x36\x1b\x20\xa9\x54\x4c\xdb\x9b\xef\x65\x85\x8b\x6b\xac\x70\x13\x55\x0d\x8f\x84\xf7\xef\xee\x25\x2b\x96\xfa\x7c\x1e",
|
||||
//WebAuthn key for Namecheap
|
||||
.label = "www.namecheap.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x08\xb2\xa3\xd4\x19\x39\xaa\x31\x66\x84\x93\xcb\x36\xcd\xcc\x4f\x16\xc4\xd9\xb4\xc8\x23\x8b\x73\xc2\xf6\x72\xc0\x33\x00\x71\x97",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x08\xb2\xa3\xd4\x19\x39\xaa\x31\x66\x84\x93\xcb\x36\xcd\xcc\x4f\x16\xc4\xd9\xb4\xc8\x23\x8b\x73\xc2\xf6\x72\xc0\x33\x00\x71\x97",
|
||||
//U2F key for Slush Pool
|
||||
.label = "slushpool.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x38\x80\x4f\x2e\xff\x74\xf2\x28\xb7\x41\x51\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf\x23\xfa\x14\x6b\x13\xa3\x76\x66\x31\x4f",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x38\x80\x4f\x2e\xff\x74\xf2\x28\xb7\x41\x51\xc2\x01\xaa\x82\xe7\xe8\xee\xfc\xac\xfe\xcf\x23\xfa\x14\x6b\x13\xa3\x76\x66\x31\x4f",
|
||||
//U2F key for Slush Pool
|
||||
.label = "slushpool.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x2a\xc6\xad\x09\xa6\xd0\x77\x2c\x44\xda\x73\xa6\x07\x2f\x9d\x24\x0f\xc6\x85\x4a\x70\xd7\x9c\x10\x24\xff\x7c\x75\x59\x59\x32\x92",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x2a\xc6\xad\x09\xa6\xd0\x77\x2c\x44\xda\x73\xa6\x07\x2f\x9d\x24\x0f\xc6\x85\x4a\x70\xd7\x9c\x10\x24\xff\x7c\x75\x59\x59\x32\x92",
|
||||
//U2F key for Stripe
|
||||
.label = "stripe.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xfa\xbe\xec\xe3\x98\x2f\xad\x9d\xdc\xc9\x8f\x91\xbd\x2e\x75\xaf\xc7\xd1\xf4\xca\x54\x49\x29\xb2\xd0\xd0\x42\x12\xdf\xfa\x30\xfa",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xfa\xbe\xec\xe3\x98\x2f\xad\x9d\xdc\xc9\x8f\x91\xbd\x2e\x75\xaf\xc7\xd1\xf4\xca\x54\x49\x29\xb2\xd0\xd0\x42\x12\xdf\xfa\x30\xfa",
|
||||
//U2F key for Tutanota
|
||||
.label = "tutanota.com",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x1b\x3c\x16\xdd\x2f\x7c\x46\xe2\xb4\xc2\x89\xdc\x16\x74\x6b\xcc\x60\xdf\xcf\x0f\xb8\x18\xe1\x32\x15\x52\x6e\x14\x08\xe7\xf4\x68",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x1b\x3c\x16\xdd\x2f\x7c\x46\xe2\xb4\xc2\x89\xdc\x16\x74\x6b\xcc\x60\xdf\xcf\x0f\xb8\x18\xe1\x32\x15\x52\x6e\x14\x08\xe7\xf4\x68",
|
||||
//U2F key for u2f.bin.coffee
|
||||
.label = "u2f.bin.coffee",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xa6\x42\xd2\x1b\x7c\x6d\x55\xe1\xce\x23\xc5\x39\x98\x28\xd2\xc7\x49\xbf\x6a\x6e\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xed\x53\x08\x8b",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xa6\x42\xd2\x1b\x7c\x6d\x55\xe1\xce\x23\xc5\x39\x98\x28\xd2\xc7\x49\xbf\x6a\x6e\xf2\xfe\x03\xcc\x9e\x10\xcd\xf4\xed\x53\x08\x8b",
|
||||
//WebAuthn key for webauthn.bin.coffee
|
||||
.label = "webauthn.bin.coffee",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\x74\xa6\xea\x92\x13\xc9\x9c\x2f\x74\xb2\x24\x92\xb3\x20\xcf\x40\x26\x2a\x94\xc1\xa9\x50\xa0\x39\x7f\x29\x25\x0b\x60\x84\x1e\xf0",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\x74\xa6\xea\x92\x13\xc9\x9c\x2f\x74\xb2\x24\x92\xb3\x20\xcf\x40\x26\x2a\x94\xc1\xa9\x50\xa0\x39\x7f\x29\x25\x0b\x60\x84\x1e\xf0",
|
||||
//WebAuthn key for WebAuthn.io
|
||||
.label = "webauthn.io",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xf9\x5b\xc7\x38\x28\xee\x21\x0f\x9f\xd3\xbb\xe7\x2d\x97\x90\x80\x13\xb0\xa3\x75\x9e\x9a\xea\x3d\x0a\xe3\x18\x76\x6c\xd2\xe1\xad",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xf9\x5b\xc7\x38\x28\xee\x21\x0f\x9f\xd3\xbb\xe7\x2d\x97\x90\x80\x13\xb0\xa3\x75\x9e\x9a\xea\x3d\x0a\xe3\x18\x76\x6c\xd2\xe1\xad",
|
||||
//WebAuthn key for WebAuthn.me
|
||||
.label = "webauthn.me",
|
||||
.use_sign_count = NULL,
|
||||
.use_self_attestation = NULL,
|
||||
},
|
||||
{
|
||||
.rp_id_hash = (const uint8_t *)"\xc4\x6c\xef\x82\xad\x1b\x54\x64\x77\x59\x1d\x00\x8b\x08\x75\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94\x74\xbf\xea\x69\x69\x92\x5d\x03\xb7",
|
||||
.rp_id_hash =
|
||||
(const uint8_t *)
|
||||
"\xc4\x6c\xef\x82\xad\x1b\x54\x64\x77\x59\x1d\x00\x8b\x08\x75\x9e\xc3\xe6\xd2\xec\xb4\xf3\x94\x74\xbf\xea\x69\x69\x92\x5d\x03\xb7",
|
||||
//WebAuthn key for demo.yubico.com
|
||||
.label = "demo.yubico.com",
|
||||
.use_sign_count = NULL,
|
||||
@@ -304,8 +383,9 @@ static const known_app_t kapps[] = {
|
||||
|
||||
const known_app_t *find_app_by_rp_id_hash(const uint8_t *rp_id_hash) {
|
||||
for (const known_app_t *ka = &kapps[0]; ka->rp_id_hash != NULL; ka++) {
|
||||
if (memcmp(rp_id_hash, ka->rp_id_hash, 32) == 0)
|
||||
if (memcmp(rp_id_hash, ka->rp_id_hash, 32) == 0) {
|
||||
return ka;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
160
src/fido/management.c
Normal file
160
src/fido/management.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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 "apdu.h"
|
||||
#include "version.h"
|
||||
#include "files.h"
|
||||
#include "asn1.h"
|
||||
#include "management.h"
|
||||
|
||||
int man_process_apdu();
|
||||
int man_unload();
|
||||
|
||||
const uint8_t man_aid[] = {
|
||||
8,
|
||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17
|
||||
};
|
||||
extern void scan_all();
|
||||
extern void init_otp();
|
||||
int man_select(app_t *a, uint8_t force) {
|
||||
a->process_apdu = man_process_apdu;
|
||||
a->unload = man_unload;
|
||||
sprintf((char *) res_APDU, "%d.%d.0", PICO_FIDO_VERSION_MAJOR, PICO_FIDO_VERSION_MINOR);
|
||||
res_APDU_size = (uint16_t)strlen((char *) res_APDU);
|
||||
apdu.ne = res_APDU_size;
|
||||
if (force) {
|
||||
scan_all();
|
||||
init_otp();
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
INITIALIZER ( man_ctor ) {
|
||||
register_app(man_select, man_aid);
|
||||
}
|
||||
|
||||
int man_unload() {
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
bool cap_supported(uint16_t cap) {
|
||||
file_t *ef = search_dynamic_file(EF_DEV_CONF);
|
||||
if (file_has_data(ef)) {
|
||||
uint16_t tag = 0x0;
|
||||
uint8_t *tag_data = NULL, *p = NULL;
|
||||
uint16_t tag_len = 0;
|
||||
asn1_ctx_t ctxi;
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
while (walk_tlv(&ctxi, &p, &tag, &tag_len, &tag_data)) {
|
||||
if (tag == TAG_USB_ENABLED) {
|
||||
uint16_t ecaps = tag_data[0];
|
||||
if (tag_len == 2) {
|
||||
ecaps = get_uint16_t_be(tag_data);
|
||||
}
|
||||
return ecaps & cap;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int man_get_config() {
|
||||
file_t *ef = search_dynamic_file(EF_DEV_CONF);
|
||||
res_APDU_size = 0;
|
||||
res_APDU[res_APDU_size++] = 0; // Overall length. Filled later
|
||||
res_APDU[res_APDU_size++] = TAG_USB_SUPPORTED;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
|
||||
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
|
||||
res_APDU[res_APDU_size++] = TAG_SERIAL;
|
||||
res_APDU[res_APDU_size++] = 4;
|
||||
memcpy(res_APDU + res_APDU_size, pico_serial.id, 4);
|
||||
res_APDU_size += 4;
|
||||
res_APDU[res_APDU_size++] = TAG_FORM_FACTOR;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x01;
|
||||
res_APDU[res_APDU_size++] = TAG_VERSION;
|
||||
res_APDU[res_APDU_size++] = 3;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[res_APDU_size++] = 0;
|
||||
if (!file_has_data(ef)) {
|
||||
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
res_APDU[res_APDU_size++] = CAP_FIDO2 >> 8;
|
||||
res_APDU[res_APDU_size++] = CAP_OTP | CAP_U2F | CAP_OATH;
|
||||
res_APDU[res_APDU_size++] = TAG_DEVICE_FLAGS;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = FLAG_EJECT;
|
||||
res_APDU[res_APDU_size++] = TAG_CONFIG_LOCK;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = 0x00;
|
||||
}
|
||||
else {
|
||||
memcpy(res_APDU + res_APDU_size, file_get_data(ef), file_get_size(ef));
|
||||
res_APDU_size += file_get_size(ef);
|
||||
}
|
||||
res_APDU[0] = (uint8_t)(res_APDU_size - 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmd_read_config() {
|
||||
man_get_config();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_write_config() {
|
||||
if (apdu.data[0] != apdu.nc - 1) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = file_new(EF_DEV_CONF);
|
||||
file_put_data(ef, apdu.data + 1, (uint16_t)(apdu.nc - 1));
|
||||
low_flash_available();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
extern int cbor_reset();
|
||||
int cmd_factory_reset() {
|
||||
cbor_reset();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_READ_CONFIG 0x1D
|
||||
#define INS_WRITE_CONFIG 0x1C
|
||||
#define INS_RESET 0x1E // Reset device
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_READ_CONFIG, cmd_read_config },
|
||||
{ INS_WRITE_CONFIG, cmd_write_config },
|
||||
{ INS_RESET, cmd_factory_reset },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int man_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
55
src/fido/management.h
Normal file
55
src/fido/management.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 _MANAGEMENT_H_
|
||||
#define _MANAGEMENT_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
|
||||
#define TAG_USB_SUPPORTED 0x01
|
||||
#define TAG_SERIAL 0x02
|
||||
#define TAG_USB_ENABLED 0x03
|
||||
#define TAG_FORM_FACTOR 0x04
|
||||
#define TAG_VERSION 0x05
|
||||
#define TAG_AUTO_EJECT_TIMEOUT 0x06
|
||||
#define TAG_CHALRESP_TIMEOUT 0x07
|
||||
#define TAG_DEVICE_FLAGS 0x08
|
||||
#define TAG_APP_VERSIONS 0x09
|
||||
#define TAG_CONFIG_LOCK 0x0A
|
||||
#define TAG_UNLOCK 0x0B
|
||||
#define TAG_REBOOT 0x0C
|
||||
#define TAG_NFC_SUPPORTED 0x0D
|
||||
#define TAG_NFC_ENABLED 0x0E
|
||||
|
||||
#define CAP_OTP 0x01
|
||||
#define CAP_U2F 0x02
|
||||
#define CAP_FIDO2 0x200
|
||||
#define CAP_OATH 0x20
|
||||
#define CAP_PIV 0x10
|
||||
#define CAP_OPENPGP 0x08
|
||||
#define CAP_HSMAUTH 0x100
|
||||
|
||||
#define FLAG_REMOTE_WAKEUP 0x40
|
||||
#define FLAG_EJECT 0x80
|
||||
|
||||
extern bool cap_supported(uint16_t cap);
|
||||
extern int man_get_config();
|
||||
|
||||
#endif //_MANAGEMENT_H
|
||||
631
src/fido/oath.c
Normal file
631
src/fido/oath.c
Normal file
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
* 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 "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "version.h"
|
||||
#include "asn1.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "management.h"
|
||||
|
||||
#define MAX_OATH_CRED 255
|
||||
#define CHALLENGE_LEN 8
|
||||
#define MAX_OTP_COUNTER 3
|
||||
|
||||
#define TAG_NAME 0x71
|
||||
#define TAG_NAME_LIST 0x72
|
||||
#define TAG_KEY 0x73
|
||||
#define TAG_CHALLENGE 0x74
|
||||
#define TAG_RESPONSE 0x75
|
||||
#define TAG_T_RESPONSE 0x76
|
||||
#define TAG_NO_RESPONSE 0x77
|
||||
#define TAG_PROPERTY 0x78
|
||||
#define TAG_T_VERSION 0x79
|
||||
#define TAG_IMF 0x7a
|
||||
#define TAG_ALGO 0x7b
|
||||
#define TAG_TOUCH_RESPONSE 0x7c
|
||||
#define TAG_PASSWORD 0x80
|
||||
#define TAG_NEW_PASSWORD 0x81
|
||||
#define TAG_PIN_COUNTER 0x82
|
||||
|
||||
#define ALG_HMAC_SHA1 0x01
|
||||
#define ALG_HMAC_SHA256 0x02
|
||||
#define ALG_HMAC_SHA512 0x03
|
||||
#define ALG_MASK 0x0f
|
||||
|
||||
#define OATH_TYPE_HOTP 0x10
|
||||
#define OATH_TYPE_TOTP 0x20
|
||||
#define OATH_TYPE_MASK 0xf0
|
||||
|
||||
#define PROP_INC 0x01
|
||||
#define PROP_TOUCH 0x02
|
||||
|
||||
int oath_process_apdu();
|
||||
int oath_unload();
|
||||
|
||||
static bool validated = true;
|
||||
static uint8_t challenge[CHALLENGE_LEN] = { 0 };
|
||||
|
||||
const uint8_t oath_aid[] = {
|
||||
7,
|
||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01
|
||||
};
|
||||
|
||||
int oath_select(app_t *a, uint8_t force) {
|
||||
(void) force;
|
||||
if (cap_supported(CAP_OATH)) {
|
||||
a->process_apdu = oath_process_apdu;
|
||||
a->unload = oath_unload;
|
||||
res_APDU_size = 0;
|
||||
res_APDU[res_APDU_size++] = TAG_T_VERSION;
|
||||
res_APDU[res_APDU_size++] = 3;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[res_APDU_size++] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[res_APDU_size++] = 0;
|
||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||
res_APDU[res_APDU_size++] = 8;
|
||||
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8); res_APDU_size += 8;
|
||||
if (file_has_data(search_dynamic_file(EF_OATH_CODE)) == true) {
|
||||
random_gen(NULL, challenge, sizeof(challenge));
|
||||
res_APDU[res_APDU_size++] = TAG_CHALLENGE;
|
||||
res_APDU[res_APDU_size++] = sizeof(challenge);
|
||||
memcpy(res_APDU + res_APDU_size, challenge, sizeof(challenge));
|
||||
res_APDU_size += sizeof(challenge);
|
||||
}
|
||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_otp_pin)) {
|
||||
const uint8_t *pin_data = file_get_data(ef_otp_pin);
|
||||
res_APDU[res_APDU_size++] = TAG_PIN_COUNTER;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = *pin_data;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = TAG_ALGO;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
|
||||
apdu.ne = res_APDU_size;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
INITIALIZER ( oath_ctor ) {
|
||||
register_app(oath_select, oath_aid);
|
||||
}
|
||||
|
||||
int oath_unload() {
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
file_t *find_oath_cred(const uint8_t *name, size_t name_len) {
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
asn1_ctx_t ctxi, ef_tag = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (file_has_data(ef) && asn1_find_tag(&ctxi, TAG_NAME, &ef_tag) == true && ef_tag.len == name_len && memcmp(ef_tag.data, name, name_len) == 0) {
|
||||
return ef;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmd_put() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, imf = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||
if (asn1_find_tag(&ctxi, TAG_IMF, &imf) == false) {
|
||||
memcpy(apdu.data + apdu.nc, "\x7a\x08\x00\x00\x00\x00\x00\x00\x00\x00", 10);
|
||||
apdu.nc += 10;
|
||||
}
|
||||
else { //prepend zero-valued bytes
|
||||
if (imf.len < 8) {
|
||||
memmove(imf.data + (8 - imf.len), imf.data, imf.len);
|
||||
memset(imf.data, 0, 8 - imf.len);
|
||||
*(imf.data - 1) = 8;
|
||||
apdu.nc += (8 - imf.len);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
file_t *ef = find_oath_cred(name.data, name.len);
|
||||
if (file_has_data(ef)) {
|
||||
file_put_data(ef, apdu.data, (uint16_t)apdu.nc);
|
||||
low_flash_available();
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *tef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (!file_has_data(tef)) {
|
||||
tef = file_new((uint16_t)(EF_OATH_CRED + i));
|
||||
file_put_data(tef, apdu.data, (uint16_t)apdu.nc);
|
||||
low_flash_available();
|
||||
return SW_OK();
|
||||
}
|
||||
}
|
||||
return SW_FILE_FULL();
|
||||
}
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
|
||||
int cmd_delete() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, ctxo = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &ctxo) == true) {
|
||||
file_t *ef = find_oath_cred(ctxo.data, ctxo.len);
|
||||
if (ef) {
|
||||
delete_file(ef);
|
||||
return SW_OK();
|
||||
}
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
|
||||
const mbedtls_md_info_t *get_oath_md_info(uint8_t alg) {
|
||||
if ((alg & ALG_MASK) == ALG_HMAC_SHA1) {
|
||||
return mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
|
||||
}
|
||||
else if ((alg & ALG_MASK) == ALG_HMAC_SHA256) {
|
||||
return mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
}
|
||||
else if ((alg & ALG_MASK) == ALG_HMAC_SHA512) {
|
||||
return mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmd_set_code() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
if (apdu.nc == 0) {
|
||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
||||
validated = true;
|
||||
return SW_OK();
|
||||
}
|
||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_KEY, &key) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (key.len == 0) {
|
||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
||||
validated = true;
|
||||
return SW_OK();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
|
||||
const mbedtls_md_info_t *md_info = get_oath_md_info(key.data[0]);
|
||||
if (md_info == NULL) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
uint8_t hmac[64];
|
||||
int r = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, chal.data, chal.len, hmac);
|
||||
if (r != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
if (memcmp(hmac, resp.data, resp.len) != 0) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
random_gen(NULL, challenge, sizeof(challenge));
|
||||
file_t *ef = file_new(EF_OATH_CODE);
|
||||
file_put_data(ef, key.data, key.len);
|
||||
low_flash_available();
|
||||
validated = false;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_reset() {
|
||||
if (P1(apdu) != 0xde || P2(apdu) != 0xad) {
|
||||
return SW_INCORRECT_P1P2();
|
||||
}
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (file_has_data(ef)) {
|
||||
delete_file(ef);
|
||||
}
|
||||
}
|
||||
delete_file(search_dynamic_file(EF_OATH_CODE));
|
||||
flash_clear_file(search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF));
|
||||
low_flash_available();
|
||||
validated = true;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_list() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (file_has_data(ef)) {
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1);
|
||||
res_APDU[res_APDU_size++] = key.data[0];
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
}
|
||||
}
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_validate() {
|
||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, resp = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &resp) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
file_t *ef = search_dynamic_file(EF_OATH_CODE);
|
||||
if (file_has_data(ef) == false) {
|
||||
validated = true;
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
key.data = file_get_data(ef);
|
||||
key.len = file_get_size(ef);
|
||||
const mbedtls_md_info_t *md_info = get_oath_md_info(key.data[0]);
|
||||
if (md_info == NULL) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
uint8_t hmac[64];
|
||||
int ret = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, challenge, sizeof(challenge), hmac);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
if (memcmp(hmac, resp.data, resp.len) != 0) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
ret = mbedtls_md_hmac(md_info, key.data + 1, key.len - 1, chal.data, chal.len, hmac);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
validated = true;
|
||||
res_APDU[res_APDU_size++] = TAG_RESPONSE;
|
||||
res_APDU[res_APDU_size++] = mbedtls_md_get_size(md_info);
|
||||
memcpy(res_APDU + res_APDU_size, hmac, mbedtls_md_get_size(md_info));
|
||||
res_APDU_size += mbedtls_md_get_size(md_info);
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int calculate_oath(uint8_t truncate, const uint8_t *key, size_t key_len, const uint8_t *chal, size_t chal_len) {
|
||||
const mbedtls_md_info_t *md_info = get_oath_md_info(key[0]);
|
||||
if (md_info == NULL) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
uint8_t hmac[64];
|
||||
int r = mbedtls_md_hmac(md_info, key + 2, key_len - 2, chal, chal_len, hmac);
|
||||
size_t hmac_size = mbedtls_md_get_size(md_info);
|
||||
if (r != 0) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (truncate == 0x01) {
|
||||
res_APDU[res_APDU_size++] = 4 + 1;
|
||||
res_APDU[res_APDU_size++] = key[1];
|
||||
uint8_t offset = hmac[hmac_size - 1] & 0x0f;
|
||||
res_APDU[res_APDU_size++] = hmac[offset] & 0x7f;
|
||||
res_APDU[res_APDU_size++] = hmac[offset + 1];
|
||||
res_APDU[res_APDU_size++] = hmac[offset + 2];
|
||||
res_APDU[res_APDU_size++] = hmac[offset + 3];
|
||||
}
|
||||
else {
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(hmac_size + 1);
|
||||
res_APDU[res_APDU_size++] = key[1];
|
||||
memcpy(res_APDU + res_APDU_size, hmac, hmac_size); res_APDU_size += (uint16_t)hmac_size;
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
int cmd_calculate() {
|
||||
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
||||
return SW_INCORRECT_P1P2();
|
||||
}
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
file_t *ef = find_oath_cred(name.data, name.len);
|
||||
if (file_has_data(ef) == false) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
asn1_ctx_t ctxe;
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
||||
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
|
||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
}
|
||||
|
||||
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
||||
|
||||
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||
uint64_t v = get_uint64_t_be(chal.data);
|
||||
size_t ef_size = file_get_size(ef);
|
||||
v++;
|
||||
uint8_t *tmp = (uint8_t *) calloc(1, ef_size);
|
||||
memcpy(tmp, file_get_data(ef), ef_size);
|
||||
asn1_ctx_t ctxt;
|
||||
asn1_ctx_init(tmp, (uint16_t)ef_size, &ctxt);
|
||||
asn1_find_tag(&ctxt, TAG_IMF, &chal);
|
||||
put_uint64_t_be(v, chal.data);
|
||||
file_put_data(ef, tmp, (uint16_t)ef_size);
|
||||
low_flash_available();
|
||||
free(tmp);
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_calculate_all() {
|
||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, prop = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (P2(apdu) != 0x0 && P2(apdu) != 0x1) {
|
||||
return SW_INCORRECT_P1P2();
|
||||
}
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_CHALLENGE, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
res_APDU_size = 0;
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (file_has_data(ef)) {
|
||||
const uint8_t *ef_data = file_get_data(ef);
|
||||
size_t ef_len = file_get_size(ef);
|
||||
asn1_ctx_t ctxe;
|
||||
asn1_ctx_init((uint8_t *)ef_data, (uint16_t)ef_len, &ctxe);
|
||||
if (asn1_find_tag(&ctxe, TAG_NAME, &name) == false || asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||
continue;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)name.len;
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
if ((key.data[0] & OATH_TYPE_MASK) == OATH_TYPE_HOTP) {
|
||||
res_APDU[res_APDU_size++] = TAG_NO_RESPONSE;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = key.data[1];
|
||||
}
|
||||
else if (asn1_find_tag(&ctxe, TAG_PROPERTY, &prop) == true && (prop.data[0] & PROP_TOUCH)) {
|
||||
res_APDU[res_APDU_size++] = TAG_TOUCH_RESPONSE;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = key.data[1];
|
||||
}
|
||||
else {
|
||||
res_APDU[res_APDU_size++] = TAG_RESPONSE + P2(apdu);
|
||||
int ret = calculate_oath(P2(apdu), key.data, key.len, chal.data, chal.len);
|
||||
if (ret != PICOKEY_OK) {
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = key.data[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_send_remaining() {
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_set_otp_pin() {
|
||||
uint8_t hsh[33] = { 0 };
|
||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_otp_pin)) {
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, pw = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
hsh[0] = MAX_OTP_COUNTER;
|
||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
||||
low_flash_available();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_change_otp_pin() {
|
||||
uint8_t hsh[33] = { 0 };
|
||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_otp_pin)) {
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, pw = { 0 }, new_pw = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||
if (memcmp(file_get_data(ef_otp_pin) + 1, hsh + 1, 32) != 0) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_NEW_PASSWORD, &new_pw) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
hsh[0] = MAX_OTP_COUNTER;
|
||||
double_hash_pin(new_pw.data, new_pw.len, hsh + 1);
|
||||
file_put_data(ef_otp_pin, hsh, sizeof(hsh));
|
||||
low_flash_available();
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_verify_otp_pin() {
|
||||
uint8_t hsh[33] = { 0 }, data_hsh[33];
|
||||
file_t *ef_otp_pin = search_by_fid(EF_OTP_PIN, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_otp_pin)) {
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
asn1_ctx_t ctxi, pw = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_PASSWORD, &pw) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
double_hash_pin(pw.data, pw.len, hsh + 1);
|
||||
memcpy(data_hsh, file_get_data(ef_otp_pin), sizeof(data_hsh));
|
||||
if (data_hsh[0] == 0 || memcmp(data_hsh + 1, hsh + 1, 32) != 0) {
|
||||
if (data_hsh[0] > 0) {
|
||||
data_hsh[0] -= 1;
|
||||
}
|
||||
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
||||
low_flash_available();
|
||||
validated = false;
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
data_hsh[0] = MAX_OTP_COUNTER;
|
||||
file_put_data(ef_otp_pin, data_hsh, sizeof(data_hsh));
|
||||
low_flash_available();
|
||||
validated = true;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_verify_hotp() {
|
||||
asn1_ctx_t ctxi, key = { 0 }, chal = { 0 }, name = { 0 }, code = { 0 };
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
uint32_t code_int = 0;
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
file_t *ef = find_oath_cred(name.data, name.len);
|
||||
if (file_has_data(ef) == false) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
asn1_ctx_t ctxe;
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxe);
|
||||
if (asn1_find_tag(&ctxe, TAG_KEY, &key) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
|
||||
if ((key.data[0] & OATH_TYPE_MASK) != OATH_TYPE_HOTP) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
if (asn1_find_tag(&ctxe, TAG_IMF, &chal) == false) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_RESPONSE, &code) == true) {
|
||||
code_int = get_uint32_t_be(code.data);
|
||||
}
|
||||
|
||||
int ret = calculate_oath(0x01, key.data, key.len, chal.data, chal.len);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return SW_EXEC_ERROR();
|
||||
}
|
||||
uint32_t res_int = get_uint32_t_be(res_APDU + 2);
|
||||
if (res_APDU[1] == 6) {
|
||||
res_int %= (uint32_t) 1e6;
|
||||
}
|
||||
else {
|
||||
res_int %= (uint32_t) 1e8;
|
||||
}
|
||||
if (res_int != code_int) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
res_APDU_size = 0;
|
||||
apdu.ne = 0;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_PUT 0x01
|
||||
#define INS_DELETE 0x02
|
||||
#define INS_SET_CODE 0x03
|
||||
#define INS_RESET 0x04
|
||||
#define INS_LIST 0xa1
|
||||
#define INS_CALCULATE 0xa2
|
||||
#define INS_VALIDATE 0xa3
|
||||
#define INS_CALC_ALL 0xa4
|
||||
#define INS_SEND_REMAINING 0xa5
|
||||
#define INS_VERIFY_CODE 0xb1
|
||||
#define INS_VERIFY_PIN 0xb2
|
||||
#define INS_CHANGE_PIN 0xb3
|
||||
#define INS_SET_PIN 0xb4
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_PUT, cmd_put },
|
||||
{ INS_DELETE, cmd_delete },
|
||||
{ INS_SET_CODE, cmd_set_code },
|
||||
{ INS_RESET, cmd_reset },
|
||||
{ INS_LIST, cmd_list },
|
||||
{ INS_VALIDATE, cmd_validate },
|
||||
{ INS_CALCULATE, cmd_calculate },
|
||||
{ INS_CALC_ALL, cmd_calculate_all },
|
||||
{ INS_SEND_REMAINING, cmd_send_remaining },
|
||||
{ INS_SET_PIN, cmd_set_otp_pin },
|
||||
{ INS_CHANGE_PIN, cmd_change_otp_pin },
|
||||
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
|
||||
{ INS_VERIFY_CODE, cmd_verify_hotp },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int oath_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
if (cap_supported(CAP_OATH)) {
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
619
src/fido/otp.c
Normal file
619
src/fido/otp.c
Normal file
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* 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 "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "version.h"
|
||||
#include "asn1.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "usb.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "mbedtls/aes.h"
|
||||
#include "management.h"
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "tusb.h"
|
||||
#endif
|
||||
|
||||
#define FIXED_SIZE 16
|
||||
#define KEY_SIZE 16
|
||||
#define UID_SIZE 6
|
||||
#define KEY_SIZE_OATH 20
|
||||
#define ACC_CODE_SIZE 6
|
||||
|
||||
#define CONFIG1_VALID 0x01
|
||||
#define CONFIG2_VALID 0x02
|
||||
#define CONFIG1_TOUCH 0x04
|
||||
#define CONFIG2_TOUCH 0x08
|
||||
#define CONFIG_LED_INV 0x10
|
||||
#define CONFIG_STATUS_MASK 0x1f
|
||||
|
||||
/* EXT Flags */
|
||||
#define SERIAL_BTN_VISIBLE 0x01 // Serial number visible at startup (button press)
|
||||
#define SERIAL_USB_VISIBLE 0x02 // Serial number visible in USB iSerial field
|
||||
#define SERIAL_API_VISIBLE 0x04 // Serial number visible via API call
|
||||
#define USE_NUMERIC_KEYPAD 0x08 // Use numeric keypad for digits
|
||||
#define FAST_TRIG 0x10 // Use fast trig if only cfg1 set
|
||||
#define ALLOW_UPDATE 0x20 // Allow update of existing configuration (selected flags + access code)
|
||||
#define DORMANT 0x40 // Dormant config (woken up, flag removed, requires update flag)
|
||||
#define LED_INV 0x80 // LED idle state is off rather than on
|
||||
#define EXTFLAG_UPDATE_MASK (SERIAL_BTN_VISIBLE | SERIAL_USB_VISIBLE | SERIAL_API_VISIBLE | \
|
||||
USE_NUMERIC_KEYPAD | FAST_TRIG | ALLOW_UPDATE | DORMANT | LED_INV)
|
||||
|
||||
/* TKT Flags */
|
||||
#define TAB_FIRST 0x01 // Send TAB before first part
|
||||
#define APPEND_TAB1 0x02 // Send TAB after first part
|
||||
#define APPEND_TAB2 0x04 // Send TAB after second part
|
||||
#define APPEND_DELAY1 0x08 // Add 0.5s delay after first part
|
||||
#define APPEND_DELAY2 0x10 // Add 0.5s delay after second part
|
||||
#define APPEND_CR 0x20 // Append CR as final character
|
||||
#define OATH_HOTP 0x40 // OATH HOTP mode
|
||||
#define CHAL_RESP 0x40 // Challenge-response enabled (both must be set)
|
||||
#define PROTECT_CFG2 0x80 // Block update of config 2 unless config 2 is configured and has this bit set
|
||||
#define TKTFLAG_UPDATE_MASK (TAB_FIRST | APPEND_TAB1 | APPEND_TAB2 | APPEND_DELAY1 | APPEND_DELAY2 | \
|
||||
APPEND_CR)
|
||||
|
||||
/* CFG Flags */
|
||||
#define SEND_REF 0x01 // Send reference string (0..F) before data
|
||||
#define PACING_10MS 0x04 // Add 10ms intra-key pacing
|
||||
#define PACING_20MS 0x08 // Add 20ms intra-key pacing
|
||||
#define STATIC_TICKET 0x20 // Static ticket generation
|
||||
// Static
|
||||
#define SHORT_TICKET 0x02 // Send truncated ticket (half length)
|
||||
#define STRONG_PW1 0x10 // Strong password policy flag #1 (mixed case)
|
||||
#define STRONG_PW2 0x40 // Strong password policy flag #2 (subtitute 0..7 to digits)
|
||||
#define MAN_UPDATE 0x80 // Allow manual (local) update of static OTP
|
||||
// Challenge (no keyboard)
|
||||
#define HMAC_LT64 0x04 // Set when HMAC message is less than 64 bytes
|
||||
#define CHAL_BTN_TRIG 0x08 // Challenge-response operation requires button press
|
||||
#define CHAL_YUBICO 0x20 // Challenge-response enabled - Yubico OTP mode
|
||||
#define CHAL_HMAC 0x22 // Challenge-response enabled - HMAC-SHA1
|
||||
// OATH
|
||||
#define OATH_HOTP8 0x02 // Generate 8 digits HOTP rather than 6 digits
|
||||
#define OATH_FIXED_MODHEX1 0x10 // First byte in fixed part sent as modhex
|
||||
#define OATH_FIXED_MODHEX2 0x40 // First two bytes in fixed part sent as modhex
|
||||
#define OATH_FIXED_MODHEX 0x50 // Fixed part sent as modhex
|
||||
#define OATH_FIXED_MASK 0x50 // Mask to get out fixed flags
|
||||
#define CFGFLAG_UPDATE_MASK (PACING_10MS | PACING_20MS)
|
||||
|
||||
static uint8_t config_seq = { 1 };
|
||||
|
||||
PACK(
|
||||
typedef struct otp_config {
|
||||
uint8_t fixed_data[FIXED_SIZE];
|
||||
uint8_t uid[UID_SIZE];
|
||||
uint8_t aes_key[KEY_SIZE];
|
||||
uint8_t acc_code[ACC_CODE_SIZE];
|
||||
uint8_t fixed_size;
|
||||
uint8_t ext_flags;
|
||||
uint8_t tkt_flags;
|
||||
uint8_t cfg_flags;
|
||||
uint8_t rfu[2];
|
||||
uint16_t crc;
|
||||
}) otp_config_t;
|
||||
|
||||
#define otp_config_size sizeof(otp_config_t)
|
||||
uint16_t otp_status();
|
||||
|
||||
int otp_process_apdu();
|
||||
int otp_unload();
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
#endif
|
||||
|
||||
const uint8_t otp_aid[] = {
|
||||
7,
|
||||
0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
|
||||
};
|
||||
|
||||
int otp_select(app_t *a, uint8_t force) {
|
||||
(void) force;
|
||||
if (cap_supported(CAP_OTP)) {
|
||||
a->process_apdu = otp_process_apdu;
|
||||
a->unload = otp_unload;
|
||||
if (file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ||
|
||||
file_has_data(search_dynamic_file(EF_OTP_SLOT2))) {
|
||||
config_seq = 1;
|
||||
}
|
||||
else {
|
||||
config_seq = 0;
|
||||
}
|
||||
otp_status();
|
||||
memmove(res_APDU, res_APDU + 1, 6);
|
||||
res_APDU_size = 6;
|
||||
apdu.ne = res_APDU_size;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
uint8_t modhex_tab[] =
|
||||
{ 'c', 'b', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'r', 't', 'u', 'v' };
|
||||
int encode_modhex(const uint8_t *in, size_t len, uint8_t *out) {
|
||||
for (size_t l = 0; l < len; l++) {
|
||||
*out++ = modhex_tab[in[l] >> 4];
|
||||
*out++ = modhex_tab[in[l] & 0xf];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static bool scanned = false;
|
||||
extern void scan_all();
|
||||
void init_otp() {
|
||||
if (scanned == false) {
|
||||
scan_all();
|
||||
for (uint8_t i = 0; i < 2; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_OTP_SLOT1 + i);
|
||||
uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *) data;
|
||||
if (file_has_data(ef) && !(otp_config->tkt_flags & OATH_HOTP) &&
|
||||
!(otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET)) {
|
||||
uint16_t counter = get_uint16_t_be(data + otp_config_size);
|
||||
if (++counter <= 0x7fff) {
|
||||
uint8_t new_data[otp_config_size + 8];
|
||||
memcpy(new_data, data, sizeof(new_data));
|
||||
put_uint16_t_be(counter, new_data + otp_config_size);
|
||||
file_put_data(ef, new_data, sizeof(new_data));
|
||||
}
|
||||
}
|
||||
}
|
||||
scanned = true;
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
extern int calculate_oath(uint8_t truncate,
|
||||
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;
|
||||
for (size_t idx = 0; idx < data_len; idx++) {
|
||||
crc ^= data[idx];
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
uint16_t j = crc & 0x1;
|
||||
crc >>= 1;
|
||||
if (j == 1) {
|
||||
crc ^= 0x8408;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
static uint8_t session_counter[2] = { 0 };
|
||||
#endif
|
||||
int otp_button_pressed(uint8_t slot) {
|
||||
init_otp();
|
||||
if (!cap_supported(CAP_OTP)) {
|
||||
return 3;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
const uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *) data;
|
||||
if (file_has_data(ef) == false) {
|
||||
return 1;
|
||||
}
|
||||
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||
return 2;
|
||||
}
|
||||
if (otp_config->tkt_flags & OATH_HOTP) {
|
||||
uint8_t tmp_key[KEY_SIZE + 2];
|
||||
tmp_key[0] = 0x01;
|
||||
memcpy(tmp_key + 2, otp_config->aes_key, KEY_SIZE);
|
||||
uint64_t imf = 0;
|
||||
const uint8_t *p = data + otp_config_size;
|
||||
imf = get_uint64_t_be(p);
|
||||
p += 8;
|
||||
if (imf == 0) {
|
||||
imf = get_uint16_t_be(otp_config->uid + 4);
|
||||
}
|
||||
uint8_t chal[8];
|
||||
put_uint64_t_be(imf, chal);
|
||||
res_APDU_size = 0;
|
||||
int ret = calculate_oath(1, tmp_key, sizeof(tmp_key), chal, sizeof(chal));
|
||||
if (ret == PICOKEY_OK) {
|
||||
uint32_t base = otp_config->cfg_flags & OATH_HOTP8 ? 1e8 : 1e6;
|
||||
uint32_t number = get_uint16_t_be(res_APDU + 2);
|
||||
number %= base;
|
||||
char number_str[9];
|
||||
if (otp_config->cfg_flags & OATH_HOTP8) {
|
||||
sprintf(number_str, "%08lu", (long unsigned int) number);
|
||||
add_keyboard_buffer((const uint8_t *) number_str, 8, true);
|
||||
}
|
||||
else {
|
||||
sprintf(number_str, "%06lu", (long unsigned int) number);
|
||||
add_keyboard_buffer((const uint8_t *) number_str, 6, true);
|
||||
}
|
||||
imf++;
|
||||
uint8_t new_chal[8];
|
||||
put_uint64_t_be(imf, new_chal);
|
||||
uint8_t new_otp_config[otp_config_size + sizeof(new_chal)];
|
||||
memcpy(new_otp_config, otp_config, otp_config_size);
|
||||
memcpy(new_otp_config + otp_config_size, new_chal, sizeof(new_chal));
|
||||
file_put_data(ef, new_otp_config, sizeof(new_otp_config));
|
||||
low_flash_available();
|
||||
}
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
||||
}
|
||||
}
|
||||
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
|
||||
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
|
||||
//fixed_size /= 2;
|
||||
}
|
||||
add_keyboard_buffer(otp_config->fixed_data, fixed_size, false);
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *) "\x28", 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
uint8_t otpk[22], *po = otpk;
|
||||
bool update_counter = false;
|
||||
uint16_t counter = get_uint16_t_be(data + otp_config_size), crc = 0;
|
||||
uint32_t ts = board_millis() / 1000;
|
||||
if (counter == 0) {
|
||||
update_counter = true;
|
||||
counter = 1;
|
||||
}
|
||||
memcpy(po, otp_config->fixed_data, 6);
|
||||
po += 6;
|
||||
memcpy(po, otp_config->uid, UID_SIZE);
|
||||
po += UID_SIZE;
|
||||
po += put_uint16_t_le(counter, po);
|
||||
ts >>= 1;
|
||||
*po++ = ts & 0xff;
|
||||
*po++ = ts >> 8;
|
||||
*po++ = ts >> 16;
|
||||
*po++ = session_counter[slot - 1];
|
||||
random_gen(NULL, po, 2);
|
||||
po += 2;
|
||||
crc = calculate_crc(otpk + 6, 14);
|
||||
po += put_uint16_t_le(~crc, po);
|
||||
mbedtls_aes_context ctx;
|
||||
mbedtls_aes_init(&ctx);
|
||||
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
||||
mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, otpk + 6, otpk + 6);
|
||||
mbedtls_aes_free(&ctx);
|
||||
uint8_t otp_out[44];
|
||||
encode_modhex(otpk, sizeof(otpk), otp_out);
|
||||
add_keyboard_buffer((const uint8_t *) otp_out, sizeof(otp_out), true);
|
||||
if (otp_config->tkt_flags & APPEND_CR) {
|
||||
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
||||
}
|
||||
|
||||
if (++session_counter[slot - 1] == 0) {
|
||||
if (++counter <= 0x7fff) {
|
||||
update_counter = true;
|
||||
}
|
||||
}
|
||||
if (update_counter == true) {
|
||||
uint8_t new_data[otp_config_size + 8];
|
||||
memcpy(new_data, data, sizeof(new_data));
|
||||
put_uint16_t_be(counter, new_data + otp_config_size);
|
||||
file_put_data(ef, new_data, sizeof(new_data));
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) slot;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
INITIALIZER( otp_ctor ) {
|
||||
register_app(otp_select, otp_aid);
|
||||
button_pressed_cb = otp_button_pressed;
|
||||
#ifndef ENABLE_EMULATION
|
||||
hid_set_report_cb = otp_hid_set_report_cb;
|
||||
hid_get_report_cb = otp_hid_get_report_cb;
|
||||
#endif
|
||||
}
|
||||
|
||||
int otp_unload() {
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
uint16_t otp_status() {
|
||||
if (scanned == false) {
|
||||
scan_all();
|
||||
scanned = true;
|
||||
}
|
||||
res_APDU_size = 0;
|
||||
res_APDU[1] = PICO_FIDO_VERSION_MAJOR;
|
||||
res_APDU[2] = PICO_FIDO_VERSION_MINOR;
|
||||
res_APDU[3] = 0;
|
||||
res_APDU[4] = config_seq;
|
||||
res_APDU[5] = (CONFIG2_TOUCH | CONFIG1_TOUCH) |
|
||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT1)) ? CONFIG1_VALID :
|
||||
0x00) |
|
||||
(file_has_data(search_dynamic_file(EF_OTP_SLOT2)) ? CONFIG2_VALID :
|
||||
0x00);
|
||||
res_APDU[6] = 0;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
bool check_crc(const otp_config_t *data) {
|
||||
uint16_t crc = calculate_crc((const uint8_t *) data, otp_config_size);
|
||||
return crc == 0xF0B8;
|
||||
}
|
||||
|
||||
int cmd_otp() {
|
||||
uint8_t p1 = P1(apdu), p2 = P2(apdu);
|
||||
if (p2 != 0x00) {
|
||||
return SW_INCORRECT_P1P2();
|
||||
}
|
||||
if (p1 == 0x01 || p1 == 0x03) { // Configure slot
|
||||
otp_config_t *odata = (otp_config_t *) apdu.data;
|
||||
file_t *ef = file_new(p1 == 0x01 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
if (file_has_data(ef)) {
|
||||
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
||||
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
}
|
||||
for (int c = 0; c < otp_config_size; c++) {
|
||||
if (apdu.data[c] != 0) {
|
||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
memset(apdu.data + otp_config_size, 0, 8); // Add 8 bytes extra
|
||||
file_put_data(ef, apdu.data, otp_config_size + 8);
|
||||
low_flash_available();
|
||||
config_seq++;
|
||||
return otp_status();
|
||||
}
|
||||
}
|
||||
// Delete slot
|
||||
delete_file(ef);
|
||||
if (!file_has_data(search_dynamic_file(EF_OTP_SLOT1)) &&
|
||||
!file_has_data(search_dynamic_file(EF_OTP_SLOT2))) {
|
||||
config_seq = 0;
|
||||
}
|
||||
return otp_status();
|
||||
}
|
||||
else if (p1 == 0x04 || p1 == 0x05) {
|
||||
otp_config_t *odata = (otp_config_t *) apdu.data;
|
||||
if (odata->rfu[0] != 0 || odata->rfu[1] != 0 || check_crc(odata) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = search_dynamic_file(p1 == 0x04 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
if (file_has_data(ef)) {
|
||||
otp_config_t *otpc = (otp_config_t *) file_get_data(ef);
|
||||
if (memcmp(otpc->acc_code, apdu.data + otp_config_size, ACC_CODE_SIZE) != 0) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
memcpy(apdu.data, file_get_data(ef), FIXED_SIZE + UID_SIZE + KEY_SIZE);
|
||||
odata->fixed_size = otpc->fixed_size;
|
||||
odata->ext_flags = (otpc->ext_flags & ~EXTFLAG_UPDATE_MASK) |
|
||||
(odata->ext_flags & EXTFLAG_UPDATE_MASK);
|
||||
odata->tkt_flags = (otpc->tkt_flags & ~TKTFLAG_UPDATE_MASK) |
|
||||
(odata->tkt_flags & TKTFLAG_UPDATE_MASK);
|
||||
odata->cfg_flags = (otpc->cfg_flags & ~CFGFLAG_UPDATE_MASK) |
|
||||
(odata->cfg_flags & CFGFLAG_UPDATE_MASK);
|
||||
file_put_data(ef, apdu.data, otp_config_size);
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
else if (p1 == 0x06) {
|
||||
uint8_t tmp[otp_config_size + 8];
|
||||
bool ef1_data = false;
|
||||
file_t *ef1 = file_new(EF_OTP_SLOT1);
|
||||
file_t *ef2 = file_new(EF_OTP_SLOT2);
|
||||
if (file_has_data(ef1)) {
|
||||
memcpy(tmp, file_get_data(ef1), file_get_size(ef1));
|
||||
ef1_data = true;
|
||||
}
|
||||
if (file_has_data(ef2)) {
|
||||
file_put_data(ef1, file_get_data(ef2), file_get_size(ef2));
|
||||
}
|
||||
else {
|
||||
delete_file(ef1);
|
||||
}
|
||||
if (ef1_data) {
|
||||
file_put_data(ef2, tmp, sizeof(tmp));
|
||||
}
|
||||
else {
|
||||
delete_file(ef2);
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else if (p1 == 0x10) {
|
||||
memcpy(res_APDU, pico_serial.id, 4);
|
||||
res_APDU_size = 4;
|
||||
}
|
||||
else if (p1 == 0x13) {
|
||||
man_get_config();
|
||||
}
|
||||
else if (p1 == 0x30 || p1 == 0x38 || p1 == 0x20 || p1 == 0x28) {
|
||||
file_t *ef = search_dynamic_file(p1 == 0x30 || p1 == 0x20 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
if (file_has_data(ef)) {
|
||||
otp_config_t *otp_config = (otp_config_t *) file_get_data(ef);
|
||||
if (!(otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP)) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
int ret = 0;
|
||||
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 (ret == 0) {
|
||||
res_APDU_size = 20;
|
||||
}
|
||||
}
|
||||
else if (p1 == 0x20 || p1 == 0x28) {
|
||||
uint8_t challenge[16];
|
||||
memcpy(challenge, apdu.data, 6);
|
||||
memcpy(challenge + 6, pico_serial_str, 10);
|
||||
mbedtls_aes_context ctx;
|
||||
mbedtls_aes_init(&ctx);
|
||||
mbedtls_aes_setkey_enc(&ctx, otp_config->aes_key, 128);
|
||||
ret = mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, challenge, res_APDU);
|
||||
mbedtls_aes_free(&ctx);
|
||||
if (ret == 0) {
|
||||
res_APDU_size = 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_OTP 0x01
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_OTP, cmd_otp },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
int otp_process_apdu() {
|
||||
if (CLA(apdu) != 0x00) {
|
||||
return SW_CLA_NOT_SUPPORTED();
|
||||
}
|
||||
if (cap_supported(CAP_OTP)) {
|
||||
for (const cmd_t *cmd = cmds; cmd->ins != 0x00; cmd++) {
|
||||
if (cmd->ins == INS(apdu)) {
|
||||
int r = cmd->cmd_handler();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
|
||||
uint8_t otp_frame_rx[70] = {0};
|
||||
uint8_t otp_frame_tx[70] = {0};
|
||||
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
|
||||
uint8_t otp_header[4] = {0};
|
||||
|
||||
extern uint16_t *get_send_buffer_size(uint8_t itf);
|
||||
|
||||
int otp_send_frame(uint8_t *frame, size_t frame_len) {
|
||||
uint16_t crc = calculate_crc(frame, frame_len);
|
||||
frame_len += put_uint16_t_le(~crc, frame + frame_len);
|
||||
*get_send_buffer_size(ITF_KEYBOARD) = frame_len;
|
||||
otp_exp_seq = (frame_len / 7);
|
||||
if (frame_len % 7) {
|
||||
otp_exp_seq++;
|
||||
}
|
||||
otp_curr_seq = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int otp_hid_set_report_cb(uint8_t itf,
|
||||
uint8_t report_id,
|
||||
hid_report_type_t report_type,
|
||||
uint8_t const *buffer,
|
||||
uint16_t bufsize)
|
||||
{
|
||||
if (itf == ITF_KEYBOARD) {
|
||||
if (report_type == 3) {
|
||||
DEBUG_PAYLOAD(buffer, bufsize);
|
||||
if (buffer[7] == 0xFF) { // reset
|
||||
*get_send_buffer_size(ITF_KEYBOARD) = 0;
|
||||
otp_curr_seq = otp_exp_seq = 0;
|
||||
memset(otp_frame_tx, 0, sizeof(otp_frame_tx));
|
||||
}
|
||||
else if (buffer[7] & 0x80) { // a frame
|
||||
uint8_t rseq = buffer[7] & 0x1F;
|
||||
if (rseq < 10) {
|
||||
if (rseq == 0) {
|
||||
memset(otp_frame_rx, 0, sizeof(otp_frame_rx));
|
||||
}
|
||||
memcpy(otp_frame_rx + rseq * 7, buffer, 7);
|
||||
if (rseq == 9) {
|
||||
DEBUG_DATA(otp_frame_rx, sizeof(otp_frame_rx));
|
||||
uint16_t residual_crc = calculate_crc(otp_frame_rx, 64), rcrc = 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;
|
||||
int ret = otp_process_apdu();
|
||||
if (ret == 0x9000 && res_APDU_size > 0) {
|
||||
otp_send_frame(apdu.rdata, apdu.rlen);
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("[OTP] Bad CRC!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t otp_hid_get_report_cb(uint8_t itf,
|
||||
uint8_t report_id,
|
||||
hid_report_type_t report_type,
|
||||
uint8_t *buffer,
|
||||
uint16_t reqlen) {
|
||||
// TODO not Implemented
|
||||
(void) itf;
|
||||
(void) report_id;
|
||||
(void) report_type;
|
||||
(void) buffer;
|
||||
(void) reqlen;
|
||||
uint16_t send_buffer_size = *get_send_buffer_size(ITF_KEYBOARD);
|
||||
if (send_buffer_size > 0) {
|
||||
uint8_t seq = otp_curr_seq++;
|
||||
memset(buffer, 0, 8);
|
||||
memcpy(buffer, otp_frame_tx + 7 * seq, MIN(7, send_buffer_size));
|
||||
buffer[7] = 0x40 | seq;
|
||||
DEBUG_DATA(buffer, 8);
|
||||
*get_send_buffer_size(ITF_KEYBOARD) -= MIN(7, send_buffer_size);
|
||||
}
|
||||
else if (otp_curr_seq == otp_exp_seq && otp_exp_seq > 0) {
|
||||
memset(buffer, 0, 7);
|
||||
buffer[7] = 0x40;
|
||||
DEBUG_DATA(buffer,8);
|
||||
otp_curr_seq = otp_exp_seq = 0;
|
||||
}
|
||||
else {
|
||||
res_APDU = buffer;
|
||||
otp_status();
|
||||
}
|
||||
|
||||
return reqlen;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -18,10 +18,9 @@
|
||||
#ifndef __VERSION_H_
|
||||
#define __VERSION_H_
|
||||
|
||||
#define PICO_FIDO_VERSION 0x0200
|
||||
#define PICO_FIDO_VERSION 0x0602
|
||||
|
||||
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
||||
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
7
tests/build-in-docker.sh
Executable file
7
tests/build-in-docker.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
source tests/docker_env.sh
|
||||
#run_in_docker rm -rf CMakeFiles
|
||||
run_in_docker mkdir -p build_in_docker
|
||||
run_in_docker -w "$PWD/build_in_docker" cmake -DENABLE_EMULATION=1 ..
|
||||
run_in_docker -w "$PWD/build_in_docker" make -j ${NUM_PROC}
|
||||
452
tests/conftest.py
Normal file
452
tests/conftest.py
Normal file
@@ -0,0 +1,452 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
from http import client
|
||||
from fido2.hid import CtapHidDevice
|
||||
from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError, _Ctap1ClientBackend
|
||||
from fido2.attestation import FidoU2FAttestation
|
||||
from fido2.ctap2.pin import ClientPin
|
||||
from fido2.server import Fido2Server
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType
|
||||
from utils import *
|
||||
from fido2.cose import ES256
|
||||
import sys
|
||||
import pytest
|
||||
import os
|
||||
import struct
|
||||
from inputimeout import inputimeout
|
||||
|
||||
|
||||
DEFAULT_PIN='12345678'
|
||||
|
||||
|
||||
class Packet(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def ToWireFormat(
|
||||
self,
|
||||
):
|
||||
return self.data
|
||||
|
||||
@staticmethod
|
||||
def FromWireFormat(pkt_size, data):
|
||||
return Packet(data)
|
||||
|
||||
class CliInteraction(UserInteraction):
|
||||
def prompt_up(self):
|
||||
print("\nTouch your authenticator device now...\n")
|
||||
|
||||
def request_pin(self, permissions, rd_id):
|
||||
return DEFAULT_PIN
|
||||
|
||||
def request_uv(self, permissions, rd_id):
|
||||
print("User Verification required.")
|
||||
return True
|
||||
|
||||
class DeviceSelectCredential:
|
||||
def __init__(self, number):
|
||||
pass
|
||||
|
||||
def __call__(self, status):
|
||||
pass
|
||||
|
||||
class Device():
|
||||
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
|
||||
self.__user = None
|
||||
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
||||
self.__set_server(rp=rp, attestation=attestation)
|
||||
|
||||
|
||||
def __set_client(self, origin, user_interaction, uv):
|
||||
self.__uv = uv
|
||||
self.__dev = None
|
||||
self.__origin = origin
|
||||
self.__user_interaction = user_interaction
|
||||
|
||||
# Locate a device
|
||||
self.__dev = next(CtapHidDevice.list_devices(), None)
|
||||
self.dev = self.__dev
|
||||
if self.__dev is not None:
|
||||
print("Use USB HID channel.")
|
||||
else:
|
||||
try:
|
||||
from fido2.pcsc import CtapPcscDevice
|
||||
|
||||
self.__dev = next(CtapPcscDevice.list_devices(), None)
|
||||
print("Use NFC channel.")
|
||||
except Exception as e:
|
||||
print("NFC channel search error:", e)
|
||||
|
||||
if not self.__dev:
|
||||
print("No FIDO device found")
|
||||
sys.exit(1)
|
||||
|
||||
# Set up a FIDO 2 client using the origin https://example.com
|
||||
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
||||
|
||||
# Prefer UV if supported and configured
|
||||
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
||||
self.__uv = "preferred"
|
||||
print("Authenticator supports User Verification")
|
||||
|
||||
self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
||||
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
|
||||
self.ctap1 = self.__client1._backend.ctap1
|
||||
|
||||
def __set_server(self, rp, attestation):
|
||||
self.__rp = rp
|
||||
self.__attestation = attestation
|
||||
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
||||
self.__server.allowed_algorithms = [
|
||||
PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
|
||||
for p in self.__client._backend.info.algorithms
|
||||
]
|
||||
|
||||
def client(self):
|
||||
return self.__client
|
||||
|
||||
def user(self, user=None):
|
||||
if (self.__user is None):
|
||||
self.__user = {"id": b"user_id", "name": "A. User"}
|
||||
if (user is not None):
|
||||
self.__user = user
|
||||
return self.__user
|
||||
|
||||
def rp(self, rp=None):
|
||||
if (self.__rp is None):
|
||||
self.__rp = {"id": "example.com", "name": "Example RP"}
|
||||
if (rp is not None):
|
||||
self.__rp = rp
|
||||
return self.__rp
|
||||
|
||||
def send_data(self, cmd, data, timeout = 1.0, on_keepalive = None):
|
||||
if not isinstance(data, bytes):
|
||||
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
|
||||
with Timeout(timeout) as event:
|
||||
event.is_set()
|
||||
return self.dev.call(cmd, data, event, on_keepalive = on_keepalive)
|
||||
|
||||
def cid(self):
|
||||
return self.dev._channel_id
|
||||
|
||||
def set_cid(self, cid):
|
||||
self.dev._channel_id = int.from_bytes(cid, 'big')
|
||||
|
||||
def recv_raw(self):
|
||||
with Timeout(1.0):
|
||||
r = self.dev._connection.read_packet()
|
||||
return r[4], r[7:]
|
||||
|
||||
def send_raw(self, data, cid=None):
|
||||
if cid is None:
|
||||
cid = self.dev._channel_id.to_bytes(4, 'big')
|
||||
elif not isinstance(cid, bytes):
|
||||
cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid])
|
||||
if not isinstance(data, bytes):
|
||||
data = struct.pack("%dB" % len(data), *[ord(x) for x in data])
|
||||
data = cid + data
|
||||
l = len(data)
|
||||
if l != 64:
|
||||
pad = "\x00" * (64 - l)
|
||||
pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad])
|
||||
data = data + pad
|
||||
data = bytes(data)
|
||||
assert len(data) == 64
|
||||
self.dev._connection.write_packet(data)
|
||||
|
||||
def reset(self):
|
||||
print("Resetting Authenticator...")
|
||||
try:
|
||||
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
|
||||
except CtapError:
|
||||
# Some authenticators need a power cycle
|
||||
print("Need to power cycle authentictor to reset..")
|
||||
self.reboot()
|
||||
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
|
||||
|
||||
def reboot(self):
|
||||
print("Please reboot authenticator and hit enter")
|
||||
try:
|
||||
inputimeout(prompt='>>', timeout=5)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.__set_client(self.__origin, self.__user_interaction, self.__uv)
|
||||
self.__set_server(rp=self.__rp, attestation=self.__attestation)
|
||||
|
||||
def MC(self, client_data_hash=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None, enterprise_attestation=None):
|
||||
client_data_hash = client_data_hash if client_data_hash is not Ellipsis else os.urandom(32)
|
||||
rp = rp if rp is not Ellipsis else self.__rp
|
||||
user = user if user is not Ellipsis else self.user()
|
||||
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
||||
att_obj = self.__client._backend.ctap2.make_credential(
|
||||
client_data_hash=client_data_hash,
|
||||
rp=rp,
|
||||
user=user,
|
||||
key_params=key_params,
|
||||
exclude_list=exclude_list,
|
||||
extensions=extensions,
|
||||
options=options,
|
||||
pin_uv_param=pin_uv_param,
|
||||
pin_uv_protocol=pin_uv_protocol,
|
||||
enterprise_attestation=enterprise_attestation
|
||||
)
|
||||
return {'res':att_obj,'req':{'client_data_hash':client_data_hash,
|
||||
'rp':rp,
|
||||
'user':user,
|
||||
'key_params':key_params}}
|
||||
|
||||
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
|
||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
||||
)
|
||||
rp = rp if rp is not Ellipsis else self.__rp
|
||||
user = user if user is not Ellipsis else self.user()
|
||||
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
||||
if (ctap1 is True):
|
||||
client = self.__client1
|
||||
else:
|
||||
client = self.__client
|
||||
result = client._backend.do_make_credential(
|
||||
client_data=client_data,
|
||||
rp=rp,
|
||||
user=user,
|
||||
key_params=key_params,
|
||||
exclude_list=exclude_list,
|
||||
extensions=extensions,
|
||||
rk=rk,
|
||||
user_verification=user_verification,
|
||||
enterprise_attestation=enterprise_attestation,
|
||||
event=event
|
||||
)
|
||||
return {'res':result,'req':{'client_data':client_data,
|
||||
'rp':rp,
|
||||
'user':user,
|
||||
'key_params':key_params}}
|
||||
|
||||
def try_make_credential(self, options=None):
|
||||
if (options is None):
|
||||
options, _ = self.__server.register_begin(
|
||||
self.user(), user_verification=self.__uv, authenticator_attachment="cross-platform"
|
||||
)
|
||||
try:
|
||||
result = self.__client.make_credential(options["publicKey"])
|
||||
except ClientError as e:
|
||||
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
|
||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||
client_pin.set_pin(DEFAULT_PIN)
|
||||
result = self.__client.make_credential(options["publicKey"])
|
||||
return result
|
||||
|
||||
def register(self, uv=None):
|
||||
# Prepare parameters for makeCredential
|
||||
create_options, state = self.__server.register_begin(
|
||||
self.user(), user_verification=uv or self.__uv, authenticator_attachment="cross-platform"
|
||||
)
|
||||
# Create a credential
|
||||
result = self.try_make_credential(create_options)
|
||||
|
||||
# Complete registration
|
||||
auth_data = self.__server.register_complete(
|
||||
state, result.client_data, result.attestation_object
|
||||
)
|
||||
credentials = [auth_data.credential_data]
|
||||
|
||||
print("New credential created!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print("ATTESTATION OBJECT:", result.attestation_object)
|
||||
print()
|
||||
print("CREDENTIAL DATA:", auth_data.credential_data)
|
||||
|
||||
return (result, auth_data)
|
||||
|
||||
def authenticate(self, credentials):
|
||||
# Prepare parameters for getAssertion
|
||||
request_options, state = self.__server.authenticate_begin(credentials, user_verification=self.__uv)
|
||||
|
||||
# Authenticate the credential
|
||||
result = self.__client.get_assertion(request_options["publicKey"])
|
||||
|
||||
# Only one cred in allowCredentials, only one response.
|
||||
result = result.get_response(0)
|
||||
|
||||
# Complete authenticator
|
||||
self.__server.authenticate_complete(
|
||||
state,
|
||||
credentials,
|
||||
result.credential_id,
|
||||
result.client_data,
|
||||
result.authenticator_data,
|
||||
result.signature,
|
||||
)
|
||||
|
||||
print("Credential authenticated!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print()
|
||||
print("AUTH DATA:", result.authenticator_data)
|
||||
|
||||
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
|
||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||
client_data_hash = client_data_hash if client_data_hash is not Ellipsis else os.urandom(32)
|
||||
att_obj = self.__client._backend.ctap2.get_assertion(
|
||||
rp_id=rp_id,
|
||||
client_data_hash=client_data_hash,
|
||||
allow_list=allow_list,
|
||||
extensions=extensions,
|
||||
options=options,
|
||||
pin_uv_param=pin_uv_param,
|
||||
pin_uv_protocol=pin_uv_protocol
|
||||
)
|
||||
return {'res':att_obj,'req':{'rp_id':rp_id,
|
||||
'client_data_hash':client_data_hash}}
|
||||
|
||||
def GNA(self):
|
||||
return self.__client._backend.ctap2.get_next_assertion()
|
||||
|
||||
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
|
||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
|
||||
)
|
||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||
if (ctap1 is True):
|
||||
client = self.__client1
|
||||
else:
|
||||
client = self.__client
|
||||
try:
|
||||
result = client._backend.do_get_assertion(
|
||||
client_data=client_data,
|
||||
rp_id=rp_id,
|
||||
allow_list=allow_list,
|
||||
extensions=extensions,
|
||||
user_verification=user_verification,
|
||||
event=event
|
||||
)
|
||||
except ClientError as e:
|
||||
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
|
||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||
client_pin.set_pin(DEFAULT_PIN)
|
||||
result = client._backend.do_get_assertion(
|
||||
client_data=client_data,
|
||||
rp_id=rp_id,
|
||||
allow_list=allow_list,
|
||||
extensions=extensions,
|
||||
user_verification=user_verification,
|
||||
event=event
|
||||
)
|
||||
else:
|
||||
raise
|
||||
return {'res':result,'req':{'client_data':client_data,
|
||||
'rp_id':rp_id}}
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def device():
|
||||
dev = Device()
|
||||
return dev
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def info(device):
|
||||
return device.client()._backend.info
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def MCRes(device, *args):
|
||||
return device.doMC(*args)
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def resetdevice(device):
|
||||
device.reset()
|
||||
return device
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def GARes(device, MCRes, *args):
|
||||
res = device.doGA(allow_list=[
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], *args)
|
||||
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCRes['res'].attestation_object, a, res['req']['client_data'].hash)
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def MCRes_DC(device, *args):
|
||||
return device.doMC(rk=True, *args)
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def GARes_DC(device, MCRes_DC, *args):
|
||||
res = device.GA(allow_list=[
|
||||
{"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], *args)
|
||||
verify(MCRes_DC['res'].attestation_object, res['res'], res['req']['client_data_hash'])
|
||||
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def RegRes(resetdevice, *args):
|
||||
res = resetdevice.doMC(ctap1=True, *args)
|
||||
att = FidoU2FAttestation()
|
||||
att.verify(res['res'].attestation_object.att_stmt, res['res'].attestation_object.auth_data, res['req']['client_data'].hash)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def AuthRes(device, RegRes, *args):
|
||||
res = device.doGA(ctap1=True, allow_list=[
|
||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], *args)
|
||||
aut_data = res['res'].get_response(0)
|
||||
m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
|
||||
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
|
||||
return aut_data
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def client_pin(resetdevice):
|
||||
return ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def ccid_card():
|
||||
cardtype = AnyCardType()
|
||||
try:
|
||||
# request card insertion
|
||||
cardrequest = CardRequest(timeout=10, cardType=cardtype)
|
||||
card = cardrequest.waitforcard()
|
||||
|
||||
# connect to the card and perform a few transmits
|
||||
card.connection.connect()
|
||||
return card
|
||||
|
||||
except CardRequestTimeoutException:
|
||||
print('time-out: no card inserted during last 10s')
|
||||
return None
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def select_oath(ccid_card):
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
||||
resp = send_apdu(ccid_card, 0xA4, 0x04, 0x00, aid)
|
||||
return ccid_card
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def reset_oath(select_oath):
|
||||
send_apdu(select_oath, 0x04, p1=0xde, p2=0xad)
|
||||
return select_oath
|
||||
36
tests/docker/bullseye/Dockerfile
Normal file
36
tests/docker/bullseye/Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
||||
FROM debian:bullseye
|
||||
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
|
||||
RUN git clone https://github.com/polhenarejos/python-fido2.git
|
||||
WORKDIR /python-fido2
|
||||
RUN git checkout development
|
||||
RUN pip3 install .
|
||||
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 /
|
||||
270
tests/docker/fido2/__init__.py
Normal file
270
tests/docker/fido2/__init__.py
Normal file
@@ -0,0 +1,270 @@
|
||||
# Copyright (c) 2020 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that the following
|
||||
# conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import HidDescriptor
|
||||
from ..ctap import CtapDevice, CtapError, STATUS
|
||||
from ..utils import LOG_LEVEL_TRAFFIC
|
||||
from threading import Event
|
||||
from enum import IntEnum, IntFlag, unique
|
||||
from typing import Tuple, Optional, Callable, Iterator
|
||||
import struct
|
||||
import sys
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
from . import linux as backend
|
||||
elif sys.platform.startswith("win32"):
|
||||
from . import windows as backend
|
||||
elif sys.platform.startswith("darwin"):
|
||||
from . import macos as backend
|
||||
elif sys.platform.startswith("freebsd"):
|
||||
from . import freebsd as backend
|
||||
elif sys.platform.startswith("netbsd"):
|
||||
from . import netbsd as backend
|
||||
elif sys.platform.startswith("openbsd"):
|
||||
from . import openbsd as backend
|
||||
else:
|
||||
raise Exception("Unsupported platform")
|
||||
from . import emulation as backend
|
||||
|
||||
list_descriptors = backend.list_descriptors
|
||||
get_descriptor = backend.get_descriptor
|
||||
open_connection = backend.open_connection
|
||||
|
||||
|
||||
@unique
|
||||
class CTAPHID(IntEnum):
|
||||
PING = 0x01
|
||||
MSG = 0x03
|
||||
LOCK = 0x04
|
||||
INIT = 0x06
|
||||
WINK = 0x08
|
||||
CBOR = 0x10
|
||||
CANCEL = 0x11
|
||||
|
||||
ERROR = 0x3F
|
||||
KEEPALIVE = 0x3B
|
||||
|
||||
VENDOR_FIRST = 0x40
|
||||
|
||||
|
||||
@unique
|
||||
class CAPABILITY(IntFlag):
|
||||
WINK = 0x01
|
||||
LOCK = 0x02 # Not used
|
||||
CBOR = 0x04
|
||||
NMSG = 0x08
|
||||
|
||||
def supported(self, flags: CAPABILITY) -> bool:
|
||||
return bool(flags & self)
|
||||
|
||||
|
||||
TYPE_INIT = 0x80
|
||||
|
||||
|
||||
class CtapHidDevice(CtapDevice):
|
||||
"""
|
||||
CtapDevice implementation using the HID transport.
|
||||
|
||||
:cvar descriptor: Device descriptor.
|
||||
"""
|
||||
|
||||
def __init__(self, descriptor: HidDescriptor, connection):
|
||||
self.descriptor = descriptor
|
||||
self._packet_size = descriptor.report_size_out
|
||||
self._connection = connection
|
||||
|
||||
nonce = os.urandom(8)
|
||||
self._channel_id = 0xFFFFFFFF
|
||||
response = self.call(CTAPHID.INIT, nonce)
|
||||
r_nonce, response = response[:8], response[8:]
|
||||
if r_nonce != nonce:
|
||||
raise Exception("Wrong nonce")
|
||||
(
|
||||
self._channel_id,
|
||||
self._u2fhid_version,
|
||||
v1,
|
||||
v2,
|
||||
v3,
|
||||
self._capabilities,
|
||||
) = struct.unpack_from(">IBBBBB", response)
|
||||
self._device_version = (v1, v2, v3)
|
||||
|
||||
def __repr__(self):
|
||||
return f"CtapHidDevice({self.descriptor.path!r})"
|
||||
|
||||
@property
|
||||
def version(self) -> int:
|
||||
"""CTAP HID protocol version."""
|
||||
return self._u2fhid_version
|
||||
|
||||
@property
|
||||
def device_version(self) -> Tuple[int, int, int]:
|
||||
"""Device version number."""
|
||||
return self._device_version
|
||||
|
||||
@property
|
||||
def capabilities(self) -> int:
|
||||
"""Capabilities supported by the device."""
|
||||
return self._capabilities
|
||||
|
||||
@property
|
||||
def product_name(self) -> Optional[str]:
|
||||
"""Product name of device."""
|
||||
return self.descriptor.product_name
|
||||
|
||||
@property
|
||||
def serial_number(self) -> Optional[str]:
|
||||
"""Serial number of device."""
|
||||
return self.descriptor.serial_number
|
||||
|
||||
def _send_cancel(self):
|
||||
packet = struct.pack(">IB", self._channel_id, TYPE_INIT | CTAPHID.CANCEL).ljust(
|
||||
self._packet_size, b"\0"
|
||||
)
|
||||
logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex())
|
||||
self._connection.write_packet(packet)
|
||||
|
||||
def call(
|
||||
self,
|
||||
cmd: int,
|
||||
data: bytes = b"",
|
||||
event: Optional[Event] = None,
|
||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
||||
) -> bytes:
|
||||
event = event or Event()
|
||||
remaining = data
|
||||
seq = 0
|
||||
|
||||
# Send request
|
||||
header = struct.pack(">IBH", self._channel_id, TYPE_INIT | cmd, len(remaining))
|
||||
while remaining or seq == 0:
|
||||
size = min(len(remaining), self._packet_size - len(header))
|
||||
body, remaining = remaining[:size], remaining[size:]
|
||||
packet = header + body
|
||||
logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex())
|
||||
self._connection.write_packet(packet.ljust(self._packet_size, b"\0"))
|
||||
header = struct.pack(">IB", self._channel_id, 0x7F & seq)
|
||||
seq += 1
|
||||
|
||||
try:
|
||||
# Read response
|
||||
seq = 0
|
||||
response = b""
|
||||
last_ka = None
|
||||
while True:
|
||||
if event.is_set():
|
||||
# Cancel
|
||||
logger.debug("Sending cancel...")
|
||||
self._send_cancel()
|
||||
|
||||
recv = self._connection.read_packet()
|
||||
logger.log(LOG_LEVEL_TRAFFIC, "RECV: %s", recv.hex())
|
||||
|
||||
r_channel = struct.unpack_from(">I", recv)[0]
|
||||
recv = recv[4:]
|
||||
if r_channel != self._channel_id:
|
||||
raise Exception("Wrong channel")
|
||||
|
||||
if not response: # Initialization packet
|
||||
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
||||
recv = recv[3:]
|
||||
if r_cmd == TYPE_INIT | cmd:
|
||||
pass # first data packet
|
||||
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
||||
ka_status = struct.unpack_from(">B", recv)[0]
|
||||
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
||||
if on_keepalive and ka_status != last_ka:
|
||||
try:
|
||||
ka_status = STATUS(ka_status)
|
||||
except ValueError:
|
||||
pass # Unknown status value
|
||||
last_ka = ka_status
|
||||
on_keepalive(ka_status)
|
||||
continue
|
||||
elif r_cmd == TYPE_INIT | CTAPHID.ERROR:
|
||||
raise CtapError(struct.unpack_from(">B", recv)[0])
|
||||
else:
|
||||
raise CtapError(CtapError.ERR.INVALID_COMMAND)
|
||||
else: # Continuation packet
|
||||
r_seq = struct.unpack_from(">B", recv)[0]
|
||||
recv = recv[1:]
|
||||
if r_seq != seq:
|
||||
raise Exception("Wrong sequence number")
|
||||
seq += 1
|
||||
|
||||
response += recv
|
||||
if len(response) >= r_len:
|
||||
break
|
||||
|
||||
return response[:r_len]
|
||||
except KeyboardInterrupt:
|
||||
logger.debug("Keyboard interrupt, cancelling...")
|
||||
self._send_cancel()
|
||||
|
||||
raise
|
||||
|
||||
def wink(self) -> None:
|
||||
"""Causes the authenticator to blink."""
|
||||
self.call(CTAPHID.WINK)
|
||||
|
||||
def ping(self, msg: bytes = b"Hello FIDO") -> bytes:
|
||||
"""Sends data to the authenticator, which echoes it back.
|
||||
|
||||
:param msg: The data to send.
|
||||
:return: The response from the authenticator.
|
||||
"""
|
||||
return self.call(CTAPHID.PING, msg)
|
||||
|
||||
def lock(self, lock_time: int = 10) -> None:
|
||||
"""Locks the channel."""
|
||||
self.call(CTAPHID.LOCK, struct.pack(">B", lock_time))
|
||||
|
||||
def close(self) -> None:
|
||||
if self._connection:
|
||||
self._connection.close()
|
||||
self._connection = None
|
||||
|
||||
@classmethod
|
||||
def list_devices(cls) -> Iterator[CtapHidDevice]:
|
||||
for d in list_descriptors():
|
||||
yield cls(d, open_connection(d))
|
||||
|
||||
|
||||
def list_devices() -> Iterator[CtapHidDevice]:
|
||||
return CtapHidDevice.list_devices()
|
||||
|
||||
|
||||
def open_device(path) -> CtapHidDevice:
|
||||
descriptor = get_descriptor(path)
|
||||
return CtapHidDevice(descriptor, open_connection(descriptor))
|
||||
79
tests/docker/fido2/emulation.py
Normal file
79
tests/docker/fido2/emulation.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Original work Copyright 2016 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# Modified work Copyright 2020 Yubico AB. All Rights Reserved.
|
||||
# This file, with modifications, is licensed under the above Apache License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .base import HidDescriptor, CtapHidConnection
|
||||
|
||||
import socket
|
||||
from typing import Set
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
HOST = '127.0.0.1'
|
||||
PORT = 35962
|
||||
|
||||
# Don't typecheck this file on Windows
|
||||
assert sys.platform != "win32" # nosec
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EmulationCtapHidConnection(CtapHidConnection):
|
||||
def __init__(self, descriptor):
|
||||
self.descriptor = descriptor
|
||||
self.handle = descriptor.path
|
||||
self.handle.connect((HOST, PORT))
|
||||
|
||||
def write_packet(self, packet):
|
||||
if (self.handle.send(len(packet).to_bytes(2, 'big')) != 2):
|
||||
raise OSError("write_packet sending size failed")
|
||||
if (self.handle.send(packet) != len(packet)):
|
||||
raise OSError("write_packet sending packet failed")
|
||||
|
||||
def read_packet(self):
|
||||
bts = self.handle.recv(2)
|
||||
if (len(bts) != 2):
|
||||
raise OSError("read_packet failed reading size")
|
||||
size = int.from_bytes(bts, 'big')
|
||||
data = self.handle.recv(size)
|
||||
if (len(data) != size):
|
||||
raise OSError("read_packet failed reading packet")
|
||||
return data
|
||||
|
||||
def close(self) -> None:
|
||||
return self.handle.close()
|
||||
|
||||
|
||||
def open_connection(descriptor):
|
||||
return EmulationCtapHidConnection(descriptor)
|
||||
|
||||
|
||||
def get_descriptor(_):
|
||||
HOST = 'localhost' # The remote host
|
||||
PORT = 35962 # The same port as used by the server
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
return HidDescriptor(s, 0x00, 0x00, 64, 64, "Pico-Fido", "AAAAAA")
|
||||
|
||||
def list_descriptors():
|
||||
devices = []
|
||||
try:
|
||||
devices.append(get_descriptor(None))
|
||||
except ValueError:
|
||||
pass # Not a CTAP device, ignore.
|
||||
|
||||
return devices
|
||||
107
tests/docker_env.sh
Normal file
107
tests/docker_env.sh
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
# Taken from Mbed-TLS project
|
||||
# https://github.com/Mbed-TLS/mbedtls/blob/master/tests/scripts/docker_env.sh
|
||||
#
|
||||
# docker_env.sh
|
||||
#
|
||||
# Purpose
|
||||
# -------
|
||||
#
|
||||
# This is a helper script to enable running tests under a Docker container,
|
||||
# thus making it easier to get set up as well as isolating test dependencies
|
||||
# (which include legacy/insecure configurations of openssl and gnutls).
|
||||
#
|
||||
# WARNING: the Dockerfile used by this script is no longer maintained! See
|
||||
# https://github.com/Mbed-TLS/mbedtls-test/blob/master/README.md#quick-start
|
||||
# for the set of Docker images we use on the CI.
|
||||
#
|
||||
# Notes for users
|
||||
# ---------------
|
||||
# This script expects a Linux x86_64 system with a recent version of Docker
|
||||
# installed and available for use, as well as http/https access. If a proxy
|
||||
# server must be used, invoke this script with the usual environment variables
|
||||
# (http_proxy and https_proxy) set appropriately. If an alternate Docker
|
||||
# registry is needed, specify MBEDTLS_DOCKER_REGISTRY to point at the
|
||||
# host name.
|
||||
#
|
||||
#
|
||||
# Running this script directly will check for Docker availability and set up
|
||||
# the Docker image.
|
||||
|
||||
# Copyright The Mbed TLS Contributors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# default values, can be overridden by the environment
|
||||
: ${MBEDTLS_DOCKER_GUEST:=bullseye}
|
||||
|
||||
|
||||
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"
|
||||
|
||||
# Make sure docker is available
|
||||
if ! which docker > /dev/null; then
|
||||
echo "Docker is required but doesn't seem to be installed. See https://www.docker.com/ to get started"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Figure out if we need to 'sudo docker'
|
||||
if groups | grep docker > /dev/null; then
|
||||
DOCKER="docker"
|
||||
else
|
||||
echo "Using sudo to invoke docker since you're not a member of the docker group..."
|
||||
DOCKER="docker"
|
||||
fi
|
||||
|
||||
# Figure out the number of processors available
|
||||
if [ "$(uname)" == "Darwin" ]; then
|
||||
NUM_PROC="$(sysctl -n hw.logicalcpu)"
|
||||
else
|
||||
NUM_PROC="$(nproc)"
|
||||
fi
|
||||
|
||||
# Build the Docker image
|
||||
echo "Getting docker image up to date (this may take a few minutes)..."
|
||||
${DOCKER} image build \
|
||||
-t ${DOCKER_IMAGE_TAG} \
|
||||
--cache-from=${DOCKER_IMAGE_TAG} \
|
||||
--network host \
|
||||
--build-arg MAKEFLAGS_PARALLEL="-j ${NUM_PROC}" \
|
||||
tests/docker/${MBEDTLS_DOCKER_GUEST}
|
||||
|
||||
run_in_docker()
|
||||
{
|
||||
ENV_ARGS=""
|
||||
while [ "$1" == "-e" ]; do
|
||||
ENV_ARGS="${ENV_ARGS} $1 $2"
|
||||
shift 2
|
||||
done
|
||||
|
||||
WORKDIR="${PWD}"
|
||||
if [ "$1" == '-w' ]; then
|
||||
WORKDIR="$2"
|
||||
shift 2
|
||||
fi
|
||||
|
||||
${DOCKER} container run --rm \
|
||||
--cap-add ALL \
|
||||
--privileged \
|
||||
--volume $PWD:$PWD \
|
||||
--workdir ${WORKDIR} \
|
||||
-e MAKEFLAGS \
|
||||
${ENV_ARGS} \
|
||||
${DOCKER_IMAGE_TAG} \
|
||||
$@
|
||||
}
|
||||
47
tests/pico-fido/test_000_getinfo.py
Normal file
47
tests/pico-fido/test_000_getinfo.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from fido2.client import CtapError
|
||||
|
||||
|
||||
def test_getinfo(device):
|
||||
pass
|
||||
|
||||
|
||||
def test_get_info_version(info):
|
||||
assert "FIDO_2_0" in info.versions
|
||||
|
||||
|
||||
def test_Check_pin_protocols_field(info):
|
||||
if len(info.pin_uv_protocols):
|
||||
assert sum(info.pin_uv_protocols) > 0
|
||||
|
||||
|
||||
def test_Check_options_field(info):
|
||||
for x in info.options:
|
||||
assert info.options[x] in [True, False]
|
||||
|
||||
|
||||
def test_Check_up_option(device, info):
|
||||
if "up" not in info.options or info.options["up"]:
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(options={"up": True})
|
||||
assert e.value.code == CtapError.ERR.INVALID_OPTION
|
||||
261
tests/pico-fido/test_010_pin.py
Normal file
261
tests/pico-fido/test_010_pin.py
Normal file
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
/*
|
||||
* 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 os
|
||||
import pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.client import ClientPin
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.utils import hmac_sha256
|
||||
|
||||
from utils import *
|
||||
|
||||
def test_lockout(device, resetdevice, client_pin):
|
||||
pin = "TestPin"
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin)
|
||||
|
||||
for i in range(1, 10):
|
||||
err = [CtapError.ERR.PIN_INVALID]
|
||||
if 3 <= i <= 7:
|
||||
err = [CtapError.ERR.PIN_AUTH_BLOCKED]
|
||||
elif i >= 8:
|
||||
err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID]
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token("WrongPin")
|
||||
assert e.value.code == err or e.value.code in err
|
||||
|
||||
attempts = 8 - i
|
||||
if i > 8:
|
||||
attempts = 0
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == attempts
|
||||
|
||||
if err == CtapError.ERR.PIN_AUTH_BLOCKED:
|
||||
device.reboot()
|
||||
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC()
|
||||
|
||||
device.reboot()
|
||||
client_pin = ClientPin(resetdevice.client()._backend.ctap2)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token(pin)
|
||||
assert e.value.code == CtapError.ERR.PIN_BLOCKED
|
||||
|
||||
def test_send_zero_length_pin_auth(device):
|
||||
device.reset()
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA(pin_uv_param=b"")
|
||||
assert e.value.code in (CtapError.ERR.PIN_NOT_SET, CtapError.ERR.NO_CREDENTIALS)
|
||||
|
||||
def test_set_pin(device, client_pin):
|
||||
device.reset()
|
||||
client_pin.set_pin("TestPin")
|
||||
device.reset()
|
||||
|
||||
def test_set_pin_too_big(client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin("A" * 64)
|
||||
assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION
|
||||
|
||||
def test_get_pin_token_but_no_pin_set(resetdevice, client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token("TestPin")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
def test_change_pin_but_no_pin_set(device, client_pin):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.change_pin("TestPin", "1234")
|
||||
assert e.value.code == CtapError.ERR.PIN_NOT_SET
|
||||
|
||||
def test_setting_pin_and_get_info(device, client_pin, info):
|
||||
device.reset()
|
||||
client_pin.set_pin("TestPin")
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin("TestPin")
|
||||
|
||||
assert info.options["clientPin"]
|
||||
|
||||
pin_token = client_pin.get_pin_token("TestPin")
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == 8
|
||||
|
||||
device.reset()
|
||||
|
||||
PIN1 = "12345678"
|
||||
PIN2 = "ABCDEF"
|
||||
|
||||
@pytest.fixture(scope="class", params=[PIN1])
|
||||
def SetPinRes(request, device, client_pin):
|
||||
device.reset()
|
||||
|
||||
pin = request.param
|
||||
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def CPRes(request, device, client_pin):
|
||||
res = client_pin.ctap.client_pin(2, ClientPin.CMD.GET_KEY_AGREEMENT)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCPinRes(device):
|
||||
res = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)
|
||||
return res
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def GAPinRes(device, MCPinRes):
|
||||
res = device.doGA()
|
||||
return res
|
||||
|
||||
def test_pin(CPRes):
|
||||
pass
|
||||
|
||||
def test_set_pin_twice(device, client_pin):
|
||||
""" Setting pin when a pin is already set should result in error NotAllowed. """
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.set_pin('1234')
|
||||
client_pin.set_pin('1234')
|
||||
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
|
||||
def test_get_key_agreement_fields(CPRes):
|
||||
key = CPRes[1]
|
||||
assert "Is public key" and key[1] == 2
|
||||
assert "Is P256" and key[-1] == 1
|
||||
assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25
|
||||
|
||||
assert "Right key" and len(key[-3]) == 32 and isinstance(key[-3], bytes)
|
||||
|
||||
def test_verify_flag(device, SetPinRes):
|
||||
reg = device.doMC(user_verification=UserVerificationRequirement.REQUIRED)['res'].attestation_object
|
||||
assert reg.auth_data.flags & (1 << 2)
|
||||
|
||||
def test_get_no_pin_auth(device):
|
||||
|
||||
reg = device.doMC()['res'].attestation_object
|
||||
allow_list = [
|
||||
{"type": "public-key", "id": reg.auth_data.credential_data.credential_id}
|
||||
]
|
||||
auth = device.GA(allow_list=allow_list)['res']
|
||||
assert not (auth.auth_data.flags & (1 << 2))
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC()
|
||||
|
||||
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
|
||||
|
||||
def test_zero_length_pin_auth(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA(pin_uv_param=b"")
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
def test_make_credential_no_pin(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.MC()
|
||||
assert e.value.code == CtapError.ERR.PUAT_REQUIRED
|
||||
|
||||
def test_get_assertion_no_pin(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
reg = device.GA()
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_change_pin(device, client_pin):
|
||||
device.reset()
|
||||
client_pin.set_pin(PIN1)
|
||||
client_pin.change_pin(PIN1, PIN2)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=PIN2)
|
||||
cdh = os.urandom(32)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
|
||||
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=PIN2)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
auth = device.GA(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh, allow_list=[{
|
||||
"type": "public-key",
|
||||
"id": reg.auth_data.credential_data.credential_id,
|
||||
}])['res']
|
||||
|
||||
assert reg.auth_data.flags & (1 << 2)
|
||||
assert auth.auth_data.flags & (1 << 2)
|
||||
|
||||
verify(reg, auth, client_data_hash=cdh)
|
||||
|
||||
def test_pin_attempts(device, client_pin):
|
||||
# Flip 1 bit
|
||||
pin = PIN1
|
||||
device.reset()
|
||||
client_pin.set_pin(pin)
|
||||
pin_wrong = list(pin)
|
||||
c = pin[len(pin) // 2]
|
||||
|
||||
pin_wrong[len(pin) // 2] = chr(ord(c) ^ 1)
|
||||
pin_wrong = "".join(pin_wrong)
|
||||
|
||||
for i in range(1, 3):
|
||||
with pytest.raises(CtapError) as e:
|
||||
pin_token = client_pin.get_pin_token(pin=pin_wrong)
|
||||
assert e.value.code == CtapError.ERR.PIN_INVALID
|
||||
|
||||
print("Check there is %d pin attempts left" % (8 - i))
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == (8 - i)
|
||||
|
||||
for i in range(1, 3):
|
||||
with pytest.raises(CtapError) as e:
|
||||
client_pin.get_pin_token(pin_wrong)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
|
||||
|
||||
device.reboot()
|
||||
client_pin = ClientPin(device.client()._backend.ctap2)
|
||||
|
||||
pin_token = client_pin.get_pin_token(pin=pin)
|
||||
cdh = os.urandom(32)
|
||||
pin_auth = hmac_sha256(pin_token, cdh)[:32]
|
||||
|
||||
reg = device.MC(pin_uv_param=pin_auth, pin_uv_protocol=2, client_data_hash=cdh)['res']
|
||||
|
||||
res = client_pin.get_pin_retries()
|
||||
assert res[0] == (8)
|
||||
197
tests/pico-fido/test_020_register.py
Normal file
197
tests/pico-fido/test_020_register.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
from fido2.client import CtapError
|
||||
from fido2.cose import ES256, ES384, ES512
|
||||
import fido2.features
|
||||
fido2.features.webauthn_json_mapping.enabled = False
|
||||
from utils import ES256K
|
||||
import pytest
|
||||
|
||||
|
||||
def test_register(device):
|
||||
device.reset()
|
||||
REGRes,AUTData = device.register()
|
||||
|
||||
def test_make_credential():
|
||||
pass
|
||||
|
||||
def test_attestation_format(MCRes):
|
||||
assert MCRes['res'].attestation_object.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
|
||||
|
||||
def test_authdata_length(MCRes):
|
||||
assert len(MCRes['res'].attestation_object.auth_data) >= 77
|
||||
|
||||
def test_missing_cdh(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(client_data_hash=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_cdh(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(client_data_hash=b'\xff')
|
||||
|
||||
def test_missing_user(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_user_user(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user=b"12345678")
|
||||
|
||||
def test_missing_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(rp=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(rp=b"12345678")
|
||||
|
||||
def test_missing_pubKeyCredParams(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_pubKeyCredParams(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(key_params=b"12345678")
|
||||
|
||||
def test_bad_type_excludeList(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(exclude_list=8)
|
||||
|
||||
def test_bad_type_extensions(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(extensions=8)
|
||||
|
||||
def test_bad_type_options(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(options=8)
|
||||
|
||||
def test_bad_type_rp_name(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
|
||||
|
||||
def test_bad_type_rp_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
|
||||
|
||||
def test_bad_type_rp_icon(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
|
||||
|
||||
def test_bad_type_user_name(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": b"user_id", "name": 8})
|
||||
|
||||
def test_bad_type_user_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name"})
|
||||
|
||||
def test_bad_type_user_displayName(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
|
||||
|
||||
def test_bad_type_user_icon(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
|
||||
|
||||
def test_bad_type_pubKeyCredParams(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=["wrong"])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM]
|
||||
)
|
||||
def test_algorithms(device, info, alg):
|
||||
if ({'alg': alg, 'type': 'public-key'} in info.algorithms):
|
||||
device.doMC(key_params=[{"alg": alg, "type": "public-key"}])
|
||||
|
||||
def test_missing_pubKeyCredParams_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
|
||||
|
||||
assert e.value.code == CtapError.ERR.INVALID_CBOR
|
||||
|
||||
def test_missing_pubKeyCredParams_alg(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"type": "public-key"}])
|
||||
|
||||
assert e.value.code in [
|
||||
CtapError.ERR.INVALID_CBOR,
|
||||
CtapError.ERR.UNSUPPORTED_ALGORITHM,
|
||||
]
|
||||
|
||||
def test_bad_type_pubKeyCredParams_alg(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
|
||||
|
||||
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
|
||||
|
||||
def test_unsupported_algorithm(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(key_params=[{"alg": 1337, "type": "public-key"}])
|
||||
|
||||
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
||||
|
||||
def test_exclude_list(resetdevice):
|
||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
|
||||
|
||||
def test_exclude_list2(resetdevice):
|
||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
|
||||
|
||||
def test_bad_type_exclude_list(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=["1234"])
|
||||
|
||||
def test_missing_exclude_list_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"id": b"1234"}])
|
||||
|
||||
def test_missing_exclude_list_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"type": "public-key"}])
|
||||
|
||||
def test_bad_type_exclude_list_id(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
|
||||
|
||||
def test_bad_type_exclude_list_type(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[{"type": b"public-key", "id": b"1234"}])
|
||||
|
||||
def test_exclude_list_excluded(device):
|
||||
res = device.doMC()['res'].attestation_object
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(exclude_list=[
|
||||
{"id": res.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
def test_unknown_option(device):
|
||||
device.reset()
|
||||
device.MC(options={"unknown": False})
|
||||
225
tests/pico-fido/test_021_authenticate.py
Normal file
225
tests/pico-fido/test_021_authenticate.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
from fido2.client import CtapError
|
||||
from fido2.cose import ES256, ES384, ES512
|
||||
from utils import verify, ES256K
|
||||
import pytest
|
||||
|
||||
def test_authenticate(device):
|
||||
device.reset()
|
||||
REGRes,AUTData = device.register()
|
||||
|
||||
credentials = [AUTData.credential_data]
|
||||
AUTRes = device.authenticate(credentials)
|
||||
|
||||
def test_assertion_auth_data(GARes):
|
||||
assert len(GARes['res'].get_response(0).authenticator_data) == 37
|
||||
|
||||
def test_Check_that_AT_flag_is_not_set(GARes):
|
||||
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
|
||||
|
||||
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
|
||||
res = device.GA(allow_list=[
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
assert res['res'].user == None
|
||||
assert res['res'].number_of_credentials == None
|
||||
|
||||
def test_empty_allowList(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[])
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM]
|
||||
)
|
||||
def test_algorithms(device, info, alg):
|
||||
if ({'alg': alg, 'type': 'public-key'} in info.algorithms):
|
||||
MCRes = device.doMC(key_params=[{"alg": alg, "type": "public-key"}])
|
||||
res = device.GA(allow_list=[
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
verify(MCRes['res'].attestation_object, res['res'], res['req']['client_data_hash'])
|
||||
|
||||
def test_get_assertion_allow_list_filtering_and_buffering(device):
|
||||
""" Check that authenticator filters and stores items in allow list correctly """
|
||||
allow_list = []
|
||||
|
||||
rp1 = {"id": "rp1.com", "name": "rp1.com"}
|
||||
rp2 = {"id": "rp2.com", "name": "rp2.com"}
|
||||
|
||||
rp1_registrations = []
|
||||
rp2_registrations = []
|
||||
rp1_assertions = []
|
||||
rp2_assertions = []
|
||||
|
||||
l1 = 4
|
||||
for i in range(0, l1):
|
||||
res = device.doMC(rp=rp1)['res'].attestation_object
|
||||
rp1_registrations.append(res)
|
||||
allow_list.append({
|
||||
"id": res.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
})
|
||||
|
||||
l2 = 6
|
||||
for i in range(0, l2):
|
||||
res = device.doMC(rp=rp2)['res'].attestation_object
|
||||
rp2_registrations.append(res)
|
||||
allow_list.append({
|
||||
"id": res.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
})
|
||||
|
||||
# CTAP 2.1: If allowlist is passed, only one (any) applicable
|
||||
# credential signs, and numberOfCredentials = None is returned.
|
||||
# <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#:~:text=If%20the%20allowList%20parameter%20is%20present%3A,Go%20to%20Step%2013>
|
||||
#
|
||||
# CTAP 2.0: Expects the authenticator to return the total number
|
||||
# even when allowlist is passed (and hence keep the credential IDs
|
||||
# cached.
|
||||
|
||||
# Should authenticate to all credentials matching rp1
|
||||
rp1_assertions = device.doGA(rp_id=rp1['id'], allow_list=allow_list)['res'].get_assertions()
|
||||
|
||||
# Should authenticate to all credentials matching rp2
|
||||
rp2_assertions = device.doGA(rp_id=rp2['id'], allow_list=allow_list)['res'].get_assertions()
|
||||
|
||||
counts = (
|
||||
len(rp1_assertions),
|
||||
len(rp2_assertions)
|
||||
)
|
||||
|
||||
assert counts in [(1, 1), (l1, l2)]
|
||||
|
||||
def test_corrupt_credId(device, MCRes):
|
||||
# apply bit flip
|
||||
badid = list(MCRes['res'].attestation_object.auth_data.credential_data.credential_id[:])
|
||||
badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1
|
||||
badid = bytes(badid)
|
||||
|
||||
allow_list = [{"id": badid, "type": "public-key"}]
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=allow_list)['res']
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_mismatched_rp(device, GARes):
|
||||
rp_id = device.rp()['id']
|
||||
rp_id += ".com"
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(rp_id=rp_id)
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_missing_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(rp_id=None)
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_rp(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(rp_id={"id": {"type": "wrong"}})
|
||||
|
||||
def test_missing_cdh(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(client_data_hash=None)
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_cdh(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(client_data_hash={"type": "wrong"})
|
||||
|
||||
def test_bad_allow_list(device):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list={"type": "wrong"})
|
||||
|
||||
def test_bad_allow_list_item(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=["wrong"] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
def test_unknown_option(device, MCRes):
|
||||
device.GA(options={"unknown": True}, allow_list=[
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
|
||||
def test_option_uv(device, info, GARes):
|
||||
if "uv" in info.options:
|
||||
if info.options["uv"]:
|
||||
res = device.doGA(options={"uv": True})['res']
|
||||
assert res.auth_data.flags & (1 << 2)
|
||||
|
||||
def test_option_up(device, info, GARes):
|
||||
if "up" in info.options:
|
||||
if info.options["up"]:
|
||||
res = device.doGA(options={"up": True})['res']
|
||||
assert res.auth_data.flags & (1 << 0)
|
||||
|
||||
def test_allow_list_fake_item(device, MCRes):
|
||||
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
|
||||
+ [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],
|
||||
)
|
||||
|
||||
def test_allow_list_missing_field(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"id": b"1234"}] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
def test_allow_list_field_wrong_type(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"type": b"public-key", "id": b"1234"}]
|
||||
+ [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
def test_allow_list_id_wrong_type(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
|
||||
+ [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
def test_allow_list_missing_id(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doGA(allow_list=[{"type": "public-key"}] + [
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
]
|
||||
)
|
||||
|
||||
def test_user_presence_option_false(device, MCRes):
|
||||
res = device.GA(options={"up": False}, allow_list=[
|
||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
|
||||
def test_credential_resets(device, MCRes, GARes):
|
||||
device.reset()
|
||||
with pytest.raises(CtapError) as e:
|
||||
new_auth = device.doGA()
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
259
tests/pico-fido/test_022_discoverable.py
Normal file
259
tests/pico-fido/test_022_discoverable.py
Normal file
@@ -0,0 +1,259 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
from fido2.client import CtapError
|
||||
import pytest
|
||||
import random
|
||||
from utils import *
|
||||
|
||||
@pytest.mark.parametrize("do_reboot", [False, True])
|
||||
def test_user_info_returned_when_using_allowlist(device, MCRes_DC, GARes_DC, do_reboot):
|
||||
assert "id" in GARes_DC['res'].user.keys()
|
||||
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
if do_reboot:
|
||||
device.reboot()
|
||||
|
||||
ga_res = device.GA(allow_list=allow_list)['res']
|
||||
|
||||
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
|
||||
|
||||
def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
|
||||
assert "id" in GARes_DC['res'].user.keys()
|
||||
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
ga_res = device.GA(allow_list=allow_list)['res']
|
||||
|
||||
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
|
||||
|
||||
device.reset()
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
ga_res = device.doGA(allow_list=allow_list)
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
|
||||
|
||||
def test_resident_key(MCRes_DC, info):
|
||||
pass
|
||||
|
||||
def test_resident_key_auth(MCRes_DC, GARes_DC):
|
||||
pass
|
||||
|
||||
def test_user_info_returned(device, MCRes_DC, GARes_DC):
|
||||
assert "id" in GARes_DC['res'].user.keys()
|
||||
assert (
|
||||
MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
== GARes_DC['res'].credential["id"]
|
||||
)
|
||||
assert MCRes_DC["req"]["user"]["id"] == GARes_DC['res'].user["id"]
|
||||
if not GARes_DC['res'].number_of_credentials:
|
||||
assert "id" in GARes_DC['res'].user.keys() and len(GARes_DC['res'].user.keys()) == 1
|
||||
else:
|
||||
assert MCRes_DC["req"]["user"] == GARes_DC['res'].user
|
||||
|
||||
|
||||
def test_multiple_rk_nodisplay(device, MCRes_DC):
|
||||
auths = []
|
||||
regs = []
|
||||
# Use unique RP to not collide with other credentials
|
||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
||||
for i in range(0, 3):
|
||||
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||
regs.append(res)
|
||||
# time.sleep(2)
|
||||
|
||||
res = device.doGA(rp_id=rp['id'])['res']
|
||||
auths = res.get_assertions()
|
||||
|
||||
assert len(regs) == 3
|
||||
assert len(regs) == len(auths)
|
||||
|
||||
for x in auths:
|
||||
for y in ("name", "displayName", "id"):
|
||||
if y not in x.user.keys():
|
||||
print("FAIL: %s was not in user: " % y, x.user)
|
||||
|
||||
|
||||
def test_rk_maximum_size_nodisplay(device):
|
||||
"""
|
||||
Check the lengths of the fields according to the FIDO2 spec
|
||||
https://github.com/solokeys/solo/issues/158#issue-426613303
|
||||
https://www.w3.org/TR/webauthn/#dom-publickeycredentialuserentity-displayname
|
||||
"""
|
||||
device.reset()
|
||||
user_max = generate_user_maximum()
|
||||
resMC = device.doMC(user=user_max, rk=True)
|
||||
resGA = device.doGA()['res']
|
||||
auths = resGA.get_assertions()
|
||||
|
||||
user_max_GA = auths[0]
|
||||
print(auths)
|
||||
for y in ("name", "displayName", "id"):
|
||||
if (y in user_max_GA):
|
||||
assert user_max_GA.user[y] == user_max[y]
|
||||
|
||||
|
||||
def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
||||
"""
|
||||
Test maximum returned capacity of the RK for the given RP
|
||||
"""
|
||||
|
||||
# Try to determine from get_info, or default to 19.
|
||||
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
||||
if not RK_CAPACITY_PER_RP:
|
||||
RK_CAPACITY_PER_RP = 19
|
||||
|
||||
users = []
|
||||
|
||||
def get_user():
|
||||
user = generate_user_maximum()
|
||||
users.append(user)
|
||||
return user
|
||||
|
||||
# Use unique RP to not collide with other credentials from other tests.
|
||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
||||
|
||||
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
|
||||
# res = device.sendGA(*req.toGA())
|
||||
current_credentials_count = 0
|
||||
|
||||
auths = []
|
||||
regs = [MCRes_DC]
|
||||
RK_to_generate = RK_CAPACITY_PER_RP - current_credentials_count
|
||||
for i in range(RK_to_generate):
|
||||
res = device.doMC(user=get_user(), rp=rp, rk=True)['res'].attestation_object
|
||||
regs.append(res)
|
||||
|
||||
res = device.GA(rp_id = rp['id'])['res']
|
||||
assert res.number_of_credentials == RK_CAPACITY_PER_RP
|
||||
|
||||
auths.append(res)
|
||||
for i in range(RK_CAPACITY_PER_RP - 1):
|
||||
auths.append(device.GNA())
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GNA()
|
||||
|
||||
auths = auths[::-1][-RK_to_generate:]
|
||||
regs = regs[-RK_to_generate:]
|
||||
users = users[-RK_to_generate:]
|
||||
|
||||
assert len(auths) == len(users)
|
||||
|
||||
for x, u in zip(auths, users):
|
||||
for y in ("name", "displayName", "id"):
|
||||
assert y in x.user.keys()
|
||||
assert x.user[y] == u[y]
|
||||
|
||||
assert len(auths) == len(regs)
|
||||
|
||||
|
||||
def test_rk_with_allowlist_of_different_rp(resetdevice):
|
||||
"""
|
||||
Test that a rk credential is not found when using an allowList item for a different RP
|
||||
"""
|
||||
|
||||
rk_rp = {"id": "rk-cred.org", "name": "Example"}
|
||||
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
|
||||
|
||||
server_rp = {"id": "server-cred.com", "name": "Example"}
|
||||
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
|
||||
|
||||
allow_list_with_different_rp_cred = [
|
||||
{
|
||||
"id": server_res.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
|
||||
def test_same_userId_overwrites_rk(resetdevice):
|
||||
"""
|
||||
A make credential request with a UserId & Rp that is the same as an existing one should overwrite.
|
||||
"""
|
||||
rp = {"id": "overwrite.org", "name": "Example"}
|
||||
user = generate_random_user()
|
||||
|
||||
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
||||
|
||||
# Should overwrite the first credential.
|
||||
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
||||
|
||||
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
|
||||
|
||||
# If there's only one credential, this is None
|
||||
assert ga_res.number_of_credentials == None
|
||||
|
||||
|
||||
def test_larger_icon_than_128(device):
|
||||
"""
|
||||
Test it works if we give an icon value larger than 128 bytes
|
||||
"""
|
||||
rp = {"id": "overwrite.org", "name": "Example"}
|
||||
user = generate_random_user()
|
||||
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
|
||||
|
||||
device.doMC(rp = rp, rk=True, user = user)
|
||||
|
||||
|
||||
def test_returned_credential(device):
|
||||
"""
|
||||
Test that when two rk credentials put in allow_list,
|
||||
only 1 will get returned.
|
||||
"""
|
||||
device.reset()
|
||||
|
||||
regs = []
|
||||
allow_list = []
|
||||
for i in range(0, 2):
|
||||
res = device.doMC(rk=True, user = {
|
||||
"id": b'123456' + bytes([i]), "name": f'Test User {i}', "displayName": f'Test User display {i}'
|
||||
})['res'].attestation_object
|
||||
regs.append(res)
|
||||
allow_list.append({"id": res.auth_data.credential_data.credential_id[:], "type": "public-key"})
|
||||
|
||||
|
||||
ga_res = device.GA(allow_list=allow_list,options={'up':False})['res']
|
||||
|
||||
# No other credentials should be returned
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GNA()
|
||||
|
||||
# the returned credential should have user id in it
|
||||
print(ga_res)
|
||||
assert 'id' in ga_res.user and len(ga_res.user["id"]) > 0
|
||||
150
tests/pico-fido/test_031_blob.py
Normal file
150
tests/pico-fido/test_031_blob.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from utils import verify
|
||||
import os
|
||||
|
||||
PIN='12345678'
|
||||
SMALL_BLOB=b"A"*32
|
||||
LARGE_BLOB=b"B"*1024
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCCredBlob(device):
|
||||
res = device.doMC(extensions={'credBlob': SMALL_BLOB})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GACredBlob(device, MCCredBlob):
|
||||
res = device.doGA(allow_list=[
|
||||
{"id": MCCredBlob.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], extensions={'getCredBlob': True})
|
||||
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCCredBlob, a, res['req']['client_data'].hash)
|
||||
return assertions[0]
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCLBK(device):
|
||||
res = device.doMC(
|
||||
rk=True,
|
||||
extensions={'largeBlob':{'support':'required'}}
|
||||
)['res']
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBRead(device, MCLBK):
|
||||
res = device.doGA(
|
||||
allow_list=[
|
||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],extensions={'largeBlob':{'read': True}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res']
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBReadLBK(GALBRead):
|
||||
return GALBRead.get_assertions()[0]
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBReadLB(GALBRead):
|
||||
return GALBRead.get_response(0)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBWrite(device, MCLBK):
|
||||
res = device.doGA(
|
||||
allow_list=[
|
||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res'].get_response(0)
|
||||
|
||||
def test_supports_credblob(info):
|
||||
assert info.extensions
|
||||
assert 'credBlob' in info.extensions
|
||||
assert info.max_cred_blob_length
|
||||
assert info.max_cred_blob_length > 0
|
||||
|
||||
def test_mc_credblob(MCCredBlob):
|
||||
assert MCCredBlob.auth_data.extensions
|
||||
assert "credBlob" in MCCredBlob.auth_data.extensions
|
||||
assert MCCredBlob.auth_data.extensions['credBlob'] is True
|
||||
|
||||
def test_ga_credblob(GACredBlob):
|
||||
assert GACredBlob.auth_data.extensions
|
||||
assert "credBlob" in GACredBlob.auth_data.extensions
|
||||
assert GACredBlob.auth_data.extensions['credBlob'] == SMALL_BLOB
|
||||
|
||||
def test_wrong_credblob(device, info):
|
||||
device.reset()
|
||||
cdh = os.urandom(32)
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
pin_token = ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG)
|
||||
protocol = PinProtocolV2()
|
||||
MC = device.MC(
|
||||
client_data_hash=cdh,
|
||||
extensions={'credBlob': b'A'*(info.max_cred_blob_length+1)},
|
||||
pin_uv_protocol=protocol.VERSION,
|
||||
pin_uv_param=protocol.authenticate(pin_token, cdh)
|
||||
)['res']
|
||||
|
||||
assert MC.auth_data.extensions
|
||||
assert "credBlob" in MC.auth_data.extensions
|
||||
assert MC.auth_data.extensions['credBlob'] is False
|
||||
|
||||
res = device.doGA(allow_list=[
|
||||
{"id": MC.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], extensions={'getCredBlob': True})
|
||||
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MC, a, res['req']['client_data'].hash)
|
||||
|
||||
assert assertions[0].auth_data.extensions
|
||||
assert "credBlob" in assertions[0].auth_data.extensions
|
||||
assert len(assertions[0].auth_data.extensions['credBlob']) == 0
|
||||
|
||||
def test_supports_largeblobs(info):
|
||||
assert info.extensions
|
||||
assert 'largeBlobKey' in info.extensions
|
||||
assert 'largeBlobs' in info.options
|
||||
assert info.max_large_blob is None or (info.max_large_blob > 1024)
|
||||
|
||||
def test_get_largeblobkey_mc(MCLBK):
|
||||
assert 'supported' in MCLBK.extension_results
|
||||
assert MCLBK.extension_results['supported'] is True
|
||||
|
||||
def test_get_largeblobkey_ga(GALBReadLBK):
|
||||
assert GALBReadLBK.large_blob_key is not None
|
||||
|
||||
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
||||
assert 'written' in GALBWrite.extension_results
|
||||
assert GALBWrite.extension_results['written'] is True
|
||||
|
||||
assert 'blob' in GALBReadLB.extension_results
|
||||
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
|
||||
169
tests/pico-fido/test_033_credprotect.py
Normal file
169
tests/pico-fido/test_033_credprotect.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from fido2.ctap2.extensions import CredProtectExtension
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.ctap import CtapError
|
||||
|
||||
class CredProtect:
|
||||
UserVerificationOptional = 1
|
||||
UserVerificationOptionalWithCredentialId = 2
|
||||
UserVerificationRequired = 3
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectOptional(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectOptionalList(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCCredProtectRequired(resetdevice):
|
||||
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
|
||||
return res
|
||||
|
||||
|
||||
def test_credprotect_make_credential_1(MCCredProtectOptional):
|
||||
assert MCCredProtectOptional.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
|
||||
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
|
||||
|
||||
def test_credprotect_make_credential_2(MCCredProtectOptionalList):
|
||||
assert MCCredProtectOptionalList.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
|
||||
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
|
||||
|
||||
def test_credprotect_make_credential_3(MCCredProtectRequired):
|
||||
assert MCCredProtectRequired.auth_data.extensions
|
||||
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
|
||||
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
|
||||
|
||||
def test_credprotect_optional_excluded(device, MCCredProtectOptional):
|
||||
""" CredProtectOptional Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
|
||||
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired):
|
||||
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
|
||||
exclude_list = [
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
# works
|
||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
|
||||
|
||||
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
|
||||
|
||||
# works
|
||||
res = device.doGA()['res'].get_assertions()[0]
|
||||
|
||||
# If there's only one credential, this is None
|
||||
assert res.number_of_credentials == None
|
||||
|
||||
def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
# works
|
||||
res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
|
||||
assert res1.number_of_credentials in (None, 2)
|
||||
|
||||
results = 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[:]
|
||||
|
||||
def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
|
||||
):
|
||||
|
||||
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
|
||||
|
||||
for ext in ["credProtect", "hmac-secret"]:
|
||||
assert res.auth_data.extensions
|
||||
assert ext in res.auth_data.extensions
|
||||
assert res.auth_data.extensions[ext] == True
|
||||
|
||||
|
||||
def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
|
||||
allow_list = [
|
||||
{
|
||||
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
{
|
||||
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
},
|
||||
]
|
||||
|
||||
pin = "12345678"
|
||||
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
|
||||
|
||||
assert res1.number_of_credentials in (None, 3)
|
||||
|
||||
219
tests/pico-fido/test_035_hmac_secret.py
Normal file
219
tests/pico-fido/test_035_hmac_secret.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.extensions import HmacSecretExtension
|
||||
from fido2.utils import hmac_sha256
|
||||
from fido2.ctap2.pin import PinProtocolV2
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from utils import *
|
||||
|
||||
salt1 = b"\xa5" * 32
|
||||
salt2 = b"\x96" * 32
|
||||
salt3 = b"\x03" * 32
|
||||
salt4 = b"\x5a" * 16
|
||||
salt5 = b"\x96" * 64
|
||||
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def MCHmacSecret(resetdevice):
|
||||
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
||||
return res['res'].attestation_object
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def hmac(resetdevice):
|
||||
return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
|
||||
|
||||
def test_hmac_secret_make_credential(MCHmacSecret):
|
||||
assert MCHmacSecret.auth_data.extensions
|
||||
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
|
||||
assert MCHmacSecret.auth_data.extensions["hmac-secret"] == True
|
||||
|
||||
def test_hmac_secret_info(info):
|
||||
assert "hmac-secret" in info.extensions
|
||||
|
||||
def test_fake_extension(device):
|
||||
device.doMC(extensions={"tetris": True})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
|
||||
):
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
|
||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||
ext = auth.extension_results
|
||||
assert ext
|
||||
assert "hmacGetSecret" in ext
|
||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
|
||||
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
|
||||
if len(salts) == 1:
|
||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
||||
if len(salts) == 2:
|
||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
||||
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
|
||||
|
||||
def get_output(device, MCHmacSecret, hmac, salts):
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
|
||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||
|
||||
ext = auth.extension_results
|
||||
assert ext
|
||||
assert "hmacGetSecret" in ext
|
||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||
|
||||
if len(salts) == 2:
|
||||
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
|
||||
else:
|
||||
return ext["hmacGetSecret"]['output1']
|
||||
|
||||
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
||||
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
|
||||
output12 = get_output(
|
||||
device, MCHmacSecret, hmac, (salt1, salt2)
|
||||
)
|
||||
output21 = get_output(
|
||||
device, MCHmacSecret, hmac, (salt2, salt1)
|
||||
)
|
||||
|
||||
assert output12[0] == output1
|
||||
assert output21[1] == output1
|
||||
assert output21[0] == output12[1]
|
||||
assert output12[0] != output12[1]
|
||||
|
||||
def test_missing_keyAgreement(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
|
||||
with pytest.raises(CtapError):
|
||||
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
|
||||
|
||||
def test_missing_saltAuth(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_missing_saltEnc(device, hmac):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_auth(device, hmac, MCHmacSecret):
|
||||
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
||||
bad_auth = list(hout[3][:])
|
||||
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
|
||||
bad_auth = bytes(bad_auth)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
|
||||
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
||||
def test_invalid_salt_length(device, hmac, salts):
|
||||
with pytest.raises(ValueError) as e:
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
|
||||
device.doGA(extensions={"hmacGetSecret": hout})
|
||||
|
||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||
def test_get_next_assertion_has_extension(
|
||||
device, hmac, salts
|
||||
):
|
||||
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
accounts = 3
|
||||
regs = []
|
||||
auths = []
|
||||
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
|
||||
fixed_users = [generate_random_user() for _ in range(accounts)]
|
||||
for i in range(accounts):
|
||||
res = device.doMC(extensions={"hmacCreateSecret": True},
|
||||
rk=True,
|
||||
rp=rp,
|
||||
user=fixed_users[i])['res'].attestation_object
|
||||
regs.append(res)
|
||||
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
|
||||
ga = device.doGA(extensions={"hmacGetSecret": hout}, rp_id=rp['id'])
|
||||
auths = ga['res'].get_assertions()
|
||||
|
||||
for x in auths:
|
||||
assert x.auth_data.flags & (1 << 7) # has extension
|
||||
ext = x.auth_data.extensions
|
||||
assert ext
|
||||
assert "hmac-secret" in ext
|
||||
assert isinstance(ext["hmac-secret"], bytes)
|
||||
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
||||
key = hmac.process_get_output(x)
|
||||
|
||||
|
||||
|
||||
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
||||
salts = [salt1]
|
||||
if (len(salts) == 2):
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
||||
else:
|
||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
||||
|
||||
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
|
||||
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
|
||||
|
||||
ext_no_uv = auth_no_uv.auth_data.extensions
|
||||
assert ext_no_uv
|
||||
assert "hmac-secret" in ext_no_uv
|
||||
assert isinstance(ext_no_uv["hmac-secret"], bytes)
|
||||
assert len(ext_no_uv["hmac-secret"]) == len(salts) * 32 + 16
|
||||
|
||||
# Now get same auth with UV
|
||||
hout = {'salt1':salts[0]}
|
||||
if (len(salts) > 1):
|
||||
hout['salt2'] = salts[1]
|
||||
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
||||
|
||||
assert auth_uv.authenticator_data.flags & (1 << 2)
|
||||
ext_uv = auth_uv.extension_results
|
||||
assert ext_uv
|
||||
assert "hmacGetSecret" in ext_uv
|
||||
assert len(ext_uv["hmacGetSecret"]) == len(salts)
|
||||
|
||||
# Now see if the hmac-secrets are different
|
||||
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
||||
101
tests/pico-fido/test_037_minpinlength.py
Normal file
101
tests/pico-fido/test_037_minpinlength.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from fido2.ctap2.extensions import CredProtectExtension
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from fido2.ctap2 import Config
|
||||
|
||||
PIN='12345678'
|
||||
MINPINLENGTH=6
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCMinPin(device):
|
||||
res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def SetMinPin(device):
|
||||
device.reset()
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com'])
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def SetMinPinWrongRpid(device):
|
||||
device.reset()
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['notanexample.com'])
|
||||
|
||||
def PinToken(device):
|
||||
return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG)
|
||||
|
||||
def FidoConfig(device):
|
||||
pt = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
return Config(device.client()._backend.ctap2, pin_protocol, pt)
|
||||
|
||||
def test_supports_minpin(info):
|
||||
assert info.extensions
|
||||
assert 'minPinLength' in info.extensions
|
||||
assert info.options
|
||||
assert 'setMinPINLength' in info.options
|
||||
assert info.options['setMinPINLength'] is True
|
||||
|
||||
def test_minpin(SetMinPin, MCMinPin):
|
||||
assert MCMinPin.auth_data.extensions
|
||||
assert "minPinLength" in MCMinPin.auth_data.extensions
|
||||
assert MCMinPin.auth_data.extensions['minPinLength'] == MINPINLENGTH
|
||||
|
||||
def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin):
|
||||
assert not MCMinPin.auth_data.extensions
|
||||
assert "minPinLength" not in MCMinPin.auth_data.extensions
|
||||
|
||||
def test_setminpin(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH+2,rp_ids=['example.com'])
|
||||
res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object
|
||||
assert res.auth_data.extensions
|
||||
assert "minPinLength" in res.auth_data.extensions
|
||||
assert res.auth_data.extensions['minPinLength'] == MINPINLENGTH+2
|
||||
|
||||
def test_no_setminpin(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
with pytest.raises(CtapError) as e:
|
||||
cfg.set_min_pin_length(MINPINLENGTH-2,rp_ids=['example.com'])
|
||||
assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION
|
||||
|
||||
def test_setminpin_check_force(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(len(PIN)+1,rp_ids=['example.com'])
|
||||
info = device.client()._backend.ctap2.get_info()
|
||||
assert info.force_pin_change == True
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"force", [True, False]
|
||||
)
|
||||
def test_setminpin_set_forcee(device, SetMinPin, MCMinPin, force):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com'],force_change_pin=force)
|
||||
info = device.client()._backend.ctap2.get_info()
|
||||
assert info.force_pin_change == force
|
||||
347
tests/pico-fido/test_040_cred_mgmt.py
Normal file
347
tests/pico-fido/test_040_cred_mgmt.py
Normal file
@@ -0,0 +1,347 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
import time
|
||||
import random
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2 import CredentialManagement
|
||||
from fido2.utils import sha256
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from binascii import hexlify
|
||||
from utils import generate_random_user
|
||||
|
||||
PIN = "12345678"
|
||||
|
||||
@pytest.fixture(params=[PIN], scope = 'function')
|
||||
def client_pin_set(request, device, client_pin):
|
||||
#device.reboot()
|
||||
device.reset()
|
||||
pin = request.param
|
||||
client_pin.set_pin(pin)
|
||||
|
||||
@pytest.fixture(scope = 'function')
|
||||
def MC_RK_Res(device, client_pin_set):
|
||||
rp = {"id": "ssh:", "name": "Bate Goiko"}
|
||||
device.doMC(rp=rp, rk=True)
|
||||
|
||||
rp = {"id": "xakcop.com", "name": "John Doe"}
|
||||
device.doMC(rp=rp, rk=True)
|
||||
|
||||
def PinToken(device):
|
||||
return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT)
|
||||
|
||||
def CredMgmt(device):
|
||||
pt = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, pt)
|
||||
|
||||
|
||||
def _test_enumeration(device, rp_map):
|
||||
"Enumerate credentials using BFS"
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_rps()
|
||||
assert len(rp_map.keys()) == len(res)
|
||||
|
||||
for rp in res:
|
||||
creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode()))
|
||||
assert len(creds) == rp_map[rp[3]["id"]]
|
||||
|
||||
|
||||
def _test_enumeration_interleaved(device, rp_map):
|
||||
"Enumerate credentials using DFS"
|
||||
credMgmt = CredMgmt(device)
|
||||
first_rp = credMgmt.enumerate_rps_begin()
|
||||
assert len(rp_map.keys()) == first_rp[CredentialManagement.RESULT.TOTAL_RPS]
|
||||
|
||||
rk_count = 1
|
||||
first_rk = credMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"].encode()))
|
||||
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
|
||||
c = credMgmt.enumerate_creds_next()
|
||||
rk_count += 1
|
||||
|
||||
assert rk_count == rp_map[first_rp[3]["id"]]
|
||||
|
||||
for i in range(1, first_rp[CredentialManagement.RESULT.TOTAL_RPS]):
|
||||
next_rp = credMgmt.enumerate_rps_next()
|
||||
|
||||
rk_count = 1
|
||||
first_rk =credMgmt.enumerate_creds_begin(
|
||||
sha256(next_rp[3]["id"].encode())
|
||||
)
|
||||
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
|
||||
c = credMgmt.enumerate_creds_next()
|
||||
rk_count += 1
|
||||
|
||||
assert rk_count == rp_map[next_rp[3]["id"]]
|
||||
|
||||
|
||||
def CredMgmtWrongPinAuth(device):
|
||||
pin_token = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
wrong_pt = bytearray(pin_token)
|
||||
wrong_pt[0] = (wrong_pt[0] + 1) % 256
|
||||
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, bytes(wrong_pt))
|
||||
|
||||
|
||||
def assert_cred_response_has_all_fields(cred_res):
|
||||
for i in (
|
||||
CredentialManagement.RESULT.USER,
|
||||
CredentialManagement.RESULT.CREDENTIAL_ID,
|
||||
CredentialManagement.RESULT.PUBLIC_KEY,
|
||||
CredentialManagement.RESULT.TOTAL_CREDENTIALS,
|
||||
#CredentialManagement.RESULT.CRED_PROTECT,
|
||||
):
|
||||
assert i in cred_res
|
||||
|
||||
|
||||
def test_get_info(info):
|
||||
assert "credMgmt" in info.options
|
||||
assert info.options["credMgmt"] == True
|
||||
assert 0x7 in info
|
||||
assert info[0x7] > 1
|
||||
assert 0x8 in info
|
||||
assert info[0x8] > 1
|
||||
|
||||
def test_get_metadata_ok(MC_RK_Res, device):
|
||||
metadata = CredMgmt(device).get_metadata()
|
||||
assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2
|
||||
assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48
|
||||
|
||||
def test_enumerate_rps(MC_RK_Res, device):
|
||||
res = CredMgmt(device).enumerate_rps()
|
||||
assert len(res) == 2
|
||||
assert res[0][CredentialManagement.RESULT.RP]["id"] == "ssh:"
|
||||
assert res[0][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"ssh:")
|
||||
# Solo doesn't store rpId with the exception of "ssh:"
|
||||
assert res[1][CredentialManagement.RESULT.RP]["id"] == "xakcop.com"
|
||||
assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com")
|
||||
|
||||
def test_enumarate_creds(MC_RK_Res, device):
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_creds(sha256(b"ssh:"))
|
||||
assert len(res) == 1
|
||||
assert_cred_response_has_all_fields(res[0])
|
||||
res = credMgmt.enumerate_creds(sha256(b"xakcop.com"))
|
||||
assert len(res) == 1
|
||||
assert_cred_response_has_all_fields(res[0])
|
||||
res = credMgmt.enumerate_creds(sha256(b"missing.com"))
|
||||
assert not res
|
||||
|
||||
def test_get_metadata_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.get_metadata()
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rpbegin_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.enumerate_rps_begin()
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rkbegin_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.enumerate_creds_begin(sha256(b"ssh:"))
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rpnext_without_rpbegin(device, MC_RK_Res):
|
||||
credMgmt = CredMgmt(device)
|
||||
credMgmt.enumerate_creds_begin(sha256(b"ssh:"))
|
||||
with pytest.raises(CtapError) as e:
|
||||
credMgmt.enumerate_rps_next()
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
def test_rknext_without_rkbegin(device, MC_RK_Res):
|
||||
credMgmt = CredMgmt(device)
|
||||
credMgmt.enumerate_rps_begin()
|
||||
with pytest.raises(CtapError) as e:
|
||||
credMgmt.enumerate_creds_next()
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
def test_delete(device):
|
||||
|
||||
# create a new RK
|
||||
rp = {"id": "example_3.com", "name": "John Doe 2"}
|
||||
reg = device.doMC(rp=rp, rk=True)['res'].attestation_object
|
||||
|
||||
# make sure it works
|
||||
auth = device.doGA(rp_id=rp['id'])
|
||||
|
||||
# get the ID from enumeration
|
||||
credMgmt = CredMgmt(device)
|
||||
creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
for cred in creds:
|
||||
if cred[7]["id"] == reg.auth_data.credential_data.credential_id:
|
||||
break
|
||||
|
||||
# delete it
|
||||
cred = {"id": cred[7]["id"], "type": "public-key"}
|
||||
credMgmt.delete_cred(cred)
|
||||
|
||||
# make sure it doesn't work
|
||||
with pytest.raises(CtapError) as e:
|
||||
auth = device.doGA(rp_id=rp['id'])
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_add_delete(device):
|
||||
""" Delete a credential in the 'middle' and ensure other credentials are not affected. """
|
||||
|
||||
rp = {"id": "example_4.com", "name": "John Doe 3"}
|
||||
regs = []
|
||||
|
||||
# create 3 new RK's
|
||||
for i in range(0, 3):
|
||||
reg = device.doMC(rp=rp, rk=True, user=generate_random_user())['res'].attestation_object
|
||||
regs.append(reg)
|
||||
|
||||
# Check they all enumerate
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash)
|
||||
assert len(res) == 3
|
||||
|
||||
# delete the middle one
|
||||
creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
for cred in creds:
|
||||
if cred[7]["id"] == regs[1].auth_data.credential_data.credential_id:
|
||||
break
|
||||
|
||||
assert cred[7]["id"] == regs[1].auth_data.credential_data.credential_id
|
||||
|
||||
cred = {"id": cred[7]["id"], "type": "public-key"}
|
||||
credMgmt.delete_cred(cred)
|
||||
|
||||
# Check one less enumerates
|
||||
res = credMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash)
|
||||
assert len(res) == 2
|
||||
|
||||
def test_multiple_creds_per_multiple_rps(
|
||||
device, MC_RK_Res
|
||||
):
|
||||
res = CredMgmt(device).enumerate_rps()
|
||||
assert len(res) == 2
|
||||
|
||||
new_rps = [
|
||||
{"id": "new_example_1.com", "name": "Example-3-creds"},
|
||||
{"id": "new_example_2.com", "name": "Example-3-creds"},
|
||||
{"id": "new_example_3.com", "name": "Example-3-creds"},
|
||||
]
|
||||
|
||||
# create 3 new credentials per RP
|
||||
for rp in new_rps:
|
||||
for i in range(0, 3):
|
||||
reg = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_rps()
|
||||
assert len(res) == 5
|
||||
|
||||
for rp in res:
|
||||
if rp[3]["id"][:12] == "new_example_":
|
||||
creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8")))
|
||||
assert len(creds) == 3
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
|
||||
)
|
||||
def test_multiple_enumeration(
|
||||
device, MC_RK_Res, enumeration_test
|
||||
):
|
||||
""" Test enumerate still works after different commands """
|
||||
|
||||
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
|
||||
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
new_rps = [
|
||||
{"id": "example-2.com", "name": "Example-2-creds", "count": 2},
|
||||
{"id": "example-1.com", "name": "Example-1-creds", "count": 1},
|
||||
{"id": "example-5.com", "name": "Example-5-creds", "count": 5},
|
||||
]
|
||||
|
||||
# create 3 new credentials per RP
|
||||
for rp in new_rps:
|
||||
for i in range(0, rp["count"]):
|
||||
reg = device.doMC(rp={"id": rp["id"], "name": rp["name"]}, rk=True, user=generate_random_user())
|
||||
|
||||
# Now expect creds from this RP
|
||||
expected_enumeration[rp["id"]] = rp["count"]
|
||||
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
|
||||
)
|
||||
def test_multiple_enumeration_with_deletions(
|
||||
device, MC_RK_Res, enumeration_test
|
||||
):
|
||||
""" Create each credential in random order. Test enumerate still works after randomly deleting each credential"""
|
||||
|
||||
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
|
||||
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
new_rps = [
|
||||
{"id": "example-1.com", "name": "Example-1-creds"},
|
||||
{"id": "example-2.com", "name": "Example-2-creds"},
|
||||
{"id": "example-2.com", "name": "Example-2-creds"},
|
||||
{"id": "example-3.com", "name": "Example-3-creds"},
|
||||
{"id": "example-3.com", "name": "Example-3-creds"},
|
||||
{"id": "example-3.com", "name": "Example-3-creds"},
|
||||
]
|
||||
random.shuffle(new_rps)
|
||||
|
||||
# create new credentials per RP in random order
|
||||
for rp in new_rps:
|
||||
device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||
|
||||
if rp["id"] not in expected_enumeration:
|
||||
expected_enumeration[rp["id"]] = 1
|
||||
else:
|
||||
expected_enumeration[rp["id"]] += 1
|
||||
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
total_creds = len(new_rps)
|
||||
|
||||
while total_creds != 0:
|
||||
rp = random.choice(list(expected_enumeration.keys()))
|
||||
|
||||
num = expected_enumeration[rp]
|
||||
|
||||
index = 0 if num == 1 else random.randint(0, num - 1)
|
||||
credMgmt = CredMgmt(device)
|
||||
cred = credMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index]
|
||||
|
||||
# print('Delete %d index (%d total) cred of %s' % (index, expected_enumeration[rp], rp))
|
||||
credMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"})
|
||||
|
||||
expected_enumeration[rp] -= 1
|
||||
if expected_enumeration[rp] == 0:
|
||||
del expected_enumeration[rp]
|
||||
|
||||
if len(list(expected_enumeration.keys())) == 0:
|
||||
break
|
||||
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
def _test_wrong_pinauth(device, cmd):
|
||||
|
||||
credMgmt = CredMgmtWrongPinAuth(device)
|
||||
|
||||
for i in range(2):
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
43
tests/pico-fido/test_051_ctap1_interop.py
Normal file
43
tests/pico-fido/test_051_ctap1_interop.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
# Test U2F register works with FIDO2 auth
|
||||
def test_ctap1_register(RegRes):
|
||||
pass
|
||||
|
||||
def test_ctap1_authenticate(RegRes, AuthRes):
|
||||
pass
|
||||
|
||||
def test_authenticate_ctap1_through_ctap2(device, RegRes):
|
||||
res = device.doGA(ctap1=False, allow_list=[
|
||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
])
|
||||
assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
|
||||
|
||||
# Test FIDO2 register works with U2F auth
|
||||
def test_ctap1_authenticate_attestation(MCRes, device):
|
||||
key_handle = MCRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
if len(key_handle) <= 255:
|
||||
res = device.doGA(ctap1=True, allow_list=[
|
||||
{"id": key_handle, "type": "public-key"}
|
||||
])
|
||||
else:
|
||||
print("ctap2 credId is longer than 255 bytes, cannot use with U2F.")
|
||||
130
tests/pico-fido/test_052_u2f.py
Normal file
130
tests/pico-fido/test_052_u2f.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
import os
|
||||
from fido2.ctap1 import APDU, ApduError, Ctap1
|
||||
from fido2.webauthn import CollectedClientData
|
||||
from fido2.utils import sha256
|
||||
|
||||
def test_u2f_reg(RegRes):
|
||||
pass
|
||||
|
||||
def test_u2f_auth(RegRes, AuthRes):
|
||||
pass
|
||||
|
||||
def test_u2f_auth_check_only(device, RegRes):
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.authenticate(
|
||||
RegRes['req']['client_data'].hash,
|
||||
RegRes['res'].attestation_object.auth_data.rp_id_hash,
|
||||
RegRes['res'].attestation_object.auth_data.credential_data.credential_id,
|
||||
check_only=True,
|
||||
)
|
||||
assert e.value.code == APDU.USE_NOT_SATISFIED
|
||||
|
||||
def test_version(device):
|
||||
assert device.ctap1.get_version() == "U2F_V2"
|
||||
|
||||
def test_bad_ins(device):
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.send_apdu(0, 0, 0, 0, b"")
|
||||
assert e.value.code == 0x6D00
|
||||
|
||||
def test_bad_cla(device):
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.send_apdu(1, Ctap1.INS.VERSION, 0, 0, b"abc")
|
||||
assert e.value.code == 0x6E00
|
||||
|
||||
@pytest.mark.parametrize("iterations", (5,))
|
||||
def test_u2f_it(device, iterations):
|
||||
lastc = 0
|
||||
|
||||
regs = []
|
||||
|
||||
cd = CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.CREATE, origin=None, challenge=os.urandom(32)
|
||||
)
|
||||
cdh = cd.hash
|
||||
rih = sha256(device.rp()['id'].encode())
|
||||
|
||||
for i in range(0, iterations):
|
||||
reg = device.ctap1.register(cdh, rih)
|
||||
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
|
||||
auth.verify(rih, cdh, reg.public_key)
|
||||
|
||||
regs.append(reg)
|
||||
# check endianness
|
||||
if lastc:
|
||||
assert (auth.counter - lastc) < 256
|
||||
lastc = auth.counter
|
||||
if lastc > 0x80000000:
|
||||
print("WARNING: counter is unusually high: %04x" % lastc)
|
||||
assert 0
|
||||
|
||||
for reg in regs:
|
||||
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
|
||||
|
||||
device.reboot()
|
||||
|
||||
for reg in regs:
|
||||
auth = device.ctap1.authenticate(cdh, rih, reg.key_handle)
|
||||
|
||||
for reg in regs:
|
||||
with pytest.raises(ApduError) as e:
|
||||
auth = device.ctap1.authenticate(
|
||||
cdh, rih, reg.key_handle, check_only=True
|
||||
)
|
||||
assert e.value.code == APDU.USE_NOT_SATISFIED
|
||||
|
||||
def test_bad_key_handle(device, RegRes):
|
||||
kh = bytearray(RegRes['res'].attestation_object.auth_data.credential_data.credential_id)
|
||||
kh[0] = kh[0] ^ (0x40)
|
||||
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.authenticate(
|
||||
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh, check_only=True
|
||||
)
|
||||
assert e.value.code == APDU.WRONG_DATA
|
||||
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.authenticate(
|
||||
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh
|
||||
)
|
||||
assert e.value.code == APDU.WRONG_DATA
|
||||
|
||||
def test_bad_key_handle_length(device, RegRes):
|
||||
kh = bytearray(RegRes['res'].attestation_object.auth_data.credential_data.credential_id)
|
||||
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.authenticate(
|
||||
RegRes['res'].client_data.hash, RegRes['res'].attestation_object.auth_data.rp_id_hash, kh[: len(kh) // 2]
|
||||
)
|
||||
assert e.value.code == APDU.WRONG_DATA
|
||||
|
||||
def test_incorrect_appid(device, RegRes):
|
||||
|
||||
badid = bytearray(RegRes['res'].attestation_object.auth_data.rp_id_hash)
|
||||
badid[0] = badid[0] ^ (0x40)
|
||||
with pytest.raises(ApduError) as e:
|
||||
device.ctap1.authenticate(
|
||||
RegRes['res'].client_data.hash, badid, RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||
)
|
||||
assert e.value.code == APDU.WRONG_DATA
|
||||
268
tests/pico-fido/test_055_hid.py
Normal file
268
tests/pico-fido/test_055_hid.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
/*
|
||||
* 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 os
|
||||
import socket
|
||||
import time
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
import pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.hid import CTAPHID
|
||||
from utils import Timeout
|
||||
|
||||
class TestHID(object):
|
||||
def test_long_ping(self, device):
|
||||
amt = 1000
|
||||
pingdata = os.urandom(amt)
|
||||
|
||||
t1 = time.time() * 1000
|
||||
r = device.send_data(CTAPHID.PING, pingdata)
|
||||
t2 = time.time() * 1000
|
||||
delt = t2 - t1
|
||||
|
||||
assert not (delt > 555 * (amt / 1000))
|
||||
|
||||
assert r == pingdata
|
||||
|
||||
def test_init(self, device, check_timeouts=False):
|
||||
if check_timeouts:
|
||||
with pytest.raises(socket.timeout):
|
||||
cmd, resp = self.recv_raw()
|
||||
|
||||
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
|
||||
r = device.send_data(CTAPHID.INIT, payload)
|
||||
print(r)
|
||||
assert r[:8] == payload
|
||||
|
||||
def test_ping(self, device):
|
||||
|
||||
pingdata = os.urandom(100)
|
||||
r = device.send_data(CTAPHID.PING, pingdata)
|
||||
assert r == pingdata
|
||||
|
||||
def test_wink(self, device):
|
||||
r = device.send_data(CTAPHID.WINK, "")
|
||||
|
||||
def test_cbor_no_payload(self, device):
|
||||
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
|
||||
r = device.send_data(CTAPHID.INIT, payload)
|
||||
capabilities = r[16]
|
||||
|
||||
if (capabilities ^ 0x04) != 0:
|
||||
print("Implements CBOR.")
|
||||
with pytest.raises(CtapError) as e:
|
||||
r = device.send_data(CTAPHID.CBOR, "")
|
||||
assert e.value.code == CtapError.ERR.INVALID_LENGTH
|
||||
else:
|
||||
print("CBOR is not implemented.")
|
||||
|
||||
def test_no_data_in_u2f_msg(self, device):
|
||||
payload = b"\x11\x11\x11\x11\x11\x11\x11\x11"
|
||||
r = device.send_data(CTAPHID.INIT, payload)
|
||||
capabilities = r[16]
|
||||
|
||||
if (capabilities ^ 0x08) == 0:
|
||||
print("U2F implemented.")
|
||||
with pytest.raises(CtapError) as e:
|
||||
r = device.send_data(CTAPHID.MSG, "")
|
||||
print(hexlify(r))
|
||||
assert e.value.code == CtapError.ERR.INVALID_LENGTH
|
||||
else:
|
||||
print("U2F not implemented.")
|
||||
|
||||
def test_invalid_hid_cmd(self, device):
|
||||
r = device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
r = device.send_data(0x66, "")
|
||||
assert e.value.code == CtapError.ERR.INVALID_COMMAND
|
||||
|
||||
def test_oversize_packet(self, device):
|
||||
device.send_raw("\x81\x1d\xba\x00")
|
||||
cmd, resp = device.recv_raw()
|
||||
assert resp[0] == CtapError.ERR.INVALID_LENGTH
|
||||
|
||||
def test_skip_sequence_number(self, device):
|
||||
r = device.send_data(CTAPHID.PING, "\x44" * 200)
|
||||
device.send_raw("\x81\x04\x90")
|
||||
device.send_raw("\x00")
|
||||
device.send_raw("\x01")
|
||||
# skip 2
|
||||
device.send_raw("\x03")
|
||||
cmd, resp = device.recv_raw()
|
||||
assert resp[0] == CtapError.ERR.INVALID_SEQ
|
||||
|
||||
def test_resync_and_ping(self, device):
|
||||
r = device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
pingdata = os.urandom(100)
|
||||
r = device.send_data(CTAPHID.PING, pingdata)
|
||||
if r != pingdata:
|
||||
raise ValueError("Ping data not echo'd")
|
||||
|
||||
def test_ping_abort(self, device):
|
||||
device.send_raw("\x81\x04\x00")
|
||||
device.send_raw("\x00")
|
||||
device.send_raw("\x01")
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
|
||||
def test_ping_abort_from_different_cid(self, device, check_timeouts=False):
|
||||
oldcid = device.dev._channel_id
|
||||
newcid = int.from_bytes(b"\x11\x22\x33\x44", 'big')
|
||||
device.send_raw("\x81\x10\x00")
|
||||
device.send_raw("\x00")
|
||||
device.send_raw("\x01")
|
||||
device.dev._channel_id = newcid
|
||||
device.send_raw(
|
||||
"\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88"
|
||||
) # init from different cid
|
||||
print("wait for init response")
|
||||
cmd, r = device.recv_raw() # init response
|
||||
assert cmd == 0x86
|
||||
device.dev._channel_id = oldcid
|
||||
if check_timeouts:
|
||||
# print('wait for timeout')
|
||||
cmd, r = device.recv_raw() # timeout response
|
||||
assert cmd == 0xBF
|
||||
|
||||
def test_timeout(self, device):
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
t1 = time.time() * 1000
|
||||
device.send_raw("\x81\x04\x00")
|
||||
device.send_raw("\x00")
|
||||
device.send_raw("\x01")
|
||||
cmd, r = device.recv_raw() # timeout response
|
||||
t2 = time.time() * 1000
|
||||
delt = t2 - t1
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.TIMEOUT
|
||||
assert delt < 1000 and delt > 400
|
||||
|
||||
def test_not_cont(self, device, check_timeouts=False):
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
device.send_raw("\x81\x04\x00")
|
||||
device.send_raw("\x00")
|
||||
device.send_raw("\x01")
|
||||
device.send_raw("\x81\x10\x00") # init packet
|
||||
cmd, r = device.recv_raw() # timeout response
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.INVALID_SEQ
|
||||
|
||||
if check_timeouts:
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
device.send_raw("\x01\x10\x00")
|
||||
with pytest.raises(socket.timeout):
|
||||
cmd, r = device.recv_raw() # timeout response
|
||||
|
||||
def test_check_busy(self, device):
|
||||
t1 = time.time() * 1000
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
#oldcid = device.cid().to_bytes(4, 'big')
|
||||
newcid = b"\x11\x22\x33\x44"
|
||||
device.send_raw("\x81\x04\x00")
|
||||
device.set_cid(newcid)
|
||||
device.send_raw("\x81\x04\x00")
|
||||
cmd, r = device.recv_raw() # busy response
|
||||
#t2 = time.time() * 1000
|
||||
#assert t2 - t1 < 100
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.CHANNEL_BUSY
|
||||
|
||||
def test_check_busy_interleaved(self, device):
|
||||
cid1 = b"\x11\x22\x33\x44"
|
||||
cid2 = b"\x01\x22\x33\x44"
|
||||
device.set_cid(cid2)
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
device.set_cid(cid1)
|
||||
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
|
||||
device.send_raw("\x81\x00\x63") # echo 99 bytes first channel
|
||||
|
||||
device.set_cid(cid2) # send ping on 2nd channel
|
||||
device.send_raw("\x81\x00\x39")
|
||||
cmd, r = device.recv_raw() # busy response
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
device.set_cid(cid1) # finish 1st channel ping
|
||||
device.send_raw("\x00")
|
||||
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.CHANNEL_BUSY
|
||||
|
||||
device.set_cid(cid1)
|
||||
cmd, r = device.recv_raw() # ping response
|
||||
assert cmd == 0x81
|
||||
assert len(r) == 0x39
|
||||
cmd, r = device.recv_raw() # ping response
|
||||
|
||||
def test_cid_0(self, device):
|
||||
device.reset()
|
||||
time.sleep(0.1)
|
||||
device.set_cid(b"\x00\x00\x00\x00")
|
||||
device.send_raw(
|
||||
"\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00"
|
||||
)
|
||||
cmd, r = device.recv_raw() # timeout
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.INVALID_CHANNEL
|
||||
device.set_cid(b"\x05\x04\x03\x02")
|
||||
|
||||
def test_cid_ffffffff(self, device):
|
||||
|
||||
device.set_cid(b"\xff\xff\xff\xff")
|
||||
device.send_raw(
|
||||
"\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\xff\xff\xff\xff"
|
||||
)
|
||||
cmd, r = device.recv_raw() # timeout
|
||||
assert cmd == 0xBF
|
||||
assert r[0] == CtapError.ERR.INVALID_CHANNEL
|
||||
device.set_cid(b"\x05\x04\x03\x02")
|
||||
|
||||
def test_keep_alive(self, device, check_timeouts=False):
|
||||
|
||||
precanned_make_credential = unhexlify(
|
||||
'01a401582031323334353637383961626364656630313233343536373'\
|
||||
'8396162636465663002a26269646b6578616d706c652e6f7267646e61'\
|
||||
'6d65694578616d706c65525003a462696446cc2abaf119f26469636f6'\
|
||||
'e781f68747470733a2f2f7777772e77332e6f72672f54522f77656261'\
|
||||
'7574686e2f646e616d657256696e204f6c696d7069612047657272696'\
|
||||
'56b646973706c61794e616d65781c446973706c617965642056696e20'\
|
||||
'4f6c696d706961204765727269650481a263616c672664747970656a7'\
|
||||
'075626c69632d6b6579')
|
||||
|
||||
count = 0
|
||||
def count_keepalive(_x):
|
||||
nonlocal count
|
||||
count += 1
|
||||
|
||||
# We should get a keepalive within .5s
|
||||
try:
|
||||
r = device.send_data(CTAPHID.CBOR, precanned_make_credential, timeout = .50, on_keepalive = count_keepalive)
|
||||
except CtapError as e:
|
||||
assert e.code == CtapError.ERR.KEEPALIVE_CANCEL
|
||||
assert count > 0
|
||||
|
||||
# wait for authnr to get UP or timeout
|
||||
while True:
|
||||
try:
|
||||
r = device.send_data(CTAPHID.CBOR, '\x04') # getInfo
|
||||
break
|
||||
except CtapError as e:
|
||||
assert e.code == CtapError.ERR.CHANNEL_BUSY
|
||||
258
tests/pico-fido/test_070_oath.py
Normal file
258
tests/pico-fido/test_070_oath.py
Normal file
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
/*
|
||||
* 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 pytest
|
||||
from utils import *
|
||||
import hmac, hashlib
|
||||
|
||||
INS_PUT = 0x01
|
||||
INS_DELETE = 0x02
|
||||
INS_SET_CODE = 0x03
|
||||
INS_RESET = 0x04
|
||||
INS_LIST = 0xa1
|
||||
INS_CALCULATE = 0xa2
|
||||
INS_VALIDATE = 0xa3
|
||||
INS_CALC_ALL = 0xa4
|
||||
INS_SEND_REMAINING = 0xa5
|
||||
|
||||
RESP_MORE_DATA = 0x61
|
||||
|
||||
TAG_NAME = 0x71
|
||||
TAG_NAME_LIST = 0x72
|
||||
TAG_KEY = 0x73
|
||||
TAG_CHALLENGE = 0x74
|
||||
TAG_RESPONSE = 0x75
|
||||
TAG_T_RESPONSE = 0x76
|
||||
TAG_NO_RESPONSE = 0x77
|
||||
TAG_PROPERTY = 0x78
|
||||
TAG_VERSION = 0x79
|
||||
TAG_IMF = 0x7a
|
||||
TAG_ALGO = 0x7b
|
||||
TAG_TOUCH_RESPONSE = 0x7c
|
||||
|
||||
TYPE_MASK = 0xf0
|
||||
TYPE_HOTP = 0x10
|
||||
TYPE_TOTP = 0x20
|
||||
|
||||
ALG_MASK = 0x0f
|
||||
ALG_SHA1 = 0x01
|
||||
ALG_SHA256 = 0x02
|
||||
|
||||
PROP_ALWAYS_INC = 0x01
|
||||
PROP_REQUIRE_TOUCH = 0x02
|
||||
|
||||
## Based on tests on https://github.com/Yubico/ykneo-oath/blob/master/test/test/pkgYkneoOathTest/YkneoOathTest.java
|
||||
|
||||
def test_select_oath(select_oath):
|
||||
pass
|
||||
|
||||
def list_apdu(ccid_card):
|
||||
resp = send_apdu(ccid_card, INS_LIST, p1=0, p2=0)
|
||||
return resp
|
||||
|
||||
name_kaka = [ord('k'), ord('a'), ord('k'), ord('a')]
|
||||
data_name = [TAG_NAME] + [len(name_kaka)] + name_kaka
|
||||
data_key = [TAG_KEY, 0x16, 0x21, 0x06, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b]
|
||||
data_chal = [TAG_CHALLENGE, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01]
|
||||
|
||||
def test_life(reset_oath):
|
||||
data = data_name + data_key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
||||
assert(len(resp) == 0)
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
||||
assert(resp == exp)
|
||||
|
||||
data = data_name + data_chal
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
||||
exp = [TAG_RESPONSE, 0x15, 0x06, 0xb3, 0x99, 0xbd, 0xfc, 0x9d, 0x05, 0xd1, 0x2a, 0xc4, 0x35, 0xc4, 0xc8, 0xd6, 0xcb, 0xd2, 0x47, 0xc4, 0x0a, 0x30, 0xf1]
|
||||
assert(resp == exp)
|
||||
|
||||
data = data_name
|
||||
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=data)
|
||||
resp = list_apdu(reset_oath)
|
||||
assert(len(resp) == 0)
|
||||
|
||||
def test_overwrite(reset_oath):
|
||||
data = data_name + data_key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=list(data))
|
||||
assert(len(resp) == 0)
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
||||
assert(resp == exp)
|
||||
|
||||
data = data_name + [TAG_CHALLENGE, 0x8] + list(bytes(b'\xff'*8))
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
||||
exp = [TAG_RESPONSE, 0x15, 0x06, 0x79, 0x3e, 0x1b, 0xbd, 0xbf, 0xa7, 0x75, 0xa8, 0x63,0xcc, 0x80, 0x02, 0xce, 0xe4, 0xbd, 0x6c, 0xd7, 0xce, 0xb8, 0xcd]
|
||||
assert(resp == exp)
|
||||
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, 5, 0x21] + name_kaka
|
||||
assert(resp == exp)
|
||||
|
||||
data = data_name + [TAG_CHALLENGE, 0x8] + list(bytes(b'\xff\x00'*4))
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=data)
|
||||
exp = [TAG_RESPONSE, 0x15, 0x06, 0x3b, 0x0e, 0x3c, 0x63, 0x1c, 0x01, 0x67, 0xb0, 0x93, 0xa5, 0xec, 0xb9, 0x09, 0x7d, 0x0b, 0x8e, 0x9a, 0xcc, 0x2f, 0x7f]
|
||||
assert(resp == exp)
|
||||
|
||||
def test_auth(reset_oath):
|
||||
key = list(bytes(b'kaka blahonga'))
|
||||
chal = [1,2,3,4,5,6,7,8]
|
||||
resp = [0x0c, 0x42, 0x8e, 0x9c, 0xba, 0xa3, 0xb3, 0xab, 0x18, 0x53, 0xd8, 0x79, 0xb9, 0xd2, 0x26, 0xf7, 0xce, 0xcc, 0x4a, 0x7a]
|
||||
data = [TAG_KEY, len(key)+1, ALG_SHA1 | TYPE_TOTP] + key + [TAG_CHALLENGE, len(chal)] + chal + [TAG_RESPONSE, len(resp)] + resp
|
||||
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=data)
|
||||
|
||||
reset_oath.connection.reconnect()
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = list_apdu(reset_oath)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
aid = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01, 0x01]
|
||||
resp = send_apdu(reset_oath, 0xA4, 0x04, 0x00, aid)
|
||||
assert(resp[15] == TAG_CHALLENGE)
|
||||
assert(resp[16] == 8)
|
||||
resp2 = hmac.digest(bytes(key), bytes(resp[17:17+8]), 'sha1')
|
||||
data = [TAG_RESPONSE, len(resp2)] + list(resp2) + [TAG_CHALLENGE, len(chal)] + chal
|
||||
resp = send_apdu(reset_oath, INS_VALIDATE, p1=0, p2=0, data=data)
|
||||
exp = [TAG_RESPONSE, 20] + list(hmac.digest(bytes(key), bytes(chal), 'sha1'))
|
||||
assert(exp == resp)
|
||||
resp = list_apdu(reset_oath)
|
||||
|
||||
def test_bothoath(reset_oath):
|
||||
digits = 6
|
||||
tname = list(bytes(b'totp'))
|
||||
data = [TAG_NAME, len(tname)] + tname + [TAG_KEY, 9, TYPE_TOTP | ALG_SHA1, digits] + list(bytes(b'foo bar'))
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
data[2] = ord('h')
|
||||
data[8] = TYPE_HOTP | ALG_SHA1
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
|
||||
hname = tname[:]
|
||||
hname[0] = ord('h')
|
||||
data = [TAG_CHALLENGE, 8, 0, 0, 0, 0, 0x02, 0xbc, 0xad, 0xc8]
|
||||
resp = send_apdu(reset_oath, INS_CALC_ALL, p1=0, p2=1, data=data)
|
||||
exp = [TAG_NAME, len(tname)] + tname + [TAG_T_RESPONSE, 5, digits, 0x3d, 0xc6, 0xbf, 0x3d] + [TAG_NAME, len(hname)] + hname + [TAG_NO_RESPONSE, 0x01, digits]
|
||||
assert(exp == resp)
|
||||
|
||||
data = [TAG_NAME, len(hname)] + hname + [TAG_CHALLENGE]
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, digits, 0x17, 0xfa, 0x2d, 0x40]
|
||||
assert(resp == exp)
|
||||
|
||||
def test_imf_overwrite(reset_oath):
|
||||
key = list(bytes(b'kaka'))
|
||||
imf = [0xff, 0x00, 0xff, 0xff]
|
||||
name = list(bytes(b'kaka'))
|
||||
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key + [TAG_IMF, len(imf)] + imf
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, 6, 0x45, 0xd9, 0x0f, 0x25]
|
||||
assert(exp == resp)
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, 6, 0x1b, 0xc5, 0x4a, 0x85]
|
||||
assert(exp == resp)
|
||||
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, 6, 0x16, 0x53, 0x24, 0xdb]
|
||||
assert(exp == resp)
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, 6, 0x53, 0xed, 0x5e, 0xb2]
|
||||
assert(exp == resp)
|
||||
|
||||
def test_imf_more(reset_oath):
|
||||
key = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30]
|
||||
imf = [0, 0, 0, 1]
|
||||
name = list(bytes(b'kaka'))
|
||||
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_KEY, len(key)+2, ALG_SHA1 | TYPE_HOTP, 6] + key + [TAG_IMF, len(imf)] + imf
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
data = [TAG_NAME, len(name)] + name + [TAG_CHALLENGE]
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=1, data=data)
|
||||
exp = [TAG_T_RESPONSE, 5, 6, 0x41, 0x39, 0x7e, 0xea]
|
||||
assert(exp == resp)
|
||||
|
||||
def test_delete(reset_oath):
|
||||
key = list(bytes(b'blahonga!'))
|
||||
firstname = list(bytes(b'one'))
|
||||
secondname = list(bytes(b'two'))
|
||||
thirdname = list(bytes(b'three'))
|
||||
type = ALG_SHA1 | TYPE_TOTP
|
||||
|
||||
data = [TAG_NAME, len(firstname)] + firstname + [TAG_KEY, len(key)+2, type, 6] + key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
data = [TAG_NAME, len(secondname)] + secondname + [TAG_KEY, len(key)+2, type, 6] + key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, len(firstname)+1, type] + firstname + [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
||||
assert(exp == resp)
|
||||
|
||||
data = [TAG_NAME, len(firstname)] + firstname
|
||||
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=data)
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
||||
assert(exp == resp)
|
||||
|
||||
data = [TAG_NAME, len(thirdname)] + thirdname + [TAG_KEY, len(key)+2, type, 6] + key
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=data)
|
||||
resp = list_apdu(reset_oath)
|
||||
exp = [TAG_NAME_LIST, len(thirdname)+1, type] + thirdname + [TAG_NAME_LIST, len(secondname)+1, type] + secondname
|
||||
assert(exp == resp)
|
||||
|
||||
def test_noauth(reset_oath):
|
||||
key = list(bytes(b'kaka blahonga'))
|
||||
chal = [1,2,3,4,5,6,7,8]
|
||||
resp = [0x0c, 0x42, 0x8e, 0x9c, 0xba, 0xa3, 0xb3, 0xab, 0x18, 0x53, 0xd8, 0x79, 0xb9, 0xd2, 0x26, 0xf7, 0xce, 0xcc, 0x4a, 0x7a]
|
||||
data = [TAG_KEY, len(key)+1, ALG_SHA1 | TYPE_TOTP] + key + [TAG_CHALLENGE, len(chal)] + chal + [TAG_RESPONSE, len(resp)] + resp
|
||||
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=data)
|
||||
|
||||
reset_oath.connection.reconnect()
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = list_apdu(reset_oath)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_PUT, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_DELETE, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_SET_CODE, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_CALCULATE, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_CALC_ALL, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x69, 0x82])
|
||||
|
||||
with pytest.raises(APDUResponse) as e:
|
||||
resp = send_apdu(reset_oath, INS_RESET, p1=0, p2=0, data=None)
|
||||
assert([e.value.sw1, e.value.sw2] == [0x6A, 0x86])
|
||||
resp = send_apdu(reset_oath, INS_RESET, p1=0xde, p2=0xad, data=None)
|
||||
5
tests/run-test-in-docker.sh
Executable file
5
tests/run-test-in-docker.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
source tests/docker_env.sh
|
||||
run_in_docker ./tests/start-up-and-test.sh
|
||||
|
||||
8
tests/start-up-and-test.sh
Executable file
8
tests/start-up-and-test.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash -eu
|
||||
|
||||
/usr/sbin/pcscd &
|
||||
sleep 2
|
||||
rm -f memory.flash
|
||||
cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid
|
||||
./build_in_docker/pico_fido > /dev/null &
|
||||
pytest tests
|
||||
208
tests/utils.py
Normal file
208
tests/utils.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
|
||||
from fido2.webauthn import AttestedCredentialData
|
||||
from fido2.cose import CoseKey
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from fido2.utils import bytes2int, int2bytes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
import random
|
||||
import string
|
||||
import secrets
|
||||
import math
|
||||
from threading import Event, Timer
|
||||
from numbers import Number
|
||||
|
||||
import sys
|
||||
try:
|
||||
from smartcard.CardType import AnyCardType
|
||||
from smartcard.CardRequest import CardRequest
|
||||
from smartcard.Exceptions import CardRequestTimeoutException, CardConnectionException
|
||||
except ModuleNotFoundError:
|
||||
print('ERROR: smarctard module not found! Install pyscard package.\nTry with `pip install pyscard`')
|
||||
sys.exit(-1)
|
||||
|
||||
class APDUResponse(Exception):
|
||||
def __init__(self, sw1, sw2):
|
||||
self.sw1 = sw1
|
||||
self.sw2 = sw2
|
||||
super().__init__(f'SW:{sw1:02X}{sw2:02X}')
|
||||
|
||||
def send_apdu(card, command, p1, p2, data=None, ne=None):
|
||||
lc = []
|
||||
dataf = []
|
||||
if (data):
|
||||
lc = [0x00] + list(len(data).to_bytes(2, 'big'))
|
||||
dataf = data
|
||||
if (ne is None):
|
||||
le = [0x00, 0x00]
|
||||
else:
|
||||
le = list(ne.to_bytes(2, 'big'))
|
||||
if (isinstance(command, list) and len(command) > 1):
|
||||
apdu = command
|
||||
else:
|
||||
apdu = [0x00, command]
|
||||
|
||||
apdu = apdu + [p1, p2] + lc + dataf + le
|
||||
try:
|
||||
response, sw1, sw2 = card.connection.transmit(apdu)
|
||||
except CardConnectionException:
|
||||
card.connection.reconnect()
|
||||
response, sw1, sw2 = card.connection.transmit(apdu)
|
||||
if (sw1 != 0x90):
|
||||
raise APDUResponse(sw1, sw2)
|
||||
return response
|
||||
|
||||
|
||||
def verify(MC, GA, client_data_hash):
|
||||
credential_data = AttestedCredentialData(MC.auth_data.credential_data)
|
||||
GA.verify(client_data_hash, credential_data.public_key)
|
||||
|
||||
|
||||
def generate_random_user():
|
||||
# https://www.w3.org/TR/webauthn/#user-handle
|
||||
user_id_length = random.randint(1, 64)
|
||||
user_id = secrets.token_bytes(user_id_length)
|
||||
|
||||
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
|
||||
name = "User name"
|
||||
display_name = "Displayed " + name
|
||||
|
||||
return {"id": user_id, "name": name, "displayName": display_name}
|
||||
|
||||
counter = 1
|
||||
def generate_user_maximum():
|
||||
"""
|
||||
Generate RK with the maximum lengths of the fields, according to the minimal requirements of the FIDO2 spec
|
||||
"""
|
||||
global counter
|
||||
|
||||
# https://www.w3.org/TR/webauthn/#user-handle
|
||||
user_id_length = 64
|
||||
user_id = secrets.token_bytes(user_id_length)
|
||||
|
||||
# https://www.w3.org/TR/webauthn/#dictionary-pkcredentialentity
|
||||
name = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(64))
|
||||
|
||||
name = f"{counter}: {name}"
|
||||
icon = "https://www.w3.org/TR/webauthn/" + "A" * 128
|
||||
display_name = "Displayed " + name
|
||||
|
||||
name = name[:64]
|
||||
display_name = display_name[:64]
|
||||
icon = icon[:128]
|
||||
|
||||
counter += 1
|
||||
|
||||
return {"id": user_id, "name": name, "icon": icon, "displayName": display_name}
|
||||
|
||||
def shannon_entropy(data):
|
||||
s = 0.0
|
||||
total = len(data)
|
||||
for x in range(0, 256):
|
||||
freq = data.count(x)
|
||||
p = freq / total
|
||||
if p > 0:
|
||||
s -= p * math.log2(p)
|
||||
return s
|
||||
|
||||
|
||||
# Timeout from:
|
||||
# https://github.com/Yubico/python-fido2/blob/f1dc028d6158e1d6d51558f72055c65717519b9b/fido2/utils.py
|
||||
# Copyright (c) 2013 Yubico AB
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that the following
|
||||
# conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
class Timeout(object):
|
||||
"""Utility class for adding a timeout to an event.
|
||||
:param time_or_event: A number, in seconds, or a threading.Event object.
|
||||
:ivar event: The Event associated with the Timeout.
|
||||
:ivar timer: The Timer associated with the Timeout, if any.
|
||||
"""
|
||||
|
||||
def __init__(self, time_or_event):
|
||||
|
||||
if isinstance(time_or_event, Number):
|
||||
self.event = Event()
|
||||
self.timer = Timer(time_or_event, self.event.set)
|
||||
else:
|
||||
self.event = time_or_event
|
||||
self.timer = None
|
||||
|
||||
def __enter__(self):
|
||||
if self.timer:
|
||||
self.timer.start()
|
||||
return self.event
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if self.timer:
|
||||
self.timer.cancel()
|
||||
self.timer.join()
|
||||
|
||||
class ES256K(CoseKey):
|
||||
ALGORITHM = -47
|
||||
_HASH_ALG = hashes.SHA256()
|
||||
|
||||
def verify(self, message, signature):
|
||||
if self[-1] != 8:
|
||||
raise ValueError("Unsupported elliptic curve")
|
||||
ec.EllipticCurvePublicNumbers(
|
||||
bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP256K1()
|
||||
).public_key(default_backend()).verify(
|
||||
signature, message, ec.ECDSA(self._HASH_ALG)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_cryptography_key(cls, public_key):
|
||||
pn = public_key.public_numbers()
|
||||
return cls(
|
||||
{
|
||||
1: 2,
|
||||
3: cls.ALGORITHM,
|
||||
-1: 8,
|
||||
-2: int2bytes(pn.x, 32),
|
||||
-3: int2bytes(pn.y, 32),
|
||||
}
|
||||
)
|
||||
611
tools/pico-fido-tool.py
Normal file
611
tools/pico-fido-tool.py
Normal file
@@ -0,0 +1,611 @@
|
||||
#!/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()
|
||||
61
tools/secure_key/macos.py
Normal file
61
tools/secure_key/macos.py
Normal file
@@ -0,0 +1,61 @@
|
||||
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)
|
||||
44
tools/secure_key/windows.py
Normal file
44
tools/secure_key/windows.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import sys
|
||||
|
||||
DOMAIN = "PicoKeys.com"
|
||||
USERNAME = "Pico-Fido"
|
||||
|
||||
try:
|
||||
import keyring
|
||||
except:
|
||||
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyring`')
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption, load_pem_private_key
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
except:
|
||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
|
||||
def generate_secure_key():
|
||||
pkey = ec.generate_private_key(ec.SECP256R1())
|
||||
set_secure_key(pkey)
|
||||
return keyring.get_password(DOMAIN, USERNAME)
|
||||
|
||||
def get_d(key):
|
||||
return load_pem_private_key(key, password=None).private_numbers().private_value.to_bytes(32, 'big')
|
||||
|
||||
def set_secure_key(pk):
|
||||
try:
|
||||
keyring.delete_password(DOMAIN, USERNAME)
|
||||
except:
|
||||
pass
|
||||
keyring.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode())
|
||||
|
||||
def get_secure_key():
|
||||
key = None
|
||||
try:
|
||||
key = keyring.get_password(DOMAIN, USERNAME)
|
||||
if (key is None):
|
||||
raise TypeError
|
||||
except (keyring.errors.KeyringError, TypeError):
|
||||
key = generate_secure_key()
|
||||
return get_d(key.encode())
|
||||
1
tools/words.py
Normal file
1
tools/words.py
Normal file
File diff suppressed because one or more lines are too long
@@ -2,12 +2,44 @@
|
||||
|
||||
git submodule update --init --recursive
|
||||
sudo apt update
|
||||
|
||||
if [[ $1 == "pico" ]]; then
|
||||
sudo apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib
|
||||
git clone https://github.com/raspberrypi/pico-sdk
|
||||
cd pico-sdk
|
||||
git checkout tags/2.1.0
|
||||
git submodule update --init
|
||||
cd ..
|
||||
git clone https://github.com/raspberrypi/picotool
|
||||
cd picotool
|
||||
git submodule update --init
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DPICO_SDK_PATH=../../pico-sdk ..
|
||||
make -j`nproc`
|
||||
sudo make install
|
||||
cd ../..
|
||||
mkdir build_pico
|
||||
cd build_pico
|
||||
cmake -DPICO_SDK_PATH=../pico-sdk ..
|
||||
make
|
||||
cd ..
|
||||
elif [[ $1 == "esp32" ]]; then
|
||||
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
|
||||
git clone --recursive https://github.com/espressif/esp-idf.git
|
||||
cd esp-idf
|
||||
./install.sh esp32s3
|
||||
. ./export.sh
|
||||
cd ..
|
||||
idf.py set-target esp32s3
|
||||
idf.py all
|
||||
mkdir -p release
|
||||
cd build
|
||||
esptool.py --chip ESP32-S3 merge_bin -o ../release/pico_fido_esp32-s3.bin @flash_args
|
||||
cd ..
|
||||
else
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DENABLE_EMULATION=1 ..
|
||||
make
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user