mirror of https://github.com/Yubico/python-fido2
Merge branch 'drop-py2' into next-py3.
This commit is contained in:
commit
eae65b57a0
|
@ -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
|
||||
|
|
|
@ -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 +0,0 @@
|
|||
9
|
|
@ -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.
|
|
@ -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.
|
|
@ -1 +0,0 @@
|
|||
fido2 python-fido2; PEP386
|
|
@ -1,7 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export PYBUILD_NAME=fido2
|
||||
export PYBUILD_DISABLE=test
|
||||
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
|
@ -1 +0,0 @@
|
|||
3.0 (native)
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
|
@ -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": {}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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"},
|
||||
]
|
|
@ -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"
|
|
@ -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()
|
|
@ -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()
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
144
fido2/client.py
144
fido2/client.py
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
3
mypy.ini
3
mypy.ini
|
@ -3,6 +3,3 @@ files = fido2/
|
|||
|
||||
[mypy-smartcard.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-uhid_freebsd.*]
|
||||
ignore_missing_imports = True
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
)
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue