Merge branch 'drop-py2' into next-py3.

This commit is contained in:
Dain Nilsson 2021-08-18 16:02:42 +02:00
commit eae65b57a0
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
75 changed files with 1405 additions and 1725 deletions

View File

@ -68,52 +68,3 @@ jobs:
with:
name: fido2-python-sdist
path: dist
deb:
#needs: test
runs-on: ubuntu-latest
name: Build .deb
steps:
- uses: actions/checkout@v2
- name: Check Debian version
run: |
export PY_VER=$(awk '/__version__/{print $NF}' fido2/__init__.py | tr -d '"')
export DEB_VER=$(dpkg-parsechangelog --show-field Version)
case "$PY_VER" in
*-dev*)
if [[ ! $DEB_VER =~ "+git" ]]
then
echo "Debian package version mismatch, must contain +git!"
exit 1
fi
;;
*)
if [ $PY_VER != $DEB_VER ]
then
echo "Debian package version mismatch, expected $PY_VER!"
exit 1
fi
;;
esac
- name: Build .deb
run: |
sudo apt install -y devscripts equivs
yes | sudo mk-build-deps -i debian/control
debuild -us -uc
mkdir dist
mv ../python3-fido2_* dist/
- name: Install .deb
run: |
! python3 -c "import fido2"
sudo dpkg -i dist/python3-fido2_*.deb
python3 -c "import fido2"
- name: Upload Debian packages
uses: actions/upload-artifact@v1
with:
name: python-deb-files
path: dist

5
debian/changelog vendored
View File

@ -1,5 +0,0 @@
python-fido2 (0.9.1+git) xenial; urgency=low
* Build for ppa
-- Dain Nilsson <dain@yubico.com> Wed, 3 Feb 2021 09:48:19 +0100

1
debian/compat vendored
View File

@ -1 +0,0 @@
9

29
debian/control vendored
View File

@ -1,29 +0,0 @@
Source: python-fido2
Maintainer: Debian Authentication Maintainers <pkg-auth-maintainers@lists.alioth.debian.org>
Uploaders: Dag Heyman <dag@yubico.com>,
Dain Nilsson <dain@yubico.com>,
Emil Lundberg <emil@yubico.com>
Section: python
Priority: optional
Standards-Version: 4.1.1
Build-Depends: debhelper (>= 9),
dh-python,
python3-all,
python3-cryptography,
python3-setuptools
Homepage: https://www.github.com/python-fido2/
X-Python3-Version: >= 3.6
Package: python3-fido2
Architecture: all
Section: python
Depends: ${misc:Depends},
python3,
python3-cryptography,
python3-setuptools,
python3-six,
Recommends: libu2f-udev,
python3-pyscard,
Description: Python library for implementing FIDO 2.0
A Python library for communicating with a FIDO device over USB HID as
well as verifying attestation and assertion signatures.

52
debian/copyright vendored
View File

@ -1,52 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: python-fido2
Source: https://developers.yubico.com/python-fido2
Files: *
Copyright: Copyright (c) 2018 Yubico AB
License: BSD-2-clause
Files: debian/*
Copyright: Copyright (c) 2018 Yubico AB
Copyright (c) 2018 Nicolas Braud-Santoni
License: BSD-2-clause
Files: fido2/_pyu2f/*
Copyright: Copyright (c) 2016 Google Inc.
License: Apache-2.0
On Debian systems the full text of the Apache-2.0 license can be found in
/usr/share/common-licenses/Apache-2.0.
Files: fido2/public_suffix_list.dat
Copyright: Copyright (c) 2007-16 Mozilla Foundation
License: MPL-2.0
On Debian systems the full text of the MPL-2.0 license can be found in
/usr/share/common-licenses/MPL-2.0.
License: BSD-2-clause
All rights reserved.
.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
.
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
.
* 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
OWNER 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.

View File

@ -1 +0,0 @@
fido2 python-fido2; PEP386

7
debian/rules vendored
View File

@ -1,7 +0,0 @@
#!/usr/bin/make -f
export PYBUILD_NAME=fido2
export PYBUILD_DISABLE=test
%:
dh $@ --with python3 --buildsystem=pybuild

View File

@ -1 +0,0 @@
3.0 (native)

View File

@ -35,7 +35,8 @@ Consider this highly experimental.
from __future__ import print_function, absolute_import, unicode_literals
from fido2.hid import CtapHidDevice
from fido2.ctap2 import Ctap2, ClientPin, FPBioEnrollment, CaptureError
from fido2.ctap2 import Ctap2, FPBioEnrollment, CaptureError
from fido2.ctap2.pin import ClientPin
from getpass import getpass
import sys

View File

@ -30,10 +30,9 @@ Connects to each attached FIDO device, and:
1. If the device supports CBOR commands, perform a getInfo command.
2. If the device supports WINK, perform the wink command.
"""
from __future__ import print_function, absolute_import, unicode_literals
from fido2.hid import CtapHidDevice, CAPABILITY
from fido2.ctap2 import CTAP2
from fido2.ctap2 import Ctap2
try:
from fido2.pcsc import CtapPcscDevice
@ -56,7 +55,7 @@ for dev in enumerate_devices():
print("CTAPHID protocol version: %d" % dev.version)
if dev.capabilities & CAPABILITY.CBOR:
ctap2 = CTAP2(dev)
ctap2 = Ctap2(dev)
info = ctap2.get_info()
print("DEVICE INFO: %s" % info)
else:

View File

@ -34,7 +34,8 @@ On Windows, the native WebAuthn API will be used.
from __future__ import print_function, absolute_import, unicode_literals
from fido2.hid import CtapHidDevice
from fido2.ctap2 import ClientPin, LargeBlobs
from fido2.ctap2 import LargeBlobs
from fido2.ctap2.pin import ClientPin
from fido2.client import Fido2Client
from fido2.server import Fido2Server
from getpass import getpass

View File

@ -1,13 +0,0 @@
[[source]]
verify_ssl = true
name = "pypi"
url = "https://pypi.org/simple"
[packages]
flask = "*"
pyOpenSSL = "*"
"5448283" = {editable = true, path = "./../.."}
[scripts]
server = "python server.py"
server-u2f = "python server-u2f.py"

View File

@ -1,204 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "bf2fa9b63243b7172b84ae28c7fc3abc291ed1bb91f4919f2b2c47bfc0ccdd4d"
},
"pipfile-spec": 6,
"requires": {},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"5448283": {
"editable": true,
"path": "./../.."
},
"cffi": {
"hashes": [
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
],
"version": "==1.14.5"
},
"click": {
"hashes": [
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"cryptography": {
"hashes": [
"sha256:0bf49d5b38e4f3745a0eab0597fa97720dd49b30d65f534b49a82e303f149deb",
"sha256:18d6f3ac1da14a01c95f4590ee58e96aa6628d231ce738e9eca330b9997127b6",
"sha256:4f6761a82b51fe02cda8f45af1c2f698a10f50003dc9c2572d8a49eda2e6d35b",
"sha256:8b3b79af57e12aabbc3db81e563eaa07870293a1ffdcc891d107035ce9a0dbff",
"sha256:9c6f7552d4f2130542d488b9d9e5b1546204b5d1aa90c823d50cce8eed421363",
"sha256:b0873ac0c0e6bc6882cd285930cc382ec4e78786be71bdc113c06246eea61294",
"sha256:c8dc9859c5a046e1ca22da360dfd609c09064a4f974881cb5cba977c581088ec"
],
"markers": "python_version >= '3.6'",
"version": "==3.4.5"
},
"fido2": {
"editable": true,
"path": "./../.."
},
"flask": {
"hashes": [
"sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
"sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
],
"index": "pypi",
"version": "==1.1.2"
},
"itsdangerous": {
"hashes": [
"sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
"sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.0"
},
"jinja2": {
"hashes": [
"sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
"sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.11.3"
},
"markupsafe": {
"hashes": [
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.1.1"
},
"pycparser": {
"hashes": [
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pyopenssl": {
"hashes": [
"sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51",
"sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"
],
"index": "pypi",
"version": "==20.0.1"
},
"six": {
"hashes": [
"sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
"sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.15.0"
},
"werkzeug": {
"hashes": [
"sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
"sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.0.1"
}
},
"develop": {}
}

View File

@ -4,17 +4,17 @@ WebAuthn credential registration, and use.
=== Running
To run this sample, you will need `pipenv`. For instructions on installing
`pipenv`, see https://docs.pipenv.org.
To run this sample, you will need `poetry`. For instructions on installing
`poetry`, see https://python-poetry.org/.
Run the following command in the `examples/server` directory to set up the
example:
$ pipenv install
$ poetry install
Once the environment has been created, you can run the server by running:
$ pipenv run server
$ poetry run server
When the server is running, use a browser supporting WebAuthn and open
https://localhost:5000 to access the website.
@ -49,11 +49,12 @@ a slightly altered version of the example server which uses this class to
authenticate U2F credentials as well as WebAuthn credentials. To run this
version of the server, run:
$ pipenv run server-u2f
$ poetry run server-u2f
This version allows registration both using the newer WebAuthn APIs and by using
the legacy U2F APIs, so that you can test authentication using both credential
types. The source code for this version of the server is in `server-u2f.py`.
types. The source code for this version of the server is in
`server/server_u2f.py`.
NOTE: There should be no need to support registration of new U2F credentials as
new registrations should be using the WebAuthn APIs, even for existing users.

283
examples/server/poetry.lock generated Normal file
View File

@ -0,0 +1,283 @@
[[package]]
name = "cffi"
version = "1.14.4"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "click"
version = "7.1.2"
description = "Composable command line interface toolkit"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "cryptography"
version = "3.3.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*"
[package.dependencies]
cffi = ">=1.12"
six = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=3.6.0,!=3.9.0,!=3.9.1,!=3.9.2)", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "dataclasses"
version = "0.8"
description = "A backport of the dataclasses module for Python 3.6"
category = "main"
optional = false
python-versions = ">=3.6, <3.7"
[[package]]
name = "fido2"
version = "1.0.0-dev0"
description = "FIDO2/WebAuthn library for implementing clients and servers."
category = "main"
optional = false
python-versions = "^3.6"
develop = false
[package.dependencies]
cryptography = "^2.1 || ^3.0"
dataclasses = {version = "^0.8", markers = "python_version < \"3.7\""}
uhid-freebsd = {version = "^1.2.1", markers = "sys_platform == \"FreeBSD\""}
[package.extras]
pcsc = ["pyscard (>=1.9,<3.0.0)"]
[package.source]
type = "directory"
url = "../.."
[[package]]
name = "flask"
version = "1.1.2"
description = "A simple framework for building complex web applications."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
click = ">=5.1"
itsdangerous = ">=0.24"
Jinja2 = ">=2.10.1"
Werkzeug = ">=0.15"
[package.extras]
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
dotenv = ["python-dotenv"]
[[package]]
name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "jinja2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
MarkupSafe = ">=0.23"
[package.extras]
i18n = ["Babel (>=0.8)"]
[[package]]
name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
[[package]]
name = "pycparser"
version = "2.20"
description = "C parser in Python"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "six"
version = "1.15.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "uhid-freebsd"
version = "1.2.1"
description = "Get information on FreeBSD uhid devices."
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"]
[metadata]
lock-version = "1.1"
python-versions = "^3.6"
content-hash = "280b6c647e55e56ad8b54faa26400ce17549b5126ce1ed9482ccb1f75a034dc6"
[metadata.files]
cffi = [
{file = "cffi-1.14.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ebb253464a5d0482b191274f1c8bf00e33f7e0b9c66405fbffc61ed2c839c775"},
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2c24d61263f511551f740d1a065eb0212db1dbbbbd241db758f5244281590c06"},
{file = "cffi-1.14.4-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f7a31251289b2ab6d4012f6e83e58bc3b96bd151f5b5262467f4bb6b34a7c26"},
{file = "cffi-1.14.4-cp27-cp27m-win32.whl", hash = "sha256:5cf4be6c304ad0b6602f5c4e90e2f59b47653ac1ed9c662ed379fe48a8f26b0c"},
{file = "cffi-1.14.4-cp27-cp27m-win_amd64.whl", hash = "sha256:f60567825f791c6f8a592f3c6e3bd93dd2934e3f9dac189308426bd76b00ef3b"},
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c6332685306b6417a91b1ff9fae889b3ba65c2292d64bd9245c093b1b284809d"},
{file = "cffi-1.14.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d9efd8b7a3ef378dd61a1e77367f1924375befc2eba06168b6ebfa903a5e59ca"},
{file = "cffi-1.14.4-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:51a8b381b16ddd370178a65360ebe15fbc1c71cf6f584613a7ea08bfad946698"},
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1d2c4994f515e5b485fd6d3a73d05526aa0fcf248eb135996b088d25dfa1865b"},
{file = "cffi-1.14.4-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:af5c59122a011049aad5dd87424b8e65a80e4a6477419c0c1015f73fb5ea0293"},
{file = "cffi-1.14.4-cp35-cp35m-win32.whl", hash = "sha256:594234691ac0e9b770aee9fcdb8fa02c22e43e5c619456efd0d6c2bf276f3eb2"},
{file = "cffi-1.14.4-cp35-cp35m-win_amd64.whl", hash = "sha256:64081b3f8f6f3c3de6191ec89d7dc6c86a8a43911f7ecb422c60e90c70be41c7"},
{file = "cffi-1.14.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f803eaa94c2fcda012c047e62bc7a51b0bdabda1cad7a92a522694ea2d76e49f"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:105abaf8a6075dc96c1fe5ae7aae073f4696f2905fde6aeada4c9d2926752362"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0638c3ae1a0edfb77c6765d487fee624d2b1ee1bdfeffc1f0b58c64d149e7eec"},
{file = "cffi-1.14.4-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b"},
{file = "cffi-1.14.4-cp36-cp36m-win32.whl", hash = "sha256:155136b51fd733fa94e1c2ea5211dcd4c8879869008fc811648f16541bf99668"},
{file = "cffi-1.14.4-cp36-cp36m-win_amd64.whl", hash = "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009"},
{file = "cffi-1.14.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a7711edca4dcef1a75257b50a2fbfe92a65187c47dab5a0f1b9b332c5919a3fb"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:00e28066507bfc3fe865a31f325c8391a1ac2916219340f87dfad602c3e48e5d"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03"},
{file = "cffi-1.14.4-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:a5ed8c05548b54b998b9498753fb9cadbfd92ee88e884641377d8a8b291bcc01"},
{file = "cffi-1.14.4-cp37-cp37m-win32.whl", hash = "sha256:00a1ba5e2e95684448de9b89888ccd02c98d512064b4cb987d48f4b40aa0421e"},
{file = "cffi-1.14.4-cp37-cp37m-win_amd64.whl", hash = "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35"},
{file = "cffi-1.14.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:df5169c4396adc04f9b0a05f13c074df878b6052430e03f50e68adf3a57aa28d"},
{file = "cffi-1.14.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9ffb888f19d54a4d4dfd4b3f29bc2c16aa4972f1c2ab9c4ab09b8ab8685b9c2b"},
{file = "cffi-1.14.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53"},
{file = "cffi-1.14.4-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d5ff0621c88ce83a28a10d2ce719b2ee85635e85c515f12bac99a95306da4b2e"},
{file = "cffi-1.14.4-cp38-cp38-win32.whl", hash = "sha256:b4e248d1087abf9f4c10f3c398896c87ce82a9856494a7155823eb45a892395d"},
{file = "cffi-1.14.4-cp38-cp38-win_amd64.whl", hash = "sha256:ec80dc47f54e6e9a78181ce05feb71a0353854cc26999db963695f950b5fb375"},
{file = "cffi-1.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:b18e0a9ef57d2b41f5c68beefa32317d286c3d6ac0484efd10d6e07491bb95dd"},
{file = "cffi-1.14.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:045d792900a75e8b1e1b0ab6787dd733a8190ffcf80e8c8ceb2fb10a29ff238a"},
{file = "cffi-1.14.4-cp39-cp39-win32.whl", hash = "sha256:ba4e9e0ae13fc41c6b23299545e5ef73055213e466bd107953e4a013a5ddd7e3"},
{file = "cffi-1.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:f032b34669220030f905152045dfa27741ce1a6db3324a5bc0b96b6c7420c87b"},
{file = "cffi-1.14.4.tar.gz", hash = "sha256:1a465cbe98a7fd391d47dce4b8f7e5b921e6cd805ef421d04f5f66ba8f06086c"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
]
cryptography = [
{file = "cryptography-3.3.1-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:c366df0401d1ec4e548bebe8f91d55ebcc0ec3137900d214dd7aac8427ef3030"},
{file = "cryptography-3.3.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9f6b0492d111b43de5f70052e24c1f0951cb9e6022188ebcb1cc3a3d301469b0"},
{file = "cryptography-3.3.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a69bd3c68b98298f490e84519b954335154917eaab52cf582fa2c5c7efc6e812"},
{file = "cryptography-3.3.1-cp27-cp27m-win32.whl", hash = "sha256:84ef7a0c10c24a7773163f917f1cb6b4444597efd505a8aed0a22e8c4780f27e"},
{file = "cryptography-3.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:594a1db4511bc4d960571536abe21b4e5c3003e8750ab8365fafce71c5d86901"},
{file = "cryptography-3.3.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0003a52a123602e1acee177dc90dd201f9bb1e73f24a070db7d36c588e8f5c7d"},
{file = "cryptography-3.3.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:83d9d2dfec70364a74f4e7c70ad04d3ca2e6a08b703606993407bf46b97868c5"},
{file = "cryptography-3.3.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:dc42f645f8f3a489c3dd416730a514e7a91a59510ddaadc09d04224c098d3302"},
{file = "cryptography-3.3.1-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:788a3c9942df5e4371c199d10383f44a105d67d401fb4304178020142f020244"},
{file = "cryptography-3.3.1-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:69e836c9e5ff4373ce6d3ab311c1a2eed274793083858d3cd4c7d12ce20d5f9c"},
{file = "cryptography-3.3.1-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:9e21301f7a1e7c03dbea73e8602905a4ebba641547a462b26dd03451e5769e7c"},
{file = "cryptography-3.3.1-cp36-abi3-win32.whl", hash = "sha256:b4890d5fb9b7a23e3bf8abf5a8a7da8e228f1e97dc96b30b95685df840b6914a"},
{file = "cryptography-3.3.1-cp36-abi3-win_amd64.whl", hash = "sha256:0e85aaae861d0485eb5a79d33226dd6248d2a9f133b81532c8f5aae37de10ff7"},
{file = "cryptography-3.3.1.tar.gz", hash = "sha256:7e177e4bea2de937a584b13645cab32f25e3d96fc0bc4a4cf99c27dc77682be6"},
]
dataclasses = [
{file = "dataclasses-0.8-py3-none-any.whl", hash = "sha256:0201d89fa866f68c8ebd9d08ee6ff50c0b255f8ec63a71c16fda7af82bb887bf"},
{file = "dataclasses-0.8.tar.gz", hash = "sha256:8479067f342acf957dc82ec415d355ab5edb7e7646b90dc6e2fd1d96ad084c97"},
]
fido2 = []
flask = [
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
{file = "Flask-1.1.2.tar.gz", hash = "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060"},
]
itsdangerous = [
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
]
jinja2 = [
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
]
markupsafe = [
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
pycparser = [
{file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"},
{file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"},
]
six = [
{file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"},
{file = "six-1.15.0.tar.gz", hash = "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259"},
]
uhid-freebsd = [
{file = "uhid-freebsd-1.2.1.tar.gz", hash = "sha256:13436ea492271b27cd4847fe3168fd467c0f31aa295abd4aeab61e92920fdd58"},
]
werkzeug = [
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
]

View File

@ -0,0 +1,24 @@
[tool.poetry]
name = "fido2-example-server"
version = "0.1.0"
description = ""
authors = ["Dain Nilsson <dain@yubico.com>"]
license = "Apache-2"
packages = [
{ include = "server" },
]
[tool.poetry.dependencies]
python = "^3.6"
Flask = "^1.1.2"
fido2 = {path = "../.."}
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
server = "server.server:main"
server-u2f = "server.server_u2f:main"

View File

View File

@ -35,10 +35,13 @@ Navigate to https://localhost:5000 in a supported web browser.
"""
from __future__ import print_function, absolute_import, unicode_literals
from fido2.webauthn import PublicKeyCredentialRpEntity
from fido2.webauthn import (
PublicKeyCredentialRpEntity,
AttestationObject,
AuthenticatorData,
)
from fido2.client import ClientData
from fido2.server import Fido2Server
from fido2.ctap2 import AttestationObject, AuthenticatorData
from fido2 import cbor
from flask import Flask, session, request, redirect, abort
@ -69,7 +72,6 @@ def register_begin():
"id": b"user_id",
"name": "a_user",
"displayName": "A. User",
"icon": "https://example.com/image.png",
},
credentials,
user_verification="discouraged",
@ -133,6 +135,10 @@ def authenticate_complete():
return cbor.encode({"status": "OK"})
if __name__ == "__main__":
def main():
print(__doc__)
app.run(ssl_context="adhoc", debug=False)
if __name__ == "__main__":
main()

View File

@ -35,10 +35,13 @@ Navigate to https://localhost:5000 in a supported web browser.
"""
from __future__ import print_function, absolute_import, unicode_literals
from fido2.webauthn import PublicKeyCredentialRpEntity
from fido2.webauthn import (
PublicKeyCredentialRpEntity,
AttestationObject,
AuthenticatorData,
)
from fido2.client import ClientData
from fido2.server import U2FFido2Server
from fido2.ctap2 import AttestationObject, AuthenticatorData
from fido2.ctap1 import RegistrationData
from fido2.utils import sha256, websafe_encode
from fido2 import cbor
@ -72,7 +75,6 @@ def register_begin():
"id": b"user_id",
"name": "a_user",
"displayName": "A. User",
"icon": "https://example.com/image.png",
},
credentials,
)
@ -150,7 +152,6 @@ def u2f_begin():
"id": b"user_id",
"name": "a_user",
"displayName": "A. User",
"icon": "https://example.com/image.png",
},
credentials,
)
@ -179,6 +180,10 @@ def u2f_complete():
return cbor.encode({"status": "OK"})
if __name__ == "__main__":
def main():
print(__doc__)
app.run(ssl_context="adhoc", debug=False)
if __name__ == "__main__":
main()

View File

@ -25,17 +25,5 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import abc
import six
if six.PY2:
@six.add_metaclass(abc.ABCMeta)
class ABC(object):
pass
abc.ABC = ABC
__version__ = "0.9.2-dev0"

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
from .base import ( # noqa: F401
Attestation,
NoneAttestation,

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .base import (
Attestation,
AttestationType,

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .base import (
Attestation,
AttestationType,

View File

@ -30,8 +30,9 @@ from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import padding, ec, rsa
from cryptography.exceptions import InvalidSignature as _InvalidSignature
from collections import namedtuple
from dataclasses import dataclass
from functools import wraps
from typing import List, Type
import abc
@ -63,9 +64,6 @@ class UnsupportedType(InvalidAttestation):
self.fmt = fmt
AttestationResult = namedtuple("AttestationResult", ["attestation_type", "trust_path"])
class AttestationType(Enum):
BASIC = 1
SELF = 2
@ -74,6 +72,12 @@ class AttestationType(Enum):
NONE = 0
@dataclass
class AttestationResult:
attestation_type: AttestationType
trust_path: List[bytes]
def catch_builtins(f):
@wraps(f)
def inner(*args, **kwargs):
@ -86,7 +90,7 @@ def catch_builtins(f):
@catch_builtins
def verify_x509_chain(chain):
def verify_x509_chain(chain: List[bytes]) -> None:
certs = [x509.load_der_x509_certificate(der, default_backend()) for der in chain]
cert = certs.pop(0)
while certs:
@ -113,14 +117,16 @@ def verify_x509_chain(chain):
class Attestation(abc.ABC):
@abc.abstractmethod
def verify(self, statement, auth_data, client_data_hash):
def verify(
self, statement, auth_data, client_data_hash: bytes
) -> AttestationResult:
"""Verifies attestation statement.
:return: An AttestationResult if successful.
"""
@staticmethod
def for_type(fmt):
def for_type(fmt: str) -> Type["Attestation"]:
for cls in Attestation.__subclasses__():
if getattr(cls, "FORMAT", None) == fmt:
return cls

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .base import (
Attestation,
AttestationType,

View File

@ -27,8 +27,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .base import (
Attestation,
AttestationType,
@ -42,25 +40,14 @@ from ..cose import CoseKey
from ..utils import bytes2int, ByteBuffer
from enum import IntEnum
from collections import namedtuple
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa, ec
from cryptography.hazmat.primitives import hashes
from cryptography import x509
from cryptography.exceptions import InvalidSignature as _InvalidSignature
from dataclasses import dataclass
import struct
import six
if six.PY2:
# Workaround for int max size on Python 2.
from enum import Enum
class _LongEnum(long, Enum): # noqa F821
"""Like IntEnum, but supports larger values"""
IntEnum = _LongEnum # Use instead of IntEnum # noqa F811
TPM_ALG_NULL = 0x0010
@ -100,10 +87,13 @@ class TpmAlgHash(IntEnum):
)
TpmsCertifyInfo = namedtuple("TpmsCertifyInfo", "name qualified_name")
@dataclass
class TpmsCertifyInfo:
name: bytes
qualified_name: bytes
class TpmAttestationFormat(object):
class TpmAttestationFormat:
"""the signature data is defined by [TPMv2-Part2] Section 10.12.8 (TPMS_ATTEST)
as:
TPM_GENERATED_VALUE (0xff544347 aka "\xffTCG")
@ -199,7 +189,7 @@ class TpmAttestationFormat(object):
)
class TpmsRsaParms(object):
class TpmsRsaParms:
"""Parse TPMS_RSA_PARMS struct
See:
@ -349,7 +339,7 @@ class TpmiAlgKdf(IntEnum):
KDF1_SP800_108 = 0x0022
class TpmsEccParms(object):
class TpmsEccParms:
@classmethod
def parse(cls, reader):
symmetric = reader.unpack("!H")
@ -381,7 +371,7 @@ class TpmsEccParms(object):
)
class TpmsEccPoint(object):
class TpmsEccPoint:
"""TPMS_ECC_POINT
https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf
Section 11.2.5.2
@ -402,7 +392,7 @@ class TpmsEccPoint(object):
return "<TpmsEccPoint" " x={self.x}" " y={self.y}" ">".format(self=self)
class TpmPublicFormat(object):
class TpmPublicFormat:
"""the public area structure is defined by [TPMv2-Part2] Section 12.2.4 (TPMT_PUBLIC)
as:
TPMI_ALG_PUBLIC - type

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .base import (
Attestation,
AttestationType,

View File

@ -32,17 +32,20 @@ required for FIDO 2 CTAP.
"""
import struct
import six
from typing import Any, Tuple, Union, Sequence, Mapping, Type, Callable
def dump_int(data, mt=0):
CborType = Union[int, bool, str, bytes, Sequence[Any], Mapping[Any, Any]]
def dump_int(data: int, mt: int = 0) -> bytes:
if data < 0:
mt = 1
data = -1 - data
mt = mt << 5
if data <= 23:
args = (">B", mt | data)
args: Any = (">B", mt | data)
elif data <= 0xFF:
args = (">BB", mt | 24, data)
elif data <= 0xFFFF:
@ -54,56 +57,56 @@ def dump_int(data, mt=0):
return struct.pack(*args)
def dump_bool(data):
def dump_bool(data: bool) -> bytes:
return b"\xf5" if data else b"\xf4"
def dump_list(data):
def dump_list(data: Sequence[CborType]) -> bytes:
return dump_int(len(data), mt=4) + b"".join([encode(x) for x in data])
def _sort_keys(entry):
key = entry[0]
return six.indexbytes(key, 0), len(key), key
return key[0], len(key), key
def dump_dict(data):
def dump_dict(data: Mapping[CborType, CborType]) -> bytes:
items = [(encode(k), encode(v)) for k, v in data.items()]
items.sort(key=_sort_keys)
return dump_int(len(items), mt=5) + b"".join([k + v for (k, v) in items])
def dump_bytes(data):
def dump_bytes(data: bytes) -> bytes:
return dump_int(len(data), mt=2) + data
def dump_text(data):
def dump_text(data: str) -> bytes:
data_bytes = data.encode("utf8")
return dump_int(len(data_bytes), mt=3) + data_bytes
_SERIALIZERS = [
_SERIALIZERS: Sequence[Tuple[Type, Callable[[Any], bytes]]] = [
(bool, dump_bool),
(six.integer_types, dump_int),
(dict, dump_dict),
(list, dump_list),
(six.text_type, dump_text),
(six.binary_type, dump_bytes),
(int, dump_int),
(str, dump_text),
(bytes, dump_bytes),
(Mapping, dump_dict),
(Sequence, dump_list),
]
def encode(data):
def encode(data: CborType) -> bytes:
for k, v in _SERIALIZERS:
if isinstance(data, k):
return v(data)
raise ValueError("Unsupported value: {}".format(data))
raise ValueError("Unsupported value: %r" % data)
def load_int(ai, data):
def load_int(ai: int, data: bytes) -> Tuple[int, bytes]:
if ai < 24:
return ai, data
elif ai == 24:
return six.indexbytes(data, 0), data[1:]
return data[0], data[1:]
elif ai == 25:
return struct.unpack_from(">H", data)[0], data[2:]
elif ai == 26:
@ -113,26 +116,26 @@ def load_int(ai, data):
raise ValueError("Invalid additional information")
def load_nint(ai, data):
def load_nint(ai: int, data: bytes) -> Tuple[int, bytes]:
val, rest = load_int(ai, data)
return -1 - val, rest
def load_bool(ai, data):
def load_bool(ai: int, data: bytes) -> Tuple[bool, bytes]:
return ai == 21, data
def load_bytes(ai, data):
def load_bytes(ai: int, data: bytes) -> Tuple[bytes, bytes]:
l, data = load_int(ai, data)
return data[:l], data[l:]
def load_text(ai, data):
def load_text(ai: int, data: bytes) -> Tuple[str, bytes]:
enc, rest = load_bytes(ai, data)
return enc.decode("utf8"), rest
def load_array(ai, data):
def load_array(ai: int, data: bytes) -> Tuple[Sequence[CborType], bytes]:
l, data = load_int(ai, data)
values = []
for i in range(l):
@ -141,7 +144,7 @@ def load_array(ai, data):
return values, data
def load_map(ai, data):
def load_map(ai: int, data: bytes) -> Tuple[Mapping[CborType, CborType], bytes]:
l, data = load_int(ai, data)
values = {}
for i in range(l):
@ -162,12 +165,12 @@ _DESERIALIZERS = {
}
def decode_from(data):
fb = six.indexbytes(data, 0)
def decode_from(data: bytes) -> Tuple[Any, bytes]:
fb = data[0]
return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:])
def decode(data):
def decode(data) -> CborType:
value, rest = decode_from(data)
if rest != b"":
raise ValueError("Extraneous data")

View File

@ -25,20 +25,14 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals, division
from .hid import STATUS
from .ctap import CtapError
from .ctap import CtapDevice, CtapError
from .ctap1 import Ctap1, APDU, ApduError
from .ctap2 import (
Ctap2,
AttestationObject,
AssertionResponse,
Info,
ClientPin,
)
from .ctap2 import Ctap2, AssertionResponse, Info
from .ctap2.pin import ClientPin
from .ctap2.extensions import Ctap2Extension
from .webauthn import (
AttestationObject,
PublicKeyCredentialCreationOptions,
PublicKeyCredentialRequestOptions,
AuthenticatorSelectionCriteria,
@ -51,38 +45,38 @@ from .rpid import verify_rp_id, verify_app_id
from .utils import sha256, websafe_decode, websafe_encode
from enum import Enum, IntEnum, unique
from threading import Timer, Event
from typing import Type, Any, Union, Callable, Optional, Mapping, Sequence
import json
import six
import platform
class ClientData(bytes):
def __init__(self, _):
super(ClientData, self).__init__()
self.data = json.loads(self.decode())
self._data = json.loads(self.decode())
def get(self, key):
return self.data[key]
def get(self, key: str) -> Any:
return self._data[key]
@property
def challenge(self):
def challenge(self) -> bytes:
return websafe_decode(self.get("challenge"))
@property
def b64(self):
def b64(self) -> str:
return websafe_encode(self)
@property
def hash(self):
def hash(self) -> bytes:
return sha256(self)
@classmethod
def build(cls, **kwargs):
def build(cls, **kwargs) -> "ClientData":
return cls(json.dumps(kwargs).encode())
@classmethod
def from_b64(cls, data):
def from_b64(cls, data: Union[str, bytes]) -> "ClientData":
return cls(websafe_decode(data))
def __repr__(self):
@ -180,12 +174,12 @@ def _call_polling(poll_delay, event, on_keepalive, func, *args, **kwargs):
@unique
class U2F_TYPE(six.text_type, Enum):
class U2F_TYPE(str, Enum):
REGISTER = "navigator.id.finishEnrollment"
SIGN = "navigator.id.getAssertion"
class U2fClient(object):
class U2fClient:
"""U2F-like client implementation.
The client allows registration and authentication of U2F credentials against
@ -196,7 +190,12 @@ class U2fClient(object):
:param verify: Function to verify an APP ID for a given origin.
"""
def __init__(self, device, origin, verify=verify_app_id):
def __init__(
self,
device: CtapDevice,
origin: str,
verify: Callable[[str, str], bool] = verify_app_id,
):
self.poll_delay = 0.25
self.ctap = Ctap1(device)
self.origin = origin
@ -211,8 +210,13 @@ class U2fClient(object):
raise ClientError.ERR.BAD_REQUEST()
def register(
self, app_id, register_requests, registered_keys, event=None, on_keepalive=None
):
self,
app_id: str,
register_requests: Sequence[Mapping[str, str]],
registered_keys: Sequence[Mapping[str, Any]],
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
) -> Mapping[str, str]:
self._verify_app_id(app_id)
version = self.ctap.get_version()
@ -256,7 +260,14 @@ class U2fClient(object):
return {"registrationData": reg_data.b64, "clientData": client_data.b64}
def sign(self, app_id, challenge, registered_keys, event=None, on_keepalive=None):
def sign(
self,
app_id: str,
challenge: bytes,
registered_keys: Sequence[Mapping[str, Any]],
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
) -> Mapping[str, str]:
client_data = ClientData.build(
typ=U2F_TYPE.SIGN, challenge=challenge, origin=self.origin
)
@ -292,13 +303,13 @@ class U2fClient(object):
@unique
class WEBAUTHN_TYPE(six.text_type, Enum):
class WEBAUTHN_TYPE(str, Enum):
MAKE_CREDENTIAL = "webauthn.create"
GET_ASSERTION = "webauthn.get"
class _BaseClient(object):
def __init__(self, origin, verify):
class _BaseClient:
def __init__(self, origin: str, verify: Callable[[str, str], bool]):
self.origin = origin
self._verify = verify
@ -319,25 +330,27 @@ class _BaseClient(object):
)
class AssertionSelection(object):
class AssertionSelection:
"""GetAssertion result holding one or more assertions.
Since multiple assertions may be retured by Fido2Client.get_assertion, this result
is returned which can be used to select a specific response to get.
"""
def __init__(self, client_data, assertions):
def __init__(
self, client_data: ClientData, assertions: Sequence[AssertionResponse]
):
self._client_data = client_data
self._assertions = assertions
def get_assertions(self):
def get_assertions(self) -> Sequence[AssertionResponse]:
"""Get the raw AssertionResponses available to inspect before selecting one."""
return self._assertions
def _get_extension_results(self, assertion):
return {}
return {} # Not implemented
def get_response(self, index):
def get_response(self, index: int) -> AuthenticatorAssertionResponse:
"""Get a single response."""
assertion = self._assertions[index]
@ -352,7 +365,12 @@ class AssertionSelection(object):
class Fido2ClientAssertionSelection(AssertionSelection):
def __init__(self, client_data, assertions, extensions):
def __init__(
self,
client_data: ClientData,
assertions: Sequence[AssertionResponse],
extensions: Sequence[Ctap2Extension],
):
super(Fido2ClientAssertionSelection, self).__init__(client_data, assertions)
self._extensions = extensions
@ -373,7 +391,7 @@ def _default_extensions():
return [cls for cls in Ctap2Extension.__subclasses__() if hasattr(cls, "NAME")]
_CTAP1_INFO = Info.create(["U2F_V2"])
_CTAP1_INFO = Info.create(versions=["U2F_V2"], aaguid=b"\0" * 32)
class Fido2Client(_BaseClient):
@ -387,7 +405,13 @@ class Fido2Client(_BaseClient):
:param verify: Function to verify an RP ID for a given origin.
"""
def __init__(self, device, origin, verify=verify_rp_id, extension_types=None):
def __init__(
self,
device: CtapDevice,
origin: str,
verify: Callable[[str, str], bool] = verify_rp_id,
extension_types: Optional[Type[Ctap2Extension]] = None,
):
super(Fido2Client, self).__init__(origin, verify)
self.extensions = extension_types or _default_extensions()
@ -396,7 +420,7 @@ class Fido2Client(_BaseClient):
self.ctap2 = Ctap2(device)
self.info = self.ctap2.info
try:
self.client_pin = ClientPin(self.ctap2)
self.client_pin: Optional[ClientPin] = ClientPin(self.ctap2)
except ValueError:
self.client_pin = None
self._do_make_credential = self._ctap2_make_credential
@ -481,7 +505,13 @@ class Fido2Client(_BaseClient):
internal_uv = True
return pin_protocol, pin_auth, internal_uv
def make_credential(self, options, **kwargs):
def make_credential(
self,
options: PublicKeyCredentialCreationOptions,
pin: Optional[str] = None,
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
):
"""Creates a credential.
:param options: PublicKeyCredentialCreationOptions data.
@ -491,8 +521,7 @@ class Fido2Client(_BaseClient):
"""
options = PublicKeyCredentialCreationOptions._wrap(options)
pin = kwargs.get("pin")
event = kwargs.get("event", Event())
event = event or Event()
if options.timeout:
timer = Timer(options.timeout / 1000, event.set)
timer.daemon = True
@ -507,7 +536,7 @@ class Fido2Client(_BaseClient):
selection = options.authenticator_selection or AuthenticatorSelectionCriteria()
try:
att_obj, extension_outputs = self._do_make_credential(
att_resp, extension_outputs = self._do_make_credential(
client_data,
options.rp,
options.user,
@ -518,11 +547,13 @@ class Fido2Client(_BaseClient):
selection.user_verification,
pin,
event,
kwargs.get("on_keepalive"),
on_keepalive,
)
return AuthenticatorAttestationResponse(
client_data,
att_obj,
AttestationObject.create(
att_resp.fmt, att_resp.auth_data, att_resp.att_stmt
),
extension_outputs,
)
except CtapError as e:
@ -665,7 +696,13 @@ class Fido2Client(_BaseClient):
{},
)
def get_assertion(self, options, **kwargs):
def get_assertion(
self,
options: PublicKeyCredentialRequestOptions,
pin: Optional[str] = None,
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
):
"""Get an assertion.
:param options: PublicKeyCredentialRequestOptions data.
@ -675,8 +712,7 @@ class Fido2Client(_BaseClient):
"""
options = PublicKeyCredentialRequestOptions._wrap(options)
pin = kwargs.get("pin")
event = kwargs.get("event", Event())
event = event or Event()
if options.timeout:
timer = Timer(options.timeout / 1000, event.set)
timer.daemon = True
@ -697,7 +733,7 @@ class Fido2Client(_BaseClient):
options.user_verification,
pin,
event,
kwargs.get("on_keepalive"),
on_keepalive,
)
return Fido2ClientAssertionSelection(
client_data,
@ -804,7 +840,8 @@ class Fido2Client(_BaseClient):
raise ClientError.ERR.DEVICE_INELIGIBLE()
_WIN_INFO = Info.create(["U2F_V2", "FIDO_2_0"])
_WIN_INFO = Info.create(versions=["U2F_V2", "FIDO_2_0"], aaguid=b"\0" * 32)
foo = _WIN_INFO.aaguid
if platform.system().lower() == "windows":
try:
@ -833,16 +870,21 @@ class WindowsClient(_BaseClient):
:param ctypes.wintypes.HWND handle: (optional) Window reference to use.
"""
def __init__(self, origin, verify=verify_rp_id, handle=None):
def __init__(
self,
origin: str,
verify: Callable[[str, str], bool] = verify_rp_id,
handle=None,
):
super(WindowsClient, self).__init__(origin, verify)
self.api = WinAPI(handle)
@property
def info(self):
def info(self) -> Info:
return _WIN_INFO
@staticmethod
def is_available():
def is_available() -> bool:
return platform.system().lower() == "windows" and WinAPI.version > 0
def make_credential(self, options, **kwargs):

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .utils import bytes2int, int2bytes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
from enum import IntEnum, unique
import abc
@ -43,6 +41,11 @@ class CtapDevice(abc.ABC):
list_devices, which should return a generator over discoverable devices.
"""
@property
@abc.abstractmethod
def capabilities(self) -> int:
"""Get device capabilities"""
@abc.abstractmethod
def call(self, cmd, data=b"", event=None, on_keepalive=None):
"""Sends a command to the authenticator, and reads the response.

View File

@ -25,16 +25,12 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .hid import CTAPHID
from .utils import websafe_encode, websafe_decode, bytes2int, ByteBuffer
from .cose import ES256
from .attestation import FidoU2FAttestation
from enum import IntEnum, unique
from binascii import b2a_hex
import struct
import six
@unique
@ -79,15 +75,15 @@ class RegistrationData(bytes):
def __init__(self, _):
super(RegistrationData, self).__init__()
if six.indexbytes(self, 0) != 0x05:
if self[0] != 0x05:
raise ValueError("Reserved byte != 0x05")
self.public_key = self[1:66]
kh_len = six.indexbytes(self, 66)
kh_len = self[66]
self.key_handle = self[67 : 67 + kh_len]
cert_offs = 67 + kh_len
cert_len = six.indexbytes(self, cert_offs + 1)
cert_len = self[cert_offs + 1]
if cert_len > 0x80:
n_bytes = cert_len - 0x80
cert_len = (
@ -123,7 +119,7 @@ class RegistrationData(bytes):
"RegistrationData(public_key: h'%s', key_handle: h'%s', "
"certificate: h'%s', signature: h'%s')"
) % tuple(
b2a_hex(x).decode()
(x).hex()
for x in (
self.public_key,
self.key_handle,
@ -181,7 +177,7 @@ class SignatureData(bytes):
def __repr__(self):
return (
"SignatureData(user_presence: 0x%02x, counter: %d, " "signature: h'%s'"
) % (self.user_presence, self.counter, b2a_hex(self.signature))
) % (self.user_presence, self.counter, self.signature.hex())
def __str__(self):
return "%r" % self
@ -196,7 +192,7 @@ class SignatureData(bytes):
return cls(websafe_decode(data))
class Ctap1(object):
class Ctap1:
"""Implementation of the CTAP1 specification.
:param device: A CtapHidDevice handle supporting CTAP1.

View File

@ -25,14 +25,10 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
from .base import ( # noqa
Ctap2,
Info,
AuthenticatorData,
AttestedCredentialData,
AttestationObject,
AttestationResponse,
AssertionResponse,
)
@ -41,6 +37,3 @@ from .credman import CredentialManagement # noqa
from .bio import FPBioEnrollment, CaptureError # noqa
from .blob import LargeBlobs # noqa
from .config import Config # noqa
# Alias for compatibility, this will be going away
CTAP2 = Ctap2

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .. import cbor
from ..ctap import CtapError
@ -34,7 +32,7 @@ from enum import IntEnum, unique
import struct
class BioEnrollment(object):
class BioEnrollment:
@unique
class RESULT(IntEnum):
MODALITY = 0x01
@ -90,7 +88,7 @@ class CaptureError(Exception):
super(CaptureError, self).__init__("Fingerprint capture error: %s" % code)
class FPEnrollmentContext(object):
class FPEnrollmentContext:
"""Helper object to perform fingerprint enrollment.
:param bio: An instance of FPBioEnrollment.

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .. import cbor
from ..utils import sha256
@ -76,7 +74,7 @@ def _lb_unpack(key, entry):
raise ValueError("Wrong key")
class LargeBlobs(object):
class LargeBlobs:
"""Implementation of the CTAP2.1 Large Blobs API.
Getting a largeBlobKey for a credential is done via the LargeBlobKey extension.

View File

@ -25,15 +25,13 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .. import cbor
from enum import IntEnum, unique
import struct
class Config(object):
class Config:
"""Implementation of the CTAP2.1 Authenticator Config API.
:param ctap: An instance of a CTAP2 object.

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .. import cbor
from ..ctap import CtapError
@ -34,7 +32,7 @@ from enum import IntEnum, unique
import struct
class CredentialManagement(object):
class CredentialManagement:
"""Implementation of a draft specification of the Credential Management API.
WARNING: This specification is not final and this class is likely to change.

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .pin import ClientPin
from enum import Enum, unique
import abc

View File

@ -25,9 +25,9 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from ..utils import sha256, hmac_sha256, bytes2int, int2bytes
from ..cose import CoseKey
from .base import Ctap2
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
@ -35,24 +35,53 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from enum import IntEnum, unique
import six
from enum import IntEnum, IntFlag, unique
from typing import ClassVar, Optional, Mapping, Tuple, Any
import abc
import os
def _pad_pin(pin):
if not isinstance(pin, six.string_types):
raise ValueError("PIN of wrong type, expecting %s" % six.string_types)
def _pad_pin(pin: str) -> bytes:
if not isinstance(pin, str):
raise ValueError("PIN of wrong type, expecting %s" % str)
if len(pin) < 4:
raise ValueError("PIN must be >= 4 characters")
pin = pin.encode("utf8").ljust(64, b"\0")
pin += b"\0" * (-(len(pin) - 16) % 16)
if len(pin) > 255:
pin_padded = pin.encode().ljust(64, b"\0")
pin_padded += b"\0" * (-(len(pin_padded) - 16) % 16)
if len(pin_padded) > 255:
raise ValueError("PIN must be <= 255 bytes")
return pin
return pin_padded
class PinProtocolV1(object):
class PinProtocol(abc.ABC):
VERSION: ClassVar[int]
@abc.abstractmethod
def encapsulate(self, peer_cose_key: CoseKey) -> Tuple[Mapping[int, Any], bytes]:
"""Generates an encapsulation of the public key.
Returns the message to transmit and the shared secret.
"""
@abc.abstractmethod
def encrypt(self, key: bytes, plaintext: bytes) -> bytes:
"""Encrypts data"""
@abc.abstractmethod
def decrypt(self, key: bytes, ciphertext: bytes) -> bytes:
"""Decrypts encrypted data"""
@abc.abstractmethod
def authenticate(self, key: bytes, message: bytes) -> bytes:
"""Computes a MAC of the given message."""
@abc.abstractmethod
def validate_token(self, token: bytes) -> bytes:
"""Validates that a token is well-formed.
Returns the token, or if invalid, raises a ValueError.
"""
class PinProtocolV1(PinProtocol):
"""Implementation of the CTAP2 PIN/UV protocol v1.
:param ctap: An instance of a CTAP2 object.
@ -63,7 +92,7 @@ class PinProtocolV1(object):
VERSION = 1
IV = b"\x00" * 16
def kdf(self, z):
def kdf(self, z) -> bytes:
return sha256(z)
def encapsulate(self, peer_cose_key):
@ -167,7 +196,7 @@ class PinProtocolV2(PinProtocolV1):
return token
class ClientPin(object):
class ClientPin:
"""Implementation of the CTAP2 Client PIN API.
:param ctap: An instance of a CTAP2 object.
@ -176,10 +205,7 @@ class ClientPin(object):
will be used.
"""
PROTOCOLS = [
PinProtocolV2,
PinProtocolV1,
]
PROTOCOLS = [PinProtocolV2, PinProtocolV1]
@unique
class CMD(IntEnum):
@ -201,7 +227,7 @@ class ClientPin(object):
UV_RETRIES = 0x05
@unique
class PERMISSION(IntEnum):
class PERMISSION(IntFlag):
MAKE_CREDENTIAL = 0x01
GET_ASSERTION = 0x02
CREDENTIAL_MGMT = 0x04
@ -213,7 +239,7 @@ class ClientPin(object):
def is_supported(info):
return "clientPin" in info.options
def __init__(self, ctap, protocol=None):
def __init__(self, ctap: Ctap2, protocol: Optional[PinProtocol] = None):
if not self.is_supported(ctap.info):
raise ValueError("Authenticator does not support ClientPin")
@ -221,11 +247,12 @@ class ClientPin(object):
if protocol is None:
for proto in ClientPin.PROTOCOLS:
if proto.VERSION in ctap.info.pin_uv_protocols:
protocol = proto()
self.protocol: PinProtocol = proto()
break
else:
raise ValueError("No compatible PIN/UV protocols supported!")
self.protocol = protocol
else:
self.protocol = protocol
self._supports_permissions = ctap.info.options.get("pinUvAuthToken")
def _get_shared_secret(self):
@ -236,7 +263,12 @@ class ClientPin(object):
return self.protocol.encapsulate(pk)
def get_pin_token(self, pin, permissions=None, permissions_rpid=None):
def get_pin_token(
self,
pin: str,
permissions: Optional["ClientPin.PERMISSION"] = None,
permissions_rpid: Optional[str] = None,
) -> bytes:
"""Get a PIN/UV token from the authenticator using PIN.
:param pin: The PIN of the authenticator.

View File

@ -25,13 +25,9 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
from ..ctap import CtapDevice, CtapError, STATUS
from threading import Event
from enum import IntEnum, unique
from binascii import b2a_hex as _b2a_hex
import struct
import sys
import os
@ -54,10 +50,6 @@ else:
raise Exception("Unsupported platform")
def b2a_hex(data):
return _b2a_hex(data).decode("ascii")
list_descriptors = backend.list_descriptors
get_descriptor = backend.get_descriptor
open_connection = backend.open_connection
@ -163,7 +155,7 @@ class CtapHidDevice(CtapDevice):
size = min(len(remaining), packet_size - len(header))
body, remaining = remaining[:size], remaining[size:]
packet = header + body
logger.debug("SEND: %s", b2a_hex(packet))
logger.debug("SEND: %s", packet.hex())
self._connection.write_packet(packet.ljust(packet_size, b"\0"))
header = struct.pack(">IB", self._channel_id, 0x7F & seq)
seq += 1
@ -183,7 +175,7 @@ class CtapHidDevice(CtapDevice):
self._connection.write_packet(packet)
recv = self._connection.read_packet()
logger.debug("RECV: %s", b2a_hex(recv))
logger.debug("RECV: %s", recv.hex())
r_channel = struct.unpack_from(">I", recv)[0]
recv = recv[4:]

View File

@ -25,10 +25,8 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import
from collections import namedtuple
from dataclasses import dataclass
from typing import Tuple, Union, Optional
import struct
import abc
import os
@ -37,34 +35,28 @@ FIDO_USAGE_PAGE = 0xF1D0
FIDO_USAGE = 0x1
class HidDescriptor(
namedtuple(
"HidDescriptor",
[
"path",
"vid",
"pid",
"report_size_in",
"report_size_out",
"product_name",
"serial_number",
],
)
):
__slots__ = ()
@dataclass
class HidDescriptor:
path: Union[str, bytes]
vid: int
pid: int
report_size_in: int
report_size_out: int
product_name: Optional[str]
serial_number: Optional[str]
class CtapHidConnection(abc.ABC):
@abc.abstractmethod
def read_packet(self):
def read_packet(self) -> bytes:
"""Reads a CTAP HID packet"""
@abc.abstractmethod
def write_packet(self, data):
def write_packet(self, data: bytes) -> None:
"""Writes a CTAP HID packet"""
@abc.abstractmethod
def close(self):
def close(self) -> None:
"""Closes the connection"""
@ -97,7 +89,7 @@ USAGE_PAGE = 0x04
USAGE = 0x08
def parse_report_descriptor(data):
def parse_report_descriptor(data: bytes) -> Tuple[int, int]:
# Parse report descriptor data
usage, usage_page = None, None
max_input_size, max_output_size = None, None
@ -136,6 +128,6 @@ def parse_report_descriptor(data):
report_size = value
if not remaining and usage_page == FIDO_USAGE_PAGE and usage == FIDO_USAGE:
return max_input_size, max_output_size
return max_input_size, max_output_size # type: ignore
raise ValueError("Not a FIDO device")

View File

@ -15,9 +15,6 @@
# Modified work Copyright 2020 Yubico AB. All Rights Reserved.
# This file, with modifications, is licensed under the above Apache License.
from __future__ import absolute_import
from ctypes.util import find_library
import ctypes
import glob
@ -145,7 +142,11 @@ def list_descriptors():
serial = (dev["serial_number"] if "serial_number" in dev else None) or None
descriptors.append(
_read_descriptor(
dev["vendor_id"], dev["product_id"], name, serial, dev["path"],
dev["vendor_id"],
dev["product_id"],
name,
serial,
dev["path"],
)
)
logger.debug("Found CTAP device: %s", dev["path"])

View File

@ -15,9 +15,6 @@
# Modified work Copyright 2020 Yubico AB. All Rights Reserved.
# This file, with modifications, is licensed under the above Apache License.
from __future__ import absolute_import
from .base import HidDescriptor, FileCtapHidConnection, parse_report_descriptor
import glob

View File

@ -15,15 +15,12 @@
# Modified work Copyright 2020 Yubico AB. All Rights Reserved.
# This file, with modifications, is licensed under the above Apache License.
from __future__ import absolute_import
from .base import HidDescriptor, CtapHidConnection, FIDO_USAGE_PAGE, FIDO_USAGE
import ctypes
import ctypes.util
import threading
from six.moves.queue import Queue, Empty
from queue import Queue, Empty
import logging

View File

@ -15,9 +15,6 @@
# Modified work Copyright 2020 Yubico AB. All Rights Reserved.
# This file, with modifications, is licensed under the above Apache License.
from __future__ import absolute_import
from .base import HidDescriptor, CtapHidConnection, FIDO_USAGE_PAGE, FIDO_USAGE
import ctypes

View File

@ -26,18 +26,14 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .ctap import CtapDevice, CtapError, STATUS
from .hid import CAPABILITY, CTAPHID
from smartcard import System
from smartcard.pcsc.PCSCExceptions import ListReadersException
from smartcard.pcsc.PCSCContext import PCSCContext
from binascii import b2a_hex as _b2a_hex
from threading import Event
import struct
import six
import logging
@ -50,10 +46,6 @@ SW1_MORE_DATA = 0x61
logger = logging.getLogger(__name__)
def b2a_hex(data):
return _b2a_hex(data).decode("ascii")
class CtapPcscDevice(CtapDevice):
"""
CtapDevice implementation using pyscard (PCSC).
@ -112,10 +104,10 @@ class CtapPcscDevice(CtapDevice):
:return: byte string. response from card
"""
logger.debug("SEND: %s", b2a_hex(apdu))
resp, sw1, sw2 = self._conn.transmit(list(six.iterbytes(apdu)), protocol)
response = bytes(bytearray(resp))
logger.debug("RECV: %s SW=%04X", b2a_hex(response), sw1 << 8 + sw2)
logger.debug("SEND: %s", apdu.hex())
resp, sw1, sw2 = self._conn.transmit(list(apdu), protocol)
response = bytes(resp)
logger.debug("RECV: %s SW=%04X", response.hex(), sw1 << 8 + sw2)
return response, sw1, sw2
@ -127,10 +119,10 @@ class CtapPcscDevice(CtapDevice):
:return: byte string. response
"""
logger.debug("control %s", b2a_hex(control_data))
response = self._conn.control(control_code, list(six.iterbytes(control_data)))
response = bytes(bytearray(response))
logger.debug("response %s", b2a_hex(response))
logger.debug("control %s", control_data.hex())
response = self._conn.control(control_code, list(control_data))
response = bytes(response)
logger.debug("response %s", response.hex())
return response
@ -165,7 +157,7 @@ class CtapPcscDevice(CtapDevice):
return resp, sw1, sw2
def _call_apdu(self, apdu):
if len(apdu) >= 7 and six.indexbytes(apdu, 4) == 0:
if len(apdu) >= 7 and apdu[4] == 0:
# Extended APDU
data_len = struct.unpack("!H", apdu[5:7])[0]
data = apdu[7 : 7 + data_len]
@ -173,9 +165,9 @@ class CtapPcscDevice(CtapDevice):
data = b""
else:
# Short APDU
data_len = six.indexbytes(apdu, 4)
data_len = apdu[4]
data = apdu[5 : 5 + data_len]
(cla, ins, p1, p2) = six.iterbytes(apdu[:4])
(cla, ins, p1, p2) = apdu[:4]
resp, sw1, sw2 = self._chain_apdus(cla, ins, p1, p2, data)
return resp + struct.pack("!BB", sw1, sw2)
@ -188,7 +180,7 @@ class CtapPcscDevice(CtapDevice):
while not event.is_set():
while (sw1, sw2) == SW_UPDATE:
ka_status = six.indexbytes(resp, 0)
ka_status = resp[0]
if on_keepalive and last_ka != ka_status:
try:
ka_status = STATUS(ka_status)

View File

@ -35,11 +35,8 @@ Advanced APP_ID values pointing to JSON files containing valid facets are not
supported by this implementation.
"""
from __future__ import absolute_import, unicode_literals
import os
import six
from six.moves.urllib.parse import urlparse
from urllib.parse import urlparse
tld_fname = os.path.join(os.path.dirname(__file__), "public_suffix_list.dat")
@ -51,19 +48,15 @@ with open(tld_fname, "rb") as f:
]
def verify_rp_id(rp_id, origin):
def verify_rp_id(rp_id: str, origin: str) -> bool:
"""Checks if a Webauthn RP ID is usable for a given origin.
:param rp_id: The RP ID to validate.
:param origin: The origin of the request.
:return: True if the RP ID is usable by the origin, False if not.
"""
if isinstance(rp_id, six.binary_type):
rp_id = rp_id.decode()
if not rp_id:
return False
if isinstance(origin, six.binary_type):
origin = origin.decode()
url = urlparse(origin)
if url.scheme != "https":
@ -71,21 +64,22 @@ def verify_rp_id(rp_id, origin):
host = url.hostname
if host == rp_id:
return True
if host.endswith("." + rp_id) and rp_id not in suffixes:
if host and host.endswith("." + rp_id) and rp_id not in suffixes:
return True
return False
def verify_app_id(app_id, origin):
def verify_app_id(app_id: str, origin: str) -> bool:
"""Checks if a FIDO U2F App ID is usable for a given origin.
:param app_id: The App ID to validate.
:param origin: The origin of the request.
:return: True if the App ID is usable by the origin, False if not.
"""
if isinstance(app_id, six.binary_type):
app_id = app_id.decode()
url = urlparse(app_id)
if url.scheme != "https":
return False
return verify_rp_id(url.hostname, origin)
hostname = url.hostname
if not hostname:
return False
return verify_rp_id(hostname, origin)

View File

@ -25,11 +25,8 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .rpid import verify_rp_id, verify_app_id
from .cose import CoseKey
from .ctap2 import AttestedCredentialData
from .client import WEBAUTHN_TYPE
from .attestation import (
Attestation,
@ -40,6 +37,7 @@ from .attestation import (
)
from .utils import websafe_encode, websafe_decode
from .webauthn import (
AttestedCredentialData,
AttestationConveyancePreference,
PublicKeyCredentialRpEntity,
AuthenticatorSelectionCriteria,
@ -165,7 +163,7 @@ class AttestationVerifier(abc.ABC):
self.verify_attestation(*args)
class Fido2Server(object):
class Fido2Server:
"""FIDO2 server.
:param rp: Relying party data as `PublicKeyCredentialRpEntity` instance.
@ -198,6 +196,7 @@ class Fido2Server(object):
user_verification=None,
authenticator_attachment=None,
challenge=None,
extensions=None,
):
"""Return a PublicKeyCredentialCreationOptions registration object and
the internal state dictionary that needs to be passed as is to the
@ -235,6 +234,7 @@ class Fido2Server(object):
if any((authenticator_attachment, resident_key, user_verification))
else None,
self.attestation,
extensions,
)
},
state,
@ -280,7 +280,7 @@ class Fido2Server(object):
return attestation_object.auth_data
def authenticate_begin(
self, credentials=None, user_verification=None, challenge=None
self, credentials=None, user_verification=None, challenge=None, extensions=None
):
"""Return a PublicKeyCredentialRequestOptions assertion object and the internal
state dictionary that needs to be passed as is to the corresponding
@ -304,6 +304,7 @@ class Fido2Server(object):
self.rp.id,
_wrap_credentials(credentials),
user_verification,
extensions,
)
},
state,
@ -383,13 +384,13 @@ class U2FFido2Server(Fido2Server):
)
def register_begin(self, *args, **kwargs):
kwargs.setdefault("extensions", {})["appidExclude"] = self._app_id
req, state = super(U2FFido2Server, self).register_begin(*args, **kwargs)
req["publicKey"].setdefault("extensions", {})["appidExclude"] = self._app_id
return req, state
def authenticate_begin(self, *args, **kwargs):
kwargs.setdefault("extensions", {})["appid"] = self._app_id
req, state = super(U2FFido2Server, self).authenticate_begin(*args, **kwargs)
req["publicKey"].setdefault("extensions", {})["appid"] = self._app_id
return req, state
def authenticate_complete(self, *args, **kwargs):

View File

@ -33,9 +33,8 @@ This module contains various functions used throughout the rest of the project.
from base64 import urlsafe_b64decode, urlsafe_b64encode
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hmac, hashes
from binascii import b2a_hex
from io import BytesIO
import six
from typing import Union
import struct
__all__ = [
@ -48,7 +47,7 @@ __all__ = [
]
def sha256(data):
def sha256(data: bytes) -> bytes:
"""Produces a SHA256 hash of the input.
:param data: The input data to hash.
@ -71,16 +70,16 @@ def hmac_sha256(key, data):
return h.finalize()
def bytes2int(value):
def bytes2int(value: bytes) -> int:
"""Parses an arbitrarily sized integer from a byte string.
:param value: A byte string encoding a big endian unsigned integer.
:return: The parsed int.
"""
return int(b2a_hex(value), 16)
return int.from_bytes(value, "big")
def int2bytes(value, minlen=-1):
def int2bytes(value: int, minlen: int = -1) -> bytes:
"""Encodes an int as a byte string.
:param value: The integer value to encode.
@ -93,24 +92,24 @@ def int2bytes(value, minlen=-1):
value >>= 8
ba.append(value)
ba.extend([0] * (minlen - len(ba)))
return bytes(bytearray(reversed(ba)))
return bytes(reversed(ba))
def websafe_decode(data):
"""Decodes a websafe-base64 encoded string (bytes or str).
def websafe_decode(data: Union[str, bytes]) -> bytes:
"""Decodes a websafe-base64 encoded string.
See: "Base 64 Encoding with URL and Filename Safe Alphabet" from Section 5
in RFC4648 without padding.
:param data: The input to decode.
:return: The decoded bytes.
"""
if isinstance(data, six.text_type):
if isinstance(data, str):
data = data.encode("ascii")
data += b"=" * (-len(data) % 4)
return urlsafe_b64decode(data)
def websafe_encode(data):
def websafe_encode(data: bytes) -> str:
"""Encodes a byte string into websafe-base64 encoding.
:param data: The input to encode.
@ -122,7 +121,7 @@ def websafe_encode(data):
class ByteBuffer(BytesIO):
"""BytesIO-like object with the ability to unpack values."""
def unpack(self, fmt):
def unpack(self, fmt: str):
"""Reads and unpacks a value from the buffer.
:param fmt: A struct format string yielding a single value.

View File

@ -25,12 +25,14 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from .utils import sha256
from enum import Enum, unique
import six
from . import cbor
from .cose import CoseKey, ES256
from .utils import sha256, ByteBuffer
from enum import Enum, unique, IntFlag
from dataclasses import dataclass, fields, field as _field
from typing import Any, Mapping, Optional, Sequence, Tuple
import re
import struct
"""
Data classes based on the W3C WebAuthn specification (https://www.w3.org/TR/webauthn/).
@ -38,8 +40,264 @@ Data classes based on the W3C WebAuthn specification (https://www.w3.org/TR/weba
See the specification for a description and details on their usage.
"""
# Binary types
class _StringEnum(six.text_type, Enum):
@dataclass(init=False)
class AttestedCredentialData(bytes):
aaguid: bytes
credential_id: bytes
public_key: CoseKey
def __init__(self, _):
super().__init__()
parsed = AttestedCredentialData._parse(self)
self.aaguid = parsed[0]
self.credential_id = parsed[1]
self.public_key = parsed[2]
if parsed[3]:
raise ValueError("Wrong length")
def __str__(self): # Override default implementation from bytes.
return repr(self)
@staticmethod
def _parse(data: bytes) -> Tuple[bytes, bytes, CoseKey, bytes]:
"""Parse the components of an AttestedCredentialData from a binary
string, and return them.
:param data: A binary string containing an attested credential data.
:return: AAGUID, credential ID, public key, and remaining data.
"""
reader = ByteBuffer(data)
aaguid = reader.read(16)
cred_id = reader.read(reader.unpack(">H"))
pub_key, rest = cbor.decode_from(reader.read())
return aaguid, cred_id, CoseKey.parse(pub_key), rest
@classmethod
def create(
cls, aaguid: bytes, credential_id: bytes, public_key: CoseKey
) -> "AttestedCredentialData":
"""Create an AttestedCredentialData by providing its components.
:param aaguid: The AAGUID of the authenticator.
:param credential_id: The binary ID of the credential.
:param public_key: A COSE formatted public key.
:return: The attested credential data.
"""
return cls(
aaguid
+ struct.pack(">H", len(credential_id))
+ credential_id
+ cbor.encode(public_key)
)
@classmethod
def unpack_from(cls, data: bytes) -> Tuple["AttestedCredentialData", bytes]:
"""Unpack an AttestedCredentialData from a byte string, returning it and
any remaining data.
:param data: A binary string containing an attested credential data.
:return: The parsed AttestedCredentialData, and any remaining data from
the input.
"""
aaguid, cred_id, pub_key, rest = cls._parse(data)
return cls.create(aaguid, cred_id, pub_key), rest
@classmethod
def from_ctap1(
cls, key_handle: bytes, public_key: bytes
) -> "AttestedCredentialData":
"""Create an AttestatedCredentialData from a CTAP1 RegistrationData instance.
:param key_handle: The CTAP1 credential key_handle.
:type key_handle: bytes
:param public_key: The CTAP1 65 byte public key.
:type public_key: bytes
:return: The credential data, using an all-zero AAGUID.
:rtype: AttestedCredentialData
"""
return cls.create(
b"\0" * 16, key_handle, ES256.from_ctap1(public_key) # AAGUID
)
@dataclass(init=False)
class AuthenticatorData(bytes):
"""Binary encoding of the authenticator data.
:param _: The binary representation of the authenticator data.
:ivar rp_id_hash: SHA256 hash of the RP ID.
:ivar flags: The flags of the authenticator data, see
AuthenticatorData.FLAG.
:ivar counter: The signature counter of the authenticator.
:ivar credential_data: Attested credential data, if available.
:ivar extensions: Authenticator extensions, if available.
"""
@unique
class FLAG(IntFlag):
"""Authenticator data flags
See https://www.w3.org/TR/webauthn/#sec-authenticator-data for details
"""
USER_PRESENT = 0x01
USER_VERIFIED = 0x04
ATTESTED = 0x40
EXTENSION_DATA = 0x80
rp_id_hash: bytes
flags: "AuthenticatorData.FLAG"
counter: int
credential_data: Optional[AttestedCredentialData]
extensions: Optional[Mapping]
def __init__(self, _):
super().__init__()
reader = ByteBuffer(self)
self.rp_id_hash = reader.read(32)
self.flags = reader.unpack("B")
self.counter = reader.unpack(">I")
rest = reader.read()
if self.flags & AuthenticatorData.FLAG.ATTESTED:
self.credential_data, rest = AttestedCredentialData.unpack_from(rest)
else:
self.credential_data = None
if self.flags & AuthenticatorData.FLAG.EXTENSION_DATA:
self.extensions, rest = cbor.decode_from(rest)
else:
self.extensions = None
if rest:
raise ValueError("Wrong length")
def __str__(self): # Override default implementation from bytes.
return repr(self)
@classmethod
def create(
cls,
rp_id_hash: bytes,
flags: "AuthenticatorData.FLAG",
counter: int,
credential_data: bytes = b"",
extensions: Optional[Mapping] = None,
):
"""Create an AuthenticatorData instance.
:param rp_id_hash: SHA256 hash of the RP ID.
:param flags: Flags of the AuthenticatorData.
:param counter: Signature counter of the authenticator data.
:param credential_data: Authenticated credential data (only if attested
credential data flag is set).
:param extensions: Authenticator extensions (only if ED flag is set).
:return: The authenticator data.
"""
return cls(
rp_id_hash
+ struct.pack(">BI", flags, counter)
+ credential_data
+ (cbor.encode(extensions) if extensions is not None else b"")
)
def is_user_present(self) -> bool:
"""Return true if the User Present flag is set.
:return: True if User Present is set, False otherwise.
:rtype: bool
"""
return bool(self.flags & AuthenticatorData.FLAG.USER_PRESENT)
def is_user_verified(self) -> bool:
"""Return true if the User Verified flag is set.
:return: True if User Verified is set, False otherwise.
:rtype: bool
"""
return bool(self.flags & AuthenticatorData.FLAG.USER_VERIFIED)
def is_attested(self) -> bool:
"""Return true if the Attested credential data flag is set.
:return: True if Attested credential data is set, False otherwise.
:rtype: bool
"""
return bool(self.flags & AuthenticatorData.FLAG.ATTESTED)
def has_extension_data(self) -> bool:
"""Return true if the Extenstion data flag is set.
:return: True if Extenstion data is set, False otherwise.
:rtype: bool
"""
return bool(self.flags & AuthenticatorData.FLAG.EXTENSION_DATA)
@dataclass(init=False)
class AttestationObject(bytes): # , Mapping[str, Any]):
"""Binary CBOR encoded attestation object.
:param _: The binary representation of the attestation object.
:ivar fmt: The type of attestation used.
:ivar auth_data: The attested authenticator data.
:ivar att_statement: The attestation statement.
"""
fmt: str
auth_data: AuthenticatorData
att_stmt: Mapping[str, Any]
def __init__(self, _):
super().__init__()
data = cbor.decode(bytes(self))
self.fmt = data["fmt"]
self.auth_data = AuthenticatorData(data["authData"])
self.att_stmt = data["attStmt"]
def __str__(self): # Override default implementation from bytes.
return repr(self)
@classmethod
def create(
cls, fmt: str, auth_data: AuthenticatorData, att_stmt: Mapping[str, Any]
) -> "AttestationObject":
return cls(
cbor.encode({"fmt": fmt, "authData": auth_data, "attStmt": att_stmt})
)
@classmethod
def from_ctap1(cls, app_param: bytes, registration) -> "AttestationObject":
"""Create an AttestationObject from a CTAP1 RegistrationData instance.
:param app_param: SHA256 hash of the RP ID used for the CTAP1 request.
:type app_param: bytes
:param registration: The CTAP1 registration data.
:type registration: RegistrationData
:return: The attestation object, using the "fido-u2f" format.
:rtype: AttestationObject
"""
return cls.create(
"fido-u2f",
AuthenticatorData.create(
app_param,
AuthenticatorData.FLAG.ATTESTED | AuthenticatorData.FLAG.USER_PRESENT,
0,
AttestedCredentialData.from_ctap1(
registration.key_handle, registration.public_key
),
),
{"x5c": [registration.certificate], "sig": registration.signature},
)
class _StringEnum(str, Enum):
@classmethod
def _wrap(cls, value):
if value is None:
@ -90,52 +348,51 @@ def _camel2snake(name):
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
class _DataObject(dict):
def field(*, transform=lambda x: x, **kwargs):
return _field(metadata={"transform": transform}, **kwargs)
class _DataObject(Mapping[str, Any]):
"""Base class for WebAuthn data types, acting both as dict and providing attribute
access to values.
access to values. Subclasses should be annotated with @dataclass(eq=False)
"""
def __init__(self, **data):
keys = {k: _snake2camel(k) for k in data.keys()}
super(_DataObject, self).__init__(
{keys[k]: v for k, v in data.items() if v is not None}
)
super(_DataObject, self).__setattr__("_keys", keys)
def __post_init__(self):
self._keys = []
for f in fields(self):
transform = f.metadata.get("transform")
value = getattr(self, f.name)
if value:
if transform:
setattr(self, f.name, transform(value))
self._keys.append(_snake2camel(f.name))
def __getattr__(self, name):
if name in self._keys:
return self.get(self._keys[name])
raise AttributeError(
"'{}' object has no attribute '{}'".format(type(self).__name__, name)
)
def __getitem__(self, key):
return getattr(self, _camel2snake(key))
def __setattr__(self, name, value):
if name in self._keys:
self[self._keys[name]] = value
else:
raise AttributeError(
"'{}' object has no attribute '{}'".format(type(self).__name__, name)
)
def __iter__(self):
return iter(self._keys)
def __repr__(self):
return "{}({!r})".format(self.__class__.__name__, dict(self))
def __len__(self):
return len(self._keys)
@classmethod
def _wrap(cls, data):
def _wrap(cls, data: Optional[Mapping[str, Any]]):
if data is None:
return None
if isinstance(data, cls):
return data
return cls(**{_camel2snake(k): v for k, v in data.items()})
return cls(**{_camel2snake(k): v for k, v in data.items()}) # type: ignore
@classmethod
def _wrap_list(cls, datas):
return [cls._wrap(x) for x in datas] if datas is not None else None
@dataclass(eq=False)
class PublicKeyCredentialRpEntity(_DataObject):
def __init__(self, id, name, icon=None):
super(PublicKeyCredentialRpEntity, self).__init__(id=id, name=name, icon=icon)
id: str
name: str
@property
def id_hash(self):
@ -143,123 +400,86 @@ class PublicKeyCredentialRpEntity(_DataObject):
return sha256(self.id.encode("utf8"))
@dataclass(eq=False)
class PublicKeyCredentialUserEntity(_DataObject):
def __init__(self, id, name, icon=None, display_name=None):
super(PublicKeyCredentialUserEntity, self).__init__(
id=id, name=name, icon=icon, display_name=display_name
)
id: bytes
name: str
display_name: Optional[str] = None
@dataclass(eq=False)
class PublicKeyCredentialParameters(_DataObject):
def __init__(self, type, alg):
super(PublicKeyCredentialParameters, self).__init__(
type=PublicKeyCredentialType(type), alg=alg
)
type: PublicKeyCredentialType = field(transform=PublicKeyCredentialType)
alg: int = field()
@dataclass(eq=False)
class PublicKeyCredentialDescriptor(_DataObject):
def __init__(self, type, id, transports=None):
super(PublicKeyCredentialDescriptor, self).__init__(
type=PublicKeyCredentialType(type),
id=id,
transports=transports, # Note: Type is str as in current WebAuthn draft!
)
type: PublicKeyCredentialType = field(transform=PublicKeyCredentialType)
id: bytes = field()
transports: Optional[Sequence[str]] = None
@dataclass(eq=False)
class AuthenticatorSelectionCriteria(_DataObject):
def __init__(
self,
authenticator_attachment=None,
require_resident_key=None,
user_verification=None,
):
super(AuthenticatorSelectionCriteria, self).__init__(
authenticator_attachment=AuthenticatorAttachment._wrap(
authenticator_attachment
),
require_resident_key=require_resident_key,
user_verification=UserVerificationRequirement._wrap(user_verification),
)
authenticator_attachment: Optional[AuthenticatorAttachment] = field(
transform=AuthenticatorAttachment._wrap, default=None
)
require_resident_key: Optional[bool] = None
user_verification: Optional[UserVerificationRequirement] = field(
transform=UserVerificationRequirement, default=None
)
@dataclass(eq=False)
class PublicKeyCredentialCreationOptions(_DataObject):
def __init__(
self,
rp,
user,
challenge,
pub_key_cred_params,
timeout=None,
exclude_credentials=None,
authenticator_selection=None,
attestation=None,
extensions=None,
):
super(PublicKeyCredentialCreationOptions, self).__init__(
rp=PublicKeyCredentialRpEntity._wrap(rp),
user=PublicKeyCredentialUserEntity._wrap(user),
challenge=challenge,
pub_key_cred_params=PublicKeyCredentialParameters._wrap_list(
pub_key_cred_params
),
timeout=timeout,
exclude_credentials=PublicKeyCredentialDescriptor._wrap_list(
exclude_credentials
),
authenticator_selection=AuthenticatorSelectionCriteria._wrap(
authenticator_selection
),
attestation=AttestationConveyancePreference._wrap(attestation),
extensions=extensions,
)
rp: PublicKeyCredentialRpEntity = field(transform=PublicKeyCredentialRpEntity._wrap)
user: PublicKeyCredentialUserEntity = field(
transform=PublicKeyCredentialUserEntity._wrap
)
challenge: bytes = field()
pub_key_cred_params: Sequence[PublicKeyCredentialParameters] = field(
transform=PublicKeyCredentialParameters._wrap_list
)
timeout: Optional[int] = None
exclude_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = field(
transform=PublicKeyCredentialDescriptor._wrap_list, default=None
)
authenticator_selection: Optional[AuthenticatorSelectionCriteria] = field(
transform=AuthenticatorSelectionCriteria._wrap, default=None
)
attestation: Optional[AttestationConveyancePreference] = field(
transform=AttestationConveyancePreference._wrap, default=None
)
extensions: Optional[Mapping[str, Any]] = None
@dataclass(eq=False)
class PublicKeyCredentialRequestOptions(_DataObject):
def __init__(
self,
challenge,
timeout=None,
rp_id=None,
allow_credentials=None,
user_verification=None,
extensions=None,
):
super(PublicKeyCredentialRequestOptions, self).__init__(
challenge=challenge,
timeout=timeout,
rp_id=rp_id,
allow_credentials=PublicKeyCredentialDescriptor._wrap_list(
allow_credentials
),
user_verification=UserVerificationRequirement._wrap(user_verification),
extensions=extensions,
)
challenge: bytes
timeout: Optional[int] = None
rp_id: Optional[str] = None
allow_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = field(
transform=PublicKeyCredentialDescriptor._wrap_list, default=None
)
user_verification: Optional[UserVerificationRequirement] = field(
transform=UserVerificationRequirement._wrap, default=None
)
extensions: Optional[Mapping[str, Any]] = None
@dataclass(eq=False)
class AuthenticatorAttestationResponse(_DataObject):
def __init__(self, client_data, attestation_object, extension_results=None):
super(AuthenticatorAttestationResponse, self).__init__(
client_data=client_data,
attestation_object=attestation_object,
extension_results=extension_results,
)
client_data: bytes
attestation_object: AttestationObject = field(transform=AttestationObject)
extension_results: Optional[Any] = None
@dataclass(eq=False)
class AuthenticatorAssertionResponse(_DataObject):
def __init__(
self,
client_data,
authenticator_data,
signature,
user_handle,
credential_id,
extension_results=None,
):
super(AuthenticatorAssertionResponse, self).__init__(
client_data=client_data,
authenticator_data=authenticator_data,
signature=signature,
user_handle=user_handle,
credential_id=credential_id,
extension_results=extension_results,
)
client_data: bytes
authenticator_data: AuthenticatorData = field(transform=AuthenticatorData)
signature: bytes = field()
user_handle: bytes = field()
credential_id: bytes = field()
extension_results: Optional[Any] = None

View File

@ -37,8 +37,6 @@ https://github.com/microsoft/webauthn
#
# pylint: disable=invalid-name, super-init-not-called, too-few-public-methods
from __future__ import absolute_import, unicode_literals
from enum import IntEnum, unique
from ctypes.wintypes import BOOL, DWORD, LONG, LPCWSTR, HWND
from threading import Thread
@ -50,7 +48,7 @@ PBYTE = ctypes.POINTER(ctypes.c_ubyte) # Different from wintypes.PBYTE, which i
PCWSTR = ctypes.c_wchar_p
class BytesProperty(object):
class BytesProperty:
"""Property for structs storing byte arrays as DWORD + PBYTE.
Allows for easy reading/writing to struct fields using Python bytes objects.
@ -656,7 +654,7 @@ class CancelThread(Thread):
self.join()
class WinAPI(object):
class WinAPI:
"""Implementation of Microsoft's WebAuthN APIs.
:param ctypes.HWND handle: Window handle to use for API calls.

View File

@ -3,6 +3,3 @@ files = fido2/
[mypy-smartcard.*]
ignore_missing_imports = True
[mypy-uhid_freebsd.*]
ignore_missing_imports = True

View File

@ -26,9 +26,7 @@ include = ["COPYING", "COPYING.MPLv2", "COPYING.APLv2", "NEWS", "README.adoc"]
python = "^3.6"
cryptography = "^2.1 || ^3.0"
dataclasses = {version = "^0.8", python = "<3.7"}
uhid-freebsd = {version = "^1.2.1", platform = "FreeBSD"}
pyscard = {version = "^1.9 || ^2.0.0", optional = true}
six = "^1.16.0"
[tool.poetry.extras]
pcsc = ["pyscard"]

View File

@ -1,12 +1,11 @@
import unittest
from binascii import a2b_hex
from fido2.hid.base import parse_report_descriptor
class TestBase(unittest.TestCase):
def test_parse_report_descriptor_1(self):
max_in_size, max_out_size = parse_report_descriptor(
a2b_hex(
bytes.fromhex(
"06d0f10901a1010920150026ff007508954081020921150026ff00750895409102c0"
)
)
@ -17,7 +16,7 @@ class TestBase(unittest.TestCase):
def test_parse_report_descriptor_2(self):
with self.assertRaises(ValueError):
parse_report_descriptor(
a2b_hex(
bytes.fromhex(
"05010902a1010901a10005091901290515002501950575018102950175038101"
"05010930093109381581257f750895038106c0c0"
)

View File

@ -25,10 +25,7 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.ctap2 import AuthenticatorData
from fido2.webauthn import AuthenticatorData
from fido2.attestation import (
Attestation,
AttestationType,
@ -44,14 +41,13 @@ from fido2.attestation import (
UnsupportedType,
verify_x509_chain,
)
from binascii import a2b_hex
import unittest
# GS Root R2 (https://pki.goog/)
_GSR2_DER = a2b_hex(
b"308203ba308202a2a003020102020b0400000000010f8626e60d300d06092a864886f70d0101050500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3036313231353038303030305a170d3231313231353038303030305a304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e30820122300d06092a864886f70d01010105000382010f003082010a0282010100a6cf240ebe2e6f28994542c4ab3e21549b0bd37f8470fa12b3cbbf875fc67f86d3b2305cd6fdadf17bdce5f86096099210f5d053defb7b7e7388ac52887b4aa6ca49a65ea8a78c5a11bc7a82ebbe8ce9b3ac962507974a992a072fb41e77bf8a0fb5027c1b96b8c5b93a2cbcd612b9eb597de2d006865f5e496ab5395e8834ecbc780c0898846ca8cd4bb4a07d0c794df0b82dcb21cad56c5b7de1a02984a1f9d39449cb24629120bcdd0bd5d9ccf9ea270a2b7391c69d1bacc8cbe8e0a0f42f908b4dfbb0361bf6197a85e06df26113885c9fe0930a51978a5aceafabd5f7aa09aa60bddcd95fdf72a960135e0001c94afa3fa4ea070321028e82ca03c29b8f0203010001a3819c308199300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e041604149be20757671c1ec06a06de59b49a2ddfdc19862e30360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676c6f62616c7369676e2e6e65742f726f6f742d72322e63726c301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e300d06092a864886f70d01010505000382010100998153871c68978691ece04ab8440bab81ac274fd6c1b81c4378b30c9afcea2c3c6e611b4d4b29f59f051d26c1b8e983006245b6a90893b9a9334b189ac2f887884edbdd71341ac154da463fe0d32aab6d5422f53a62cd206fba2989d7dd91eed35ca23ea15b41f5dfe564432de9d539abd2a2dfb78bd0c080191c45c02d8ce8f82da4745649c505b54f15de6e44783987a87ebbf3791891bbf46f9dc1f08c358c5d01fbc36db9ef446d7946317e0afea982c1ffefab6e20c450c95f9d4d9b178c0ce501c9a0416a7353faa550b46e250ffb4c18f4fd52d98e69b1e8110fde88d8fb1d49f7aade95cf2078c26012db25408c6afc7e4238406412f79e81e1932e" # noqa E501
_GSR2_DER = bytes.fromhex(
"308203ba308202a2a003020102020b0400000000010f8626e60d300d06092a864886f70d0101050500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3036313231353038303030305a170d3231313231353038303030305a304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e30820122300d06092a864886f70d01010105000382010f003082010a0282010100a6cf240ebe2e6f28994542c4ab3e21549b0bd37f8470fa12b3cbbf875fc67f86d3b2305cd6fdadf17bdce5f86096099210f5d053defb7b7e7388ac52887b4aa6ca49a65ea8a78c5a11bc7a82ebbe8ce9b3ac962507974a992a072fb41e77bf8a0fb5027c1b96b8c5b93a2cbcd612b9eb597de2d006865f5e496ab5395e8834ecbc780c0898846ca8cd4bb4a07d0c794df0b82dcb21cad56c5b7de1a02984a1f9d39449cb24629120bcdd0bd5d9ccf9ea270a2b7391c69d1bacc8cbe8e0a0f42f908b4dfbb0361bf6197a85e06df26113885c9fe0930a51978a5aceafabd5f7aa09aa60bddcd95fdf72a960135e0001c94afa3fa4ea070321028e82ca03c29b8f0203010001a3819c308199300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e041604149be20757671c1ec06a06de59b49a2ddfdc19862e30360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676c6f62616c7369676e2e6e65742f726f6f742d72322e63726c301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e300d06092a864886f70d01010505000382010100998153871c68978691ece04ab8440bab81ac274fd6c1b81c4378b30c9afcea2c3c6e611b4d4b29f59f051d26c1b8e983006245b6a90893b9a9334b189ac2f887884edbdd71341ac154da463fe0d32aab6d5422f53a62cd206fba2989d7dd91eed35ca23ea15b41f5dfe564432de9d539abd2a2dfb78bd0c080191c45c02d8ce8f82da4745649c505b54f15de6e44783987a87ebbf3791891bbf46f9dc1f08c358c5d01fbc36db9ef446d7946317e0afea982c1ffefab6e20c450c95f9d4d9b178c0ce501c9a0416a7353faa550b46e250ffb4c18f4fd52d98e69b1e8110fde88d8fb1d49f7aade95cf2078c26012db25408c6afc7e4238406412f79e81e1932e" # noqa E501
)
@ -68,8 +64,8 @@ class TestAttestationObject(unittest.TestCase):
self.assertIsInstance(attestation, NoneAttestation)
auth_data = AuthenticatorData(
a2b_hex(
b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501
bytes.fromhex(
"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501
)
)
res = attestation.verify({}, auth_data, b"deadbeef" * 8)
@ -84,8 +80,8 @@ class TestAttestationObject(unittest.TestCase):
self.assertIsInstance(attestation, NoneAttestation)
auth_data = AuthenticatorData(
a2b_hex(
b"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa
bytes.fromhex(
"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa
)
) # noqa
res = attestation.verify({}, auth_data, b"deadbeef" * 8)
@ -100,8 +96,8 @@ class TestAttestationObject(unittest.TestCase):
self.assertIsInstance(attestation, TpmAttestation)
statement = {
"alg": -65535,
"sig": a2b_hex(
b"""80e564d8cbb236577de68d2e68ecae200a8eaf6992889b5
"sig": bytes.fromhex(
"""80e564d8cbb236577de68d2e68ecae200a8eaf6992889b5
fdc24624a4cb69caaab18df965058fbac39df9714b9c80b9a12d715cfc4dd15ed3a6e191a6d26e
7206fd402b0733c2c8b91f62ad44e4d41c940e2e914253b1d1a1c8889b1cdaf668b5449245dc33
1fab12e0b0dcdfc530cbe1f370e1f2b06c163fbd6177925a1a8998edd2e726989246a1980fa34e
@ -109,12 +105,12 @@ fdc24624a4cb69caaab18df965058fbac39df9714b9c80b9a12d715cfc4dd15ed3a6e191a6d26e
9f361815dfb268b33ccde5f29e4348a70f95abc30754c839fa7126e5bd882377d6abe3c0c95ba5
c21190a5e4fff5380b2c23cc1655e593244019e172ba8284618471d95b92c231c1ffe98ff23
""".replace(
b"\n", b""
"\n", ""
)
),
"x5c": [
a2b_hex(
b"""308204b23082039aa0030201020210789e1a3657344c52bad2
bytes.fromhex(
"""308204b23082039aa0030201020210789e1a3657344c52bad2
2ed1ceb1bfaf300d06092a864886f70d01010b05003041313f303d060355040313364e43552d4e
54432d4b455949442d394642423739414130463532363237384245443135303932394137313731
45393641333542454637301e170d3139303430313038353934305a170d32393034303130383539
@ -146,11 +142,11 @@ f6fe5558f7d1c56a7646ba483cd601690a9323caba9257ae561781b13c658083ad1281047d94d4
c1ab9759d90a16fbe167cec388e7b67027a20dbc1b88986dbb636107ef91ffec22c413ac5fbfec
3de9ee4aa1c6e4c173e43246193890c8b024587fcc8028eb379f515de3c678b11dfb81aef3547c
3c6e790577d52f775f9148""".replace(
b"\n", b""
"\n", ""
)
),
a2b_hex(
b"""308205e8308203d0a003020102021333000000a5304bb34bf0
bytes.fromhex(
"""308205e8308203d0a003020102021333000000a5304bb34bf0
bee43e0000000000a5300d06092a864886f70d01010b050030818c310b30090603550406130255
53311330110603550408130a57617368696e67746f6e3110300e060355040713075265646d6f6e
64311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e313630340603
@ -190,21 +186,21 @@ d42d17888534fee9daf844260de901c3b18b49ccb2a5f81f0f4639f2e2cfa1ce1d7c791cef6f48
5d10df989aac02b1e9afd1094603f5307133f5f59ce105a5910700f98fea5a5fcf8f5cf4c797bd
79d440cc4f9161f5cc61e0e8f06592050cd1f0f0fd066bd1d6335710fdf8159b75281ee1082bff
1da2fc0b631bd346ac""".replace(
b"\n", b""
"\n", ""
)
),
],
"certInfo": a2b_hex(
b"""ff54434780170022000b68cec627cc6411099a1f80
"certInfo": bytes.fromhex(
"""ff54434780170022000b68cec627cc6411099a1f80
9fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff
16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e185
29c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bf
dc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7""".replace(
b"\n", b""
"\n", ""
)
),
"pubArea": a2b_hex(
b"""0001000b0006047200209dffcbf36c383ae699fb986
"pubArea": bytes.fromhex(
"""0001000b0006047200209dffcbf36c383ae699fb986
8dc6dcb89d7153884be2803922c124158bfad22ae001000100800000000000100c706586c7f46c
dffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271
823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b
@ -213,17 +209,17 @@ a6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930
a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722
d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994
ee18128ed50dd7a855e54d2459db005""".replace(
b"\n", b""
"\n", ""
)
),
}
auth_data = AuthenticatorData(
a2b_hex(
b"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd9450000000008987058cadc4b81b6e130de50dcbe9600206053b7b599d16fb3fb11ea17a344850ebd0d18183a5b7ca6dfbd20c63cdb462aa401030339010020590100c706586c7f46cdffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ada6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994ee18128ed50dd7a855e54d2459db0052143010001" # noqa
bytes.fromhex(
"54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd9450000000008987058cadc4b81b6e130de50dcbe9600206053b7b599d16fb3fb11ea17a344850ebd0d18183a5b7ca6dfbd20c63cdb462aa401030339010020590100c706586c7f46cdffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ada6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994ee18128ed50dd7a855e54d2459db0052143010001" # noqa
)
)
client_param = a2b_hex(
b"057a0ecbe7e3e99e8926941614f6af078c802b110be89eb221d69be2e17a1ba4"
client_param = bytes.fromhex(
"057a0ecbe7e3e99e8926941614f6af078c802b110be89eb221d69be2e17a1ba4"
)
res = attestation.verify(statement, auth_data, client_param)
@ -235,22 +231,22 @@ ee18128ed50dd7a855e54d2459db005""".replace(
self.assertIsInstance(attestation, FidoU2FAttestation)
statement = {
"sig": a2b_hex(
b"30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501
"sig": bytes.fromhex(
"30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501
),
"x5c": [
a2b_hex(
b"3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5" # noqa E501
bytes.fromhex(
"3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5" # noqa E501
)
],
}
auth_data = AuthenticatorData(
a2b_hex(
b"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91" # noqa E501
bytes.fromhex(
"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91" # noqa E501
)
)
client_param = a2b_hex(
b"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141"
client_param = bytes.fromhex(
"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141"
)
res = attestation.verify(statement, auth_data, client_param)
@ -267,22 +263,22 @@ ee18128ed50dd7a855e54d2459db005""".replace(
statement = {
"alg": -7,
"sig": a2b_hex(
b"304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B" # noqa E501
"sig": bytes.fromhex(
"304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B" # noqa E501
),
"x5c": [
a2b_hex(
b"308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE" # noqa E501
bytes.fromhex(
"308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE" # noqa E501
)
],
}
auth_data = AuthenticatorData(
a2b_hex(
b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D" # noqa E501
bytes.fromhex(
"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D" # noqa E501
)
)
client_param = a2b_hex(
b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF"
client_param = bytes.fromhex(
"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF"
)
res = attestation.verify(statement, auth_data, client_param)
@ -303,12 +299,12 @@ ee18128ed50dd7a855e54d2459db005""".replace(
}
auth_data = AuthenticatorData(
a2b_hex(
b"720c20fde835785e0f5ebcad8ef6a7bd88804a91612a2e820e0059b8d5358797450000000000000000000000000000000000000000004101c8fd9b533d6adacf6710ebcfb39f6361c4d7e8787db47dc0a75ae0e7c862198c9c83b81ef2547bb5669314095fc846af4ecac6875f7b230cac7359c76b0c20f7a5010203262001215820a28851e2d411b5b2c289da50d41cc41be88498941fc256dab500b21c8dafe8d1225820d289dd467715be06a622771a7b21e1bbe2372f8713d20dd7888a6e7ae1845ca8" # noqa E501
bytes.fromhex(
"720c20fde835785e0f5ebcad8ef6a7bd88804a91612a2e820e0059b8d5358797450000000000000000000000000000000000000000004101c8fd9b533d6adacf6710ebcfb39f6361c4d7e8787db47dc0a75ae0e7c862198c9c83b81ef2547bb5669314095fc846af4ecac6875f7b230cac7359c76b0c20f7a5010203262001215820a28851e2d411b5b2c289da50d41cc41be88498941fc256dab500b21c8dafe8d1225820d289dd467715be06a622771a7b21e1bbe2372f8713d20dd7888a6e7ae1845ca8" # noqa E501
)
)
client_param = a2b_hex(
b"8422c80f3428e4e6465f76ebc8a4a93759a0a2e1fb845ee5eea7a02027408520"
client_param = bytes.fromhex(
"8422c80f3428e4e6465f76ebc8a4a93759a0a2e1fb845ee5eea7a02027408520"
)
res = attestation.verify(statement, auth_data, client_param)
@ -322,22 +318,22 @@ ee18128ed50dd7a855e54d2459db005""".replace(
statement = {
"alg": -7,
"x5c": [
a2b_hex(
bytes.fromhex(
"30820242308201c9a00302010202060176af5359ff300a06082a8648ce3d0403023048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230313232383136323732345a170d3230313233313136323732345a3081913149304706035504030c4038303966626331313065613835663233613862323435616563363136333530663337646665393632313232373336653431663862646365663334366138306439311a3018060355040b0c114141412043657274696669636174696f6e31133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613059301306072a8648ce3d020106082a8648ce3d030107034200041f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d6b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37a3553053300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0303306092a864886f76364080204263024a1220420e56fb6212b3aae885294464fb10184b7fea62c48a6d78e61194e07ae6dacc132300a06082a8648ce3d040302036700306402301de8f0f238eee4f5ae80c59290b51e8c3f79397bf198e444ba162d4fccaab8558b072cf00a7c662f9058ff2a98af61ae0230149403b9643066e73a98d3659563dc4da49bf84e82b2b5bbeaf57755646fa243f36344d44b80a5798203bca023e030c7" # noqa E501
),
a2b_hex(
bytes.fromhex(
"30820234308201baa003020102021056255395c7a7fb40ebe228d8260853b6300a06082a8648ce3d040303304b311f301d06035504030c164170706c6520576562417574686e20526f6f7420434131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230303331383138333830315a170d3330303331333030303030305a3048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613076301006072a8648ce3d020106052b8104002203620004832e872f261491810225b9f5fcd6bb6378b5f55f3fcb045bc735993475fd549044df9bfe19211765c69a1dda050b38d45083401a434fb24d112d56c3e1cfbfcb9891fec0696081bef96cbc77c88dddaf46a5aee1dd515b5afaab93be9c0b2691a366306430120603551d130101ff040830060101ff020100301f0603551d2304183016801426d764d9c578c25a67d1a7de6b12d01b63f1c6d7301d0603551d0e04160414ebae82c4ffa1ac5b51d4cf24610500be63bd7788300e0603551d0f0101ff040403020106300a06082a8648ce3d0403030368003065023100dd8b1a3481a5fad9dbb4e7657b841e144c27b75b876a4186c2b1475750337227efe554457ef648950c632e5c483e70c102302c8a6044dc201fcfe59bc34d2930c1487851d960ed6a75f1eb4acabe38cd25b897d0c805bef0c7f78b07a571c6e80e07" # noqa E501
),
],
}
auth_data = AuthenticatorData(
a2b_hex(
b"c46cef82ad1b546477591d008b08759ec3e6d2ecb4f39474bfea6969925d03b7450000000000000000000000000000000000000000001473d9429f4052d84debd035eb5bb7e716e3b81863a50102032620012158201f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d2258206b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37" # noqa E501
bytes.fromhex(
"c46cef82ad1b546477591d008b08759ec3e6d2ecb4f39474bfea6969925d03b7450000000000000000000000000000000000000000001473d9429f4052d84debd035eb5bb7e716e3b81863a50102032620012158201f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d2258206b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37" # noqa E501
)
)
client_param = a2b_hex(
b"0d3ce80fabbc3adb9dd891deabb8db84603ea1fe2da8b5d4b46d6591aab342f3"
client_param = bytes.fromhex(
"0d3ce80fabbc3adb9dd891deabb8db84603ea1fe2da8b5d4b46d6591aab342f3"
)
res = attestation.verify(statement, auth_data, client_param)

View File

@ -27,11 +27,7 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2 import cbor
from binascii import a2b_hex, b2a_hex
import unittest
@ -154,7 +150,7 @@ _TEST_VECTORS = [
def cbor2hex(data):
return b2a_hex(cbor.encode(data)).decode()
return cbor.encode(data).hex()
class TestCborTestVectors(unittest.TestCase):
@ -166,8 +162,8 @@ class TestCborTestVectors(unittest.TestCase):
def test_vectors(self):
for (data, value) in _TEST_VECTORS:
try:
self.assertEqual(cbor.decode_from(a2b_hex(data)), (value, b""))
self.assertEqual(cbor.decode(a2b_hex(data)), value)
self.assertEqual(cbor.decode_from(bytes.fromhex(data)), (value, b""))
self.assertEqual(cbor.decode(bytes.fromhex(data)), value)
self.assertEqual(cbor2hex(value), data)
except Exception:
print("\nERROR in test vector, %s" % data)

View File

@ -27,19 +27,17 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
import mock
import unittest
import json
from threading import Event, Timer
from binascii import a2b_hex
from fido2.utils import sha256, websafe_decode
from fido2.hid import CAPABILITY
from fido2.ctap import CtapError
from fido2.ctap1 import ApduError, APDU, RegistrationData, SignatureData
from fido2.ctap2 import Info, AttestationObject
from fido2.ctap2 import Info, AttestationResponse
from fido2.client import ClientData, U2fClient, ClientError, Fido2Client
from fido2.webauthn import PublicKeyCredentialCreationOptions
from fido2.webauthn import PublicKeyCredentialCreationOptions, AttestationObject
class TestClientData(unittest.TestCase):
@ -50,14 +48,16 @@ class TestClientData(unittest.TestCase):
self.assertEqual(
client_data.hash,
a2b_hex("4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb"),
bytes.fromhex(
"4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb"
),
)
self.assertEqual(client_data.get("origin"), "http://example.com")
self.assertEqual(client_data, ClientData.from_b64(client_data.b64))
self.assertEqual(
client_data.data,
json.loads(client_data),
{
"typ": "navigator.id.finishEnrollment",
"challenge": "vqrS6WXDe1JUs5_c3i4-LkKIHRr-3XVb3azuA5TifHo",
@ -74,13 +74,13 @@ class TestClientData(unittest.TestCase):
APP_ID = "https://foo.example.com"
REG_DATA = RegistrationData(
a2b_hex(
b"0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501
bytes.fromhex(
"0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501
)
)
SIG_DATA = SignatureData(
a2b_hex(
b"0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501
bytes.fromhex(
"0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501
)
)
@ -316,10 +316,10 @@ class TestU2fClient(unittest.TestCase):
rp = {"id": "example.com", "name": "Example RP"}
user = {"id": b"user_id", "name": "A. User"}
challenge = b"Y2hhbGxlbmdl"
_INFO_NO_PIN = a2b_hex(
_INFO_NO_PIN = bytes.fromhex(
"a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501
)
_MC_RESP = a2b_hex(
_MC_RESP = bytes.fromhex(
"a301667061636b6564025900c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501
)
@ -337,7 +337,7 @@ class TestFido2Client(unittest.TestCase):
dev = mock.Mock()
dev.capabilities = CAPABILITY.CBOR
ctap2 = mock.MagicMock()
ctap2.get_info.return_value = Info(_INFO_NO_PIN)
ctap2.get_info.return_value = Info.parse(_INFO_NO_PIN)
PatchedCtap2.return_value = ctap2
client = Fido2Client(dev, APP_ID)
try:
@ -358,7 +358,7 @@ class TestFido2Client(unittest.TestCase):
dev = mock.Mock()
dev.capabilities = CAPABILITY.CBOR
ctap2 = mock.MagicMock()
ctap2.get_info.return_value = Info(_INFO_NO_PIN)
ctap2.get_info.return_value = Info.parse(_INFO_NO_PIN)
ctap2.info = ctap2.get_info()
ctap2.make_credential.side_effect = CtapError(CtapError.ERR.CREDENTIAL_EXCLUDED)
PatchedCtap2.return_value = ctap2
@ -385,9 +385,9 @@ class TestFido2Client(unittest.TestCase):
dev = mock.Mock()
dev.capabilities = CAPABILITY.CBOR
ctap2 = mock.MagicMock()
ctap2.get_info.return_value = Info(_INFO_NO_PIN)
ctap2.get_info.return_value = Info.parse(_INFO_NO_PIN)
ctap2.info = ctap2.get_info()
ctap2.make_credential.return_value = AttestationObject(_MC_RESP)
ctap2.make_credential.return_value = AttestationResponse.parse(_MC_RESP)
PatchedCtap2.return_value = ctap2
client = Fido2Client(dev, APP_ID)

View File

@ -25,10 +25,7 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.ctap1 import Ctap1, ApduError
from binascii import a2b_hex
import unittest
import mock
@ -63,17 +60,17 @@ class TestCtap1(unittest.TestCase):
def test_register(self):
ctap = Ctap1(mock.MagicMock())
ctap.device.call.return_value = (
a2b_hex(
bytes.fromhex(
"0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501
)
+ b"\x90\x00"
)
client_param = a2b_hex(
b"4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb"
client_param = bytes.fromhex(
"4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb"
)
app_param = a2b_hex(
b"f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4"
app_param = bytes.fromhex(
"f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4"
)
resp = ctap.register(client_param, app_param)
@ -82,25 +79,25 @@ class TestCtap1(unittest.TestCase):
)
self.assertEqual(
resp.public_key,
a2b_hex(
bytes.fromhex(
"04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9" # noqa E501
),
)
self.assertEqual(
resp.key_handle,
a2b_hex(
bytes.fromhex(
"2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25" # noqa E501
),
)
self.assertEqual(
resp.certificate,
a2b_hex(
bytes.fromhex(
"3082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df" # noqa E501
),
)
self.assertEqual(
resp.signature,
a2b_hex(
bytes.fromhex(
"304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501
),
)
@ -110,17 +107,17 @@ class TestCtap1(unittest.TestCase):
def test_authenticate(self):
ctap = Ctap1(mock.MagicMock())
ctap.device.call.return_value = (
a2b_hex(
bytes.fromhex(
"0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501
)
+ b"\x90\x00"
)
client_param = a2b_hex(
b"ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57"
client_param = bytes.fromhex(
"ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57"
)
app_param = a2b_hex(
b"4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca"
app_param = bytes.fromhex(
"4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca"
)
key_handle = b"\3" * 64
@ -139,13 +136,13 @@ class TestCtap1(unittest.TestCase):
self.assertEqual(resp.counter, 1)
self.assertEqual(
resp.signature,
a2b_hex(
bytes.fromhex(
"304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501
),
)
public_key = a2b_hex(
b"04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d" # noqa E501
public_key = bytes.fromhex(
"04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d" # noqa E501
)
resp.verify(app_param, client_param, public_key)

View File

@ -25,40 +25,36 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.ctap1 import RegistrationData
from fido2.ctap2 import (
Ctap2,
ClientPin,
PinProtocolV1,
Info,
AttestedCredentialData,
AuthenticatorData,
AttestationObject,
AttestationResponse,
AssertionResponse,
)
from fido2.webauthn import AttestationObject, AuthenticatorData, AttestedCredentialData
from fido2.attestation import Attestation
from fido2 import cbor
from binascii import a2b_hex
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
import unittest
import mock
_AAGUID = a2b_hex("F8A011F38C0A4D15800617111F9EDC7D")
_INFO = a2b_hex(
_AAGUID = bytes.fromhex("F8A011F38C0A4D15800617111F9EDC7D")
_INFO = bytes.fromhex(
"a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501
)
_INFO_EXTRA_KEY = a2b_hex(
"A70182665532465F5632684649444F5F325F3002826375766D6B686D61632D7365637265740350F8A011F38C0A4D15800617111F9EDC7D04A462726BF5627570F564706C6174F469636C69656E7450696EF4051904B00681010708" # noqa E501
_INFO_EXTRA_KEY = bytes.fromhex(
"A70182665532465F5632684649444F5F325F3002826375766D6B686D61632D7365637265740350F8A011F38C0A4D15800617111F9EDC7D04A462726BF5627570F564706C6174F469636C69656E7450696EF4051904B006810118631904D2" # noqa E501
)
class TestInfo(unittest.TestCase):
def test_parse_bytes(self):
info = Info(_INFO)
info = Info.parse(_INFO)
self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"])
self.assertEqual(info.extensions, ["uvm", "hmac-secret"])
@ -69,40 +65,44 @@ class TestInfo(unittest.TestCase):
self.assertEqual(info.max_msg_size, 1200)
self.assertEqual(info.pin_uv_protocols, [1])
self.assertEqual(
info.data,
dict(info),
{
Info.KEY.VERSIONS: ["U2F_V2", "FIDO_2_0"],
Info.KEY.EXTENSIONS: ["uvm", "hmac-secret"],
Info.KEY.AAGUID: _AAGUID,
Info.KEY.OPTIONS: {
0x01: ["U2F_V2", "FIDO_2_0"],
0x02: ["uvm", "hmac-secret"],
0x03: _AAGUID,
0x04: {
"clientPin": False,
"plat": False,
"rk": True,
"up": True,
},
Info.KEY.MAX_MSG_SIZE: 1200,
Info.KEY.PIN_UV_PROTOCOLS: [1],
0x05: 1200,
0x06: [1],
},
)
def test_info_with_extra_field(self):
info = Info(_INFO_EXTRA_KEY)
info = Info.parse(_INFO_EXTRA_KEY)
self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"])
self.assertEqual(info.data[7], 8)
self.assertEqual(info[99], 1234)
_ATT_CRED_DATA = a2b_hex(
_ATT_CRED_DATA = bytes.fromhex(
"f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" # noqa E501
)
_CRED_ID = a2b_hex(
_CRED_ID = bytes.fromhex(
"fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783" # noqa E501
)
_PUB_KEY = {
1: 2,
3: -7,
-1: 1,
-2: a2b_hex("643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf"),
-3: a2b_hex("171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290"),
-2: bytes.fromhex(
"643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf"
),
-3: bytes.fromhex(
"171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290"
),
}
@ -118,13 +118,13 @@ class TestAttestedCredentialData(unittest.TestCase):
self.assertEqual(_ATT_CRED_DATA, data)
_AUTH_DATA_MC = a2b_hex(
_AUTH_DATA_MC = bytes.fromhex(
"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000001CF8A011F38C0A4D15800617111F9EDC7D0040FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783A5010203262001215820643566C206DD00227005FA5DE69320616CA268043A38F08BDE2E9DC45A5CAFAF225820171353B2932434703726AAE579FA6542432861FE591E481EA22D63997E1A5290" # noqa E501
)
_AUTH_DATA_GA = a2b_hex(
_AUTH_DATA_GA = bytes.fromhex(
"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000001D"
)
_RP_ID_HASH = a2b_hex(
_RP_ID_HASH = bytes.fromhex(
"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12"
)
@ -147,80 +147,55 @@ class TestAuthenticatorData(unittest.TestCase):
self.assertIsNone(data.extensions)
_MC_RESP = a2b_hex(
_MC_RESP = bytes.fromhex(
"a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501
)
_GA_RESP = a2b_hex(
_GA_RESP = bytes.fromhex(
"a301a26269645840fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b1578364747970656a7075626c69632d6b65790258250021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12010000001d035846304402206765cbf6e871d3af7f01ae96f06b13c90f26f54b905c5166a2c791274fc2397102200b143893586cc799fba4da83b119eaea1bd80ac3ce88fcedb3efbd596a1f4f63" # noqa E501
)
_CRED_ID = a2b_hex(
_CRED_ID = bytes.fromhex(
"FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783" # noqa E501
)
_CRED = {"type": "public-key", "id": _CRED_ID}
_SIGNATURE = a2b_hex(
_SIGNATURE = bytes.fromhex(
"304402206765CBF6E871D3AF7F01AE96F06B13C90F26F54B905C5166A2C791274FC2397102200B143893586CC799FBA4DA83B119EAEA1BD80AC3CE88FCEDB3EFBD596A1F4F63" # noqa E501
)
class TestAttestationObject(unittest.TestCase):
def test_string_keys(self):
self.assertEqual(AttestationObject.KEY.FMT.string_key, "fmt")
self.assertEqual(AttestationObject.KEY.AUTH_DATA.string_key, "authData")
self.assertEqual(AttestationObject.KEY.ATT_STMT.string_key, "attStmt")
def test_fido_u2f_attestation(self):
att = AttestationObject.from_ctap1(
a2b_hex(
b"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE"
bytes.fromhex(
"1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE"
),
RegistrationData(
a2b_hex(
b"0504E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1427DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE4200383082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F530450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501
bytes.fromhex(
"0504E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1427DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE4200383082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F530450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501
)
),
)
Attestation.for_type(att.fmt)().verify(
att.att_statement,
att.att_stmt,
att.auth_data,
a2b_hex(
b"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141"
bytes.fromhex(
"687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141"
),
)
def test_packed_attestation(self):
att = AttestationObject(
a2b_hex(
b"a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d03a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de" # noqa E501
att = AttestationResponse.parse(
bytes.fromhex(
"a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d03a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de" # noqa E501
)
)
Attestation.for_type(att.fmt)().verify(
att.att_statement,
att.att_stmt,
att.auth_data,
a2b_hex(
b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF"
bytes.fromhex(
"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF"
),
)
def test_different_keys(self):
att = AttestationObject(
a2b_hex(
b"a363666d74667061636b65646761747453746d74a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de68617574684461746158c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d" # noqa E501
)
)
Attestation.for_type(att.fmt)().verify(
att.att_statement,
att.auth_data,
a2b_hex(
b"985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF"
),
)
att2 = att.with_int_keys()
self.assertNotEqual(att, att2)
self.assertEqual(att.data, att2.data)
self.assertEqual(att.with_int_keys(), att2)
self.assertEqual(att, att2.with_string_keys())
class TestCtap2(unittest.TestCase):
def mock_ctap(self):
@ -253,11 +228,11 @@ class TestCtap2(unittest.TestCase):
0x10, b"\1" + cbor.encode({1: 1, 2: 2, 3: 3, 4: 4}), mock.ANY, None
)
self.assertIsInstance(resp, AttestationObject)
self.assertEqual(resp, _MC_RESP)
self.assertIsInstance(resp, AttestationResponse)
self.assertEqual(resp, AttestationResponse.parse(_MC_RESP))
self.assertEqual(resp.fmt, "packed")
self.assertEqual(resp.auth_data, _AUTH_DATA_MC)
self.assertSetEqual(set(resp.att_statement.keys()), {"alg", "sig", "x5c"})
self.assertSetEqual(set(resp.att_stmt.keys()), {"alg", "sig", "x5c"})
def test_get_assertion(self):
ctap = self.mock_ctap()
@ -269,7 +244,7 @@ class TestCtap2(unittest.TestCase):
)
self.assertIsInstance(resp, AssertionResponse)
self.assertEqual(resp, _GA_RESP)
self.assertEqual(resp, AssertionResponse.parse(_GA_RESP))
self.assertEqual(resp.credential, _CRED)
self.assertEqual(resp.auth_data, _AUTH_DATA_GA)
self.assertEqual(resp.signature, _SIGNATURE)
@ -278,14 +253,24 @@ class TestCtap2(unittest.TestCase):
EC_PRIV = 0x7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684
EC_PUB_X = a2b_hex("44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F")
EC_PUB_Y = a2b_hex("EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9")
DEV_PUB_X = a2b_hex("0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168")
DEV_PUB_Y = a2b_hex("D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47")
SHARED = a2b_hex("c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c")
TOKEN_ENC = a2b_hex("7A9F98E31B77BE90F9C64D12E9635040")
TOKEN = a2b_hex("aff12c6dcfbf9df52f7a09211e8865cd")
PIN_HASH_ENC = a2b_hex("afe8327ce416da8ee3d057589c2ce1a9")
EC_PUB_X = bytes.fromhex(
"44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F"
)
EC_PUB_Y = bytes.fromhex(
"EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9"
)
DEV_PUB_X = bytes.fromhex(
"0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168"
)
DEV_PUB_Y = bytes.fromhex(
"D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47"
)
SHARED = bytes.fromhex(
"c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c"
)
TOKEN_ENC = bytes.fromhex("7A9F98E31B77BE90F9C64D12E9635040")
TOKEN = bytes.fromhex("aff12c6dcfbf9df52f7a09211e8865cd")
PIN_HASH_ENC = bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9")
class TestClientPin(unittest.TestCase):
@ -335,10 +320,10 @@ class TestClientPin(unittest.TestCase):
1,
3,
key_agreement={},
new_pin_enc=a2b_hex(
new_pin_enc=bytes.fromhex(
"0222fc42c6dd76a274a7057858b9b29d98e8a722ec2dc6668476168c5320473cec9907b4cd76ce7943c96ba5683943211d84471e64d9c51e54763488cd66526a" # noqa E501
),
pin_uv_param=a2b_hex("7b40c084ccc5794194189ab57836475f"),
pin_uv_param=bytes.fromhex("7b40c084ccc5794194189ab57836475f"),
)
def test_change_pin(self):
@ -353,11 +338,11 @@ class TestClientPin(unittest.TestCase):
1,
4,
key_agreement={},
new_pin_enc=a2b_hex(
new_pin_enc=bytes.fromhex(
"4280e14aac4fcbf02dd079985f0c0ffc9ea7d5f9c173fd1a4c843826f7590cb3c2d080c6923e2fe6d7a52c31ea1309d3fcca3dedae8a2ef14b6330cafc79339e" # noqa E501
),
pin_uv_param=a2b_hex("fb97e92f3724d7c85e001d7f93e6490a"),
pin_hash_enc=a2b_hex("afe8327ce416da8ee3d057589c2ce1a9"),
pin_uv_param=bytes.fromhex("fb97e92f3724d7c85e001d7f93e6490a"),
pin_hash_enc=bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9"),
)
def test_short_pin(self):

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.hid import CtapHidDevice
import unittest

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
import unittest
import mock
import sys

View File

@ -27,8 +27,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.rpid import verify_app_id, verify_rp_id
import unittest
@ -58,17 +56,6 @@ class TestAppId(unittest.TestCase):
)
)
def test_valid_ids_mixed_type(self):
self.assertTrue(
verify_app_id(b"https://example.com", "https://register.example.com")
)
self.assertTrue(
verify_app_id("https://example.com", b"https://fido.example.com")
)
self.assertTrue(
verify_app_id(b"https://example.com", b"https://www.example.com:444")
)
def test_invalid_ids(self):
self.assertFalse(verify_app_id("https://example.com", "http://example.com"))
self.assertFalse(verify_app_id("https://example.com", "http://www.example.com"))
@ -88,15 +75,6 @@ class TestAppId(unittest.TestCase):
)
)
def test_invalid_ids_mixed_type(self):
self.assertFalse(verify_app_id(b"https://example.com", "http://example.com"))
self.assertFalse(
verify_app_id("https://example.com", b"http://www.example.com")
)
self.assertFalse(
verify_app_id(b"https://example.com", b"https://example-test.com")
)
def test_effective_tld_names(self):
self.assertFalse(
verify_app_id("https://appspot.com", "https://foo.appspot.com")
@ -110,11 +88,6 @@ class TestRpId(unittest.TestCase):
self.assertTrue(verify_rp_id("example.com", "https://fido.example.com"))
self.assertTrue(verify_rp_id("example.com", "https://www.example.com:444"))
def test_valid_ids_mixed_type(self):
self.assertTrue(verify_rp_id(b"example.com", "https://register.example.com"))
self.assertTrue(verify_rp_id("example.com", b"https://fido.example.com"))
self.assertTrue(verify_rp_id(b"example.com", b"https://www.example.com:444"))
def test_invalid_ids(self):
self.assertFalse(verify_rp_id("example.com", "http://example.com"))
self.assertFalse(verify_rp_id("example.com", "http://www.example.com"))
@ -129,15 +102,10 @@ class TestRpId(unittest.TestCase):
)
)
def test_invalid_ids_mixed_type(self):
self.assertFalse(verify_rp_id(b"example.com", "http://example.com"))
self.assertFalse(verify_rp_id("example.com", b"http://www.example.com"))
self.assertFalse(verify_rp_id(b"example.com", b"https://example-test.com"))
def test_suffix_list(self):
self.assertFalse(verify_rp_id(b"co.uk", "https://foobar.co.uk"))
self.assertTrue(verify_rp_id(b"foobar.co.uk", "https://site.foobar.co.uk"))
self.assertFalse(verify_rp_id(b"appspot.com", "https://example.appspot.com"))
self.assertFalse(verify_rp_id("co.uk", "https://foobar.co.uk"))
self.assertTrue(verify_rp_id("foobar.co.uk", "https://site.foobar.co.uk"))
self.assertFalse(verify_rp_id("appspot.com", "https://example.appspot.com"))
self.assertTrue(
verify_rp_id(b"example.appspot.com", "https://example.appspot.com")
verify_rp_id("example.appspot.com", "https://example.appspot.com")
)

View File

@ -1,14 +1,14 @@
from __future__ import absolute_import, unicode_literals
import json
import unittest
from binascii import a2b_hex
import six
from fido2.client import WEBAUTHN_TYPE, ClientData
from fido2.ctap2 import AttestedCredentialData, AuthenticatorData
from fido2.server import Fido2Server, U2FFido2Server
from fido2.webauthn import PublicKeyCredentialRpEntity, UserVerificationRequirement
from fido2.webauthn import (
PublicKeyCredentialRpEntity,
UserVerificationRequirement,
AttestedCredentialData,
AuthenticatorData,
)
from .test_ctap2 import _ATT_CRED_DATA, _CRED_ID
from .utils import U2FDevice
@ -28,7 +28,7 @@ USER = {"id": b"user_id", "name": "A. User"}
class TestFido2Server(unittest.TestCase):
def test_register_begin_rp_no_icon(self):
def test_register_begin_rp(self):
rp = PublicKeyCredentialRpEntity("example.com", "Example")
server = Fido2Server(rp)
@ -38,21 +38,6 @@ class TestFido2Server(unittest.TestCase):
request["publicKey"]["rp"], {"id": "example.com", "name": "Example"}
)
def test_register_begin_rp_icon(self):
rp = PublicKeyCredentialRpEntity(
"example.com", "Example", "http://example.com/icon.svg"
)
server = Fido2Server(rp)
request, state = server.register_begin(USER)
data = {
"id": "example.com",
"name": "Example",
"icon": "http://example.com/icon.svg",
}
self.assertEqual(request["publicKey"]["rp"], data)
def test_register_begin_custom_challenge(self):
rp = PublicKeyCredentialRpEntity("example.com", "Example")
server = Fido2Server(rp)
@ -84,10 +69,10 @@ class TestFido2Server(unittest.TestCase):
"type": WEBAUTHN_TYPE.GET_ASSERTION,
}
client_data = ClientData(json.dumps(client_data_dict).encode("utf-8"))
_AUTH_DATA = a2b_hex(
_AUTH_DATA = bytes.fromhex(
"A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D"
)
with six.assertRaisesRegex(self, ValueError, "Invalid signature."):
with self.assertRaisesRegex(ValueError, "Invalid signature."):
server.authenticate_complete(
state,
[AttestedCredentialData(_ATT_CRED_DATA)],
@ -100,9 +85,7 @@ class TestFido2Server(unittest.TestCase):
class TestU2FFido2Server(unittest.TestCase):
def test_u2f(self):
rp = PublicKeyCredentialRpEntity(
"example.com", "Example", "http://example.com/icon.svg"
)
rp = PublicKeyCredentialRpEntity("example.com", "Example")
app_id = b"https://example.com"
server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp)
@ -133,9 +116,7 @@ class TestU2FFido2Server(unittest.TestCase):
)
def test_u2f_facets(self):
rp = PublicKeyCredentialRpEntity(
"example.com", "Example", "http://example.com/icon.svg"
)
rp = PublicKeyCredentialRpEntity("example.com", "Example")
app_id = b"https://www.example.com/facets.json"
def verify_u2f_origin(origin):
@ -181,9 +162,7 @@ class TestU2FFido2Server(unittest.TestCase):
authenticator_data, signature = device.sign(client_data)
with six.assertRaisesRegex(
self, ValueError, "Invalid origin in " "ClientData."
):
with self.assertRaisesRegex(ValueError, "Invalid origin in ClientData."):
server.authenticate_complete(
state,
[auth_data],

View File

@ -25,29 +25,27 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.attestation.tpm import TpmAttestationFormat, TpmPublicFormat
from binascii import a2b_hex
import unittest
class TestTpmObject(unittest.TestCase):
def test_parse_tpm(self):
data = a2b_hex(
data = bytes.fromhex(
"ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e18529c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bfdc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7" # noqa
)
tpm = TpmAttestationFormat.parse(data)
self.assertEqual(tpm.data, a2b_hex("f7c8b0cdeb31328648130a19733d6fff16e76e13"))
self.assertEqual(
tpm.data, bytes.fromhex("f7c8b0cdeb31328648130a19733d6fff16e76e13")
)
def test_parse_too_short_of_a_tpm(self):
with self.assertRaises(ValueError):
TpmAttestationFormat.parse(a2b_hex("ff5443"))
TpmAttestationFormat.parse(bytes.fromhex("ff5443"))
with self.assertRaises(ValueError) as e:
data = a2b_hex(
data = bytes.fromhex(
"ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648" # noqa
)
TpmAttestationFormat.parse(data)
@ -56,7 +54,7 @@ class TestTpmObject(unittest.TestCase):
)
def test_parse_public_ecc(self):
data = a2b_hex(
data = bytes.fromhex(
"0023000b00060472000000100010000300100020b9174cd199f77552afcffe6b1f069c032ffdc4f56068dec4e189e7967b3bf6b0002037bf8aa7d93fddb9507319141c6fa31c8e48a1c6da013603a9f6e3913d157c66" # noqa
)
TpmPublicFormat.parse(data)

View File

@ -29,7 +29,6 @@
import unittest
from binascii import a2b_hex
from fido2.utils import hmac_sha256, sha256, websafe_encode, websafe_decode
@ -38,14 +37,14 @@ class TestSha256(unittest.TestCase):
def test_sha256_vectors(self):
self.assertEqual(
sha256(b"abc"),
a2b_hex(
b"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
bytes.fromhex(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
),
)
self.assertEqual(
sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
a2b_hex(
b"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
bytes.fromhex(
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
),
)
@ -54,15 +53,15 @@ class TestHmacSha256(unittest.TestCase):
def test_hmac_sha256_vectors(self):
self.assertEqual(
hmac_sha256(b"\x0b" * 20, b"Hi There"),
a2b_hex(
b"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
bytes.fromhex(
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
),
)
self.assertEqual(
hmac_sha256(b"Jefe", b"what do ya want for nothing?"),
a2b_hex(
b"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
bytes.fromhex(
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
),
)

View File

@ -25,8 +25,6 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, unicode_literals
from fido2.webauthn import (
AuthenticatorSelectionCriteria,
PublicKeyCredentialRpEntity,
@ -72,7 +70,6 @@ class TestWebAuthnDataTypes(unittest.TestCase):
self.assertEqual(o, {"id": "example.com", "name": "Example"})
self.assertEqual(o.id, "example.com")
self.assertEqual(o.name, "Example")
self.assertIsNone(o.icon)
with self.assertRaises(TypeError):
PublicKeyCredentialRpEntity("example.com")
@ -88,7 +85,6 @@ class TestWebAuthnDataTypes(unittest.TestCase):
self.assertEqual(o.id, b"user")
self.assertEqual(o.name, "Example")
self.assertEqual(o.display_name, "Display")
self.assertIsNone(o.icon)
with self.assertRaises(TypeError):
PublicKeyCredentialUserEntity(b"user")
@ -203,17 +199,3 @@ class TestWebAuthnDataTypes(unittest.TestCase):
PublicKeyCredentialRequestOptions(
b"request_challenge", user_verification="invalid"
)
def test_update_value(self):
o = PublicKeyCredentialRpEntity("example.com", "Example")
self.assertEqual(o, {"id": "example.com", "name": "Example"})
self.assertEqual(o.id, "example.com")
self.assertEqual(o.name, "Example")
o.id = "new-id.com"
self.assertEqual(o, {"id": "new-id.com", "name": "Example"})
self.assertEqual(o.id, "new-id.com")
o["name"] = "New Name"
self.assertEqual(o, {"id": "new-id.com", "name": "New Name"})
self.assertEqual(o.name, "New Name")

View File

@ -1,7 +1,3 @@
import six
import unittest
from binascii import a2b_hex
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash
@ -9,21 +5,17 @@ from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from fido2.utils import sha256, bytes2int
from fido2.ctap2 import AuthenticatorData
from fido2.webauthn import AuthenticatorData
class U2FDevice(object):
_priv_key_bytes = a2b_hex(
_priv_key_bytes = bytes.fromhex(
"308184020100301006072a8648ce3d020106052b8104000a046d306b02010104201672f5ceb963e07d475f5db9a975b7ad42ac3bf71ccddfb6539cc651a1156a6ba144034200045a4be44eb94eebff3ed665dde31deb74a060fabd214c5f5713aa043efa805dac8f45e0e17afe2eafbd1802c413c1e485fd854af9f06ef20938398f6795f12e0e" # noqa E501
)
def __init__(self, credential_id, app_id):
if not hasattr(serialization.Encoding, "X962"):
raise unittest.SkipTest("Requires Cryptography >= 2.5")
assert isinstance(credential_id, six.binary_type)
assert isinstance(app_id, six.binary_type)
assert isinstance(credential_id, bytes)
assert isinstance(app_id, bytes)
# Note: do not use in production, no garantees is provided this is
# cryptographically safe to use.
priv_key_params = ConcatKDFHash(