Upgrade tests to python-fido2 v2.0.0
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
@@ -20,12 +20,13 @@
|
|||||||
|
|
||||||
from http import client
|
from http import client
|
||||||
from fido2.hid import CtapHidDevice
|
from fido2.hid import CtapHidDevice
|
||||||
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend
|
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector
|
||||||
from fido2.attestation import FidoU2FAttestation
|
from fido2.attestation import FidoU2FAttestation
|
||||||
from fido2.ctap2.pin import ClientPin
|
from fido2.ctap2.pin import ClientPin
|
||||||
from fido2.server import Fido2Server
|
from fido2.server import Fido2Server
|
||||||
from fido2.ctap import CtapError
|
from fido2.ctap import CtapError
|
||||||
from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType
|
from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions
|
||||||
|
from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension
|
||||||
from utils import *
|
from utils import *
|
||||||
from fido2.cose import ES256
|
from fido2.cose import ES256
|
||||||
import sys
|
import sys
|
||||||
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
class Device():
|
class Device():
|
||||||
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
|
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
|
||||||
self.__user = None
|
self.__user = None
|
||||||
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
|
||||||
self.__set_server(rp=rp, attestation=attestation)
|
self.__set_server(rp=rp, attestation=attestation)
|
||||||
|
|
||||||
|
def __verify_rp(rp_id, origin):
|
||||||
|
return True
|
||||||
|
|
||||||
def __set_client(self, origin, user_interaction, uv):
|
def __set_client(self, origin, user_interaction, uv):
|
||||||
self.__uv = uv
|
self.__uv = uv
|
||||||
@@ -101,14 +104,23 @@ class Device():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Set up a FIDO 2 client using the origin https://example.com
|
# Set up a FIDO 2 client using the origin https://example.com
|
||||||
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
extensions = [
|
||||||
|
HmacSecretExtension(allow_hmac_secret=True),
|
||||||
|
LargeBlobExtension(),
|
||||||
|
CredBlobExtension(),
|
||||||
|
CredProtectExtension(),
|
||||||
|
MinPinLengthExtension(),
|
||||||
|
CredPropsExtension(),
|
||||||
|
ThirdPartyPaymentExtension()
|
||||||
|
]
|
||||||
|
self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions)
|
||||||
|
|
||||||
# Prefer UV if supported and configured
|
# Prefer UV if supported and configured
|
||||||
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
||||||
self.__uv = "preferred"
|
self.__uv = "preferred"
|
||||||
print("Authenticator supports User Verification")
|
print("Authenticator supports User Verification")
|
||||||
|
|
||||||
self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction)
|
||||||
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
|
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
|
||||||
self.ctap1 = self.__client1._backend.ctap1
|
self.ctap1 = self.__client1._backend.ctap1
|
||||||
|
|
||||||
@@ -117,7 +129,7 @@ class Device():
|
|||||||
self.__attestation = attestation
|
self.__attestation = attestation
|
||||||
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
||||||
self.__server.allowed_algorithms = [
|
self.__server.allowed_algorithms = [
|
||||||
PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
|
PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
|
||||||
for p in self.__client._backend.info.algorithms
|
for p in self.__client._backend.info.algorithms
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -216,9 +228,7 @@ class Device():
|
|||||||
'key_params':key_params}}
|
'key_params':key_params}}
|
||||||
|
|
||||||
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
|
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
|
||||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||||
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
|
||||||
)
|
|
||||||
rp = rp if rp is not Ellipsis else self.__rp
|
rp = rp if rp is not Ellipsis else self.__rp
|
||||||
user = user if user is not Ellipsis else self.user()
|
user = user if user is not Ellipsis else self.user()
|
||||||
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
|
||||||
@@ -226,22 +236,31 @@ class Device():
|
|||||||
client = self.__client1
|
client = self.__client1
|
||||||
else:
|
else:
|
||||||
client = self.__client
|
client = self.__client
|
||||||
result = client._backend.do_make_credential(
|
options=PublicKeyCredentialCreationOptions(
|
||||||
client_data=client_data,
|
rp=PublicKeyCredentialRpEntity.from_dict(rp),
|
||||||
rp=rp,
|
user=PublicKeyCredentialUserEntity.from_dict(user),
|
||||||
user=user,
|
pub_key_cred_params=key_params,
|
||||||
key_params=key_params,
|
exclude_credentials=exclude_list,
|
||||||
exclude_list=exclude_list,
|
|
||||||
extensions=extensions,
|
extensions=extensions,
|
||||||
rk=rk,
|
challenge=os.urandom(32),
|
||||||
user_verification=user_verification,
|
authenticator_selection=AuthenticatorSelectionCriteria(
|
||||||
enterprise_attestation=enterprise_attestation,
|
require_resident_key=rk,
|
||||||
|
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED
|
||||||
|
),
|
||||||
|
attestation=enterprise_attestation
|
||||||
|
)
|
||||||
|
client_data, rp_id = client_data.collect_client_data(options=options)
|
||||||
|
result = client._backend.do_make_credential(
|
||||||
|
options=options,
|
||||||
|
client_data=client_data,
|
||||||
|
rp_id=rp_id,
|
||||||
|
enterprise_rpid_list=None,
|
||||||
event=event
|
event=event
|
||||||
)
|
)
|
||||||
return {'res':result,'req':{'client_data':client_data,
|
return {'res':result.response,'req':{'client_data':client_data,
|
||||||
'rp':rp,
|
'rp':rp,
|
||||||
'user':user,
|
'user':user,
|
||||||
'key_params':key_params}}
|
'key_params':key_params},'client_extension_results':result.client_extension_results}
|
||||||
|
|
||||||
def try_make_credential(self, options=None):
|
def try_make_credential(self, options=None):
|
||||||
if (options is None):
|
if (options is None):
|
||||||
@@ -267,14 +286,14 @@ class Device():
|
|||||||
|
|
||||||
# Complete registration
|
# Complete registration
|
||||||
auth_data = self.__server.register_complete(
|
auth_data = self.__server.register_complete(
|
||||||
state, result.client_data, result.attestation_object
|
state=state, response=result
|
||||||
)
|
)
|
||||||
credentials = [auth_data.credential_data]
|
credentials = [auth_data.credential_data]
|
||||||
|
|
||||||
print("New credential created!")
|
print("New credential created!")
|
||||||
|
|
||||||
print("CLIENT DATA:", result.client_data)
|
print("CLIENT DATA:", result.response.client_data)
|
||||||
print("ATTESTATION OBJECT:", result.attestation_object)
|
print("ATTESTATION OBJECT:", result.response.attestation_object)
|
||||||
print()
|
print()
|
||||||
print("CREDENTIAL DATA:", auth_data.credential_data)
|
print("CREDENTIAL DATA:", auth_data.credential_data)
|
||||||
|
|
||||||
@@ -294,17 +313,14 @@ class Device():
|
|||||||
self.__server.authenticate_complete(
|
self.__server.authenticate_complete(
|
||||||
state,
|
state,
|
||||||
credentials,
|
credentials,
|
||||||
result.credential_id,
|
result
|
||||||
result.client_data,
|
|
||||||
result.authenticator_data,
|
|
||||||
result.signature,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Credential authenticated!")
|
print("Credential authenticated!")
|
||||||
|
|
||||||
print("CLIENT DATA:", result.client_data)
|
print("CLIENT DATA:", result.response.client_data)
|
||||||
print()
|
print()
|
||||||
print("AUTH DATA:", result.authenticator_data)
|
print("AUTH DATA:", result.response.authenticator_data)
|
||||||
|
|
||||||
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
|
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
|
||||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||||
@@ -325,21 +341,31 @@ class Device():
|
|||||||
return self.__client._backend.ctap2.get_next_assertion()
|
return self.__client._backend.ctap2.get_next_assertion()
|
||||||
|
|
||||||
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
|
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
|
||||||
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
|
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
|
||||||
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
|
if (ctap1 is True):
|
||||||
)
|
client = self.__client1
|
||||||
|
else:
|
||||||
|
client = self.__client
|
||||||
|
|
||||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||||
|
options=PublicKeyCredentialRequestOptions(
|
||||||
|
challenge=os.urandom(32),
|
||||||
|
rp_id=rp_id,
|
||||||
|
allow_credentials=allow_list,
|
||||||
|
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED,
|
||||||
|
extensions=extensions
|
||||||
|
)
|
||||||
|
client_data, rp_id = client_data.collect_client_data(options=options)
|
||||||
|
|
||||||
if (ctap1 is True):
|
if (ctap1 is True):
|
||||||
client = self.__client1
|
client = self.__client1
|
||||||
else:
|
else:
|
||||||
client = self.__client
|
client = self.__client
|
||||||
try:
|
try:
|
||||||
result = client._backend.do_get_assertion(
|
result = client._backend.do_get_assertion(
|
||||||
|
options=options,
|
||||||
client_data=client_data,
|
client_data=client_data,
|
||||||
rp_id=rp_id,
|
rp_id=rp_id,
|
||||||
allow_list=allow_list,
|
|
||||||
extensions=extensions,
|
|
||||||
user_verification=user_verification,
|
|
||||||
event=event
|
event=event
|
||||||
)
|
)
|
||||||
except ClientError as e:
|
except ClientError as e:
|
||||||
@@ -347,11 +373,9 @@ class Device():
|
|||||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||||
client_pin.set_pin(DEFAULT_PIN)
|
client_pin.set_pin(DEFAULT_PIN)
|
||||||
result = client._backend.do_get_assertion(
|
result = client._backend.do_get_assertion(
|
||||||
|
options=options,
|
||||||
client_data=client_data,
|
client_data=client_data,
|
||||||
rp_id=rp_id,
|
rp_id=rp_id,
|
||||||
allow_list=allow_list,
|
|
||||||
extensions=extensions,
|
|
||||||
user_verification=user_verification,
|
|
||||||
event=event
|
event=event
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
|
|||||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
], *args)
|
], *args)
|
||||||
aut_data = res['res'].get_response(0)
|
aut_data = res['res'].get_response(0)
|
||||||
m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
|
m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash
|
||||||
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
|
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
|
||||||
return aut_data
|
return aut_data
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
@pytest.fixture(scope="class")
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ RUN apt install -y libccid \
|
|||||||
cmake \
|
cmake \
|
||||||
libfuse-dev \
|
libfuse-dev \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
|
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
|
||||||
RUN git clone https://github.com/polhenarejos/python-fido2.git
|
|
||||||
WORKDIR /python-fido2
|
|
||||||
RUN git checkout development
|
|
||||||
RUN pip3 install .
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
||||||
WORKDIR /vsmartcard/virtualsmartcard
|
WORKDIR /vsmartcard/virtualsmartcard
|
||||||
|
|||||||
@@ -27,16 +27,17 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from .base import HidDescriptor
|
import logging
|
||||||
from ..ctap import CtapDevice, CtapError, STATUS
|
import os
|
||||||
from ..utils import LOG_LEVEL_TRAFFIC
|
|
||||||
from threading import Event
|
|
||||||
from enum import IntEnum, IntFlag, unique
|
|
||||||
from typing import Tuple, Optional, Callable, Iterator
|
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import os
|
from enum import IntEnum, IntFlag, unique
|
||||||
import logging
|
from threading import Event
|
||||||
|
from typing import Callable, Iterator
|
||||||
|
|
||||||
|
from ..ctap import STATUS, CtapDevice, CtapError
|
||||||
|
from ..utils import LOG_LEVEL_TRAFFIC
|
||||||
|
from .base import HidDescriptor
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
|
|||||||
from . import openbsd as backend
|
from . import openbsd as backend
|
||||||
else:
|
else:
|
||||||
raise Exception("Unsupported platform")
|
raise Exception("Unsupported platform")
|
||||||
|
|
||||||
from . import emulation as backend
|
from . import emulation as backend
|
||||||
|
|
||||||
list_descriptors = backend.list_descriptors
|
list_descriptors = backend.list_descriptors
|
||||||
@@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor
|
|||||||
open_connection = backend.open_connection
|
open_connection = backend.open_connection
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionFailure(Exception):
|
||||||
|
"""The CTAP connection failed or returned an invalid response."""
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
class CTAPHID(IntEnum):
|
class CTAPHID(IntEnum):
|
||||||
PING = 0x01
|
PING = 0x01
|
||||||
@@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
response = self.call(CTAPHID.INIT, nonce)
|
response = self.call(CTAPHID.INIT, nonce)
|
||||||
r_nonce, response = response[:8], response[8:]
|
r_nonce, response = response[:8], response[8:]
|
||||||
if r_nonce != nonce:
|
if r_nonce != nonce:
|
||||||
raise Exception("Wrong nonce")
|
raise ConnectionFailure("Wrong nonce")
|
||||||
(
|
(
|
||||||
self._channel_id,
|
self._channel_id,
|
||||||
self._u2fhid_version,
|
self._u2fhid_version,
|
||||||
@@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
return self._u2fhid_version
|
return self._u2fhid_version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_version(self) -> Tuple[int, int, int]:
|
def device_version(self) -> tuple[int, int, int]:
|
||||||
"""Device version number."""
|
"""Device version number."""
|
||||||
return self._device_version
|
return self._device_version
|
||||||
|
|
||||||
@@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice):
|
|||||||
return self._capabilities
|
return self._capabilities
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def product_name(self) -> Optional[str]:
|
def product_name(self) -> str | None:
|
||||||
"""Product name of device."""
|
"""Product name of device."""
|
||||||
return self.descriptor.product_name
|
return self.descriptor.product_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serial_number(self) -> Optional[str]:
|
def serial_number(self) -> str | None:
|
||||||
"""Serial number of device."""
|
"""Serial number of device."""
|
||||||
return self.descriptor.serial_number
|
return self.descriptor.serial_number
|
||||||
|
|
||||||
@@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice):
|
|||||||
self,
|
self,
|
||||||
cmd: int,
|
cmd: int,
|
||||||
data: bytes = b"",
|
data: bytes = b"",
|
||||||
event: Optional[Event] = None,
|
event: Event | None = None,
|
||||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
on_keepalive: Callable[[STATUS], None] | None = None,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
event = event or Event()
|
event = event or Event()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
return self._do_call(cmd, data, event, on_keepalive)
|
||||||
|
except CtapError as e:
|
||||||
|
if e.code == CtapError.ERR.CHANNEL_BUSY:
|
||||||
|
if not event.wait(0.1):
|
||||||
|
logger.warning("CTAP channel busy, trying again...")
|
||||||
|
continue # Keep retrying on BUSY while not cancelled
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _do_call(self, cmd, data, event, on_keepalive):
|
||||||
remaining = data
|
remaining = data
|
||||||
seq = 0
|
seq = 0
|
||||||
|
|
||||||
@@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
r_channel = struct.unpack_from(">I", recv)[0]
|
r_channel = struct.unpack_from(">I", recv)[0]
|
||||||
recv = recv[4:]
|
recv = recv[4:]
|
||||||
if r_channel != self._channel_id:
|
if r_channel != self._channel_id:
|
||||||
raise Exception("Wrong channel")
|
raise ConnectionFailure("Wrong channel")
|
||||||
|
|
||||||
if not response: # Initialization packet
|
if not response: # Initialization packet
|
||||||
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
r_cmd, r_len = struct.unpack_from(">BH", recv)
|
||||||
@@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice):
|
|||||||
if r_cmd == TYPE_INIT | cmd:
|
if r_cmd == TYPE_INIT | cmd:
|
||||||
pass # first data packet
|
pass # first data packet
|
||||||
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
|
||||||
ka_status = struct.unpack_from(">B", recv)[0]
|
try:
|
||||||
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
ka_status = STATUS(struct.unpack_from(">B", recv)[0])
|
||||||
|
logger.debug(f"Got keepalive status: {ka_status:02x}")
|
||||||
|
except ValueError:
|
||||||
|
raise ConnectionFailure("Invalid keepalive status")
|
||||||
if on_keepalive and ka_status != last_ka:
|
if on_keepalive and ka_status != last_ka:
|
||||||
try:
|
|
||||||
ka_status = STATUS(ka_status)
|
|
||||||
except ValueError:
|
|
||||||
pass # Unknown status value
|
|
||||||
last_ka = ka_status
|
last_ka = ka_status
|
||||||
on_keepalive(ka_status)
|
on_keepalive(ka_status)
|
||||||
continue
|
continue
|
||||||
@@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice):
|
|||||||
r_seq = struct.unpack_from(">B", recv)[0]
|
r_seq = struct.unpack_from(">B", recv)[0]
|
||||||
recv = recv[1:]
|
recv = recv[1:]
|
||||||
if r_seq != seq:
|
if r_seq != seq:
|
||||||
raise Exception("Wrong sequence number")
|
raise ConnectionFailure("Wrong sequence number")
|
||||||
seq += 1
|
seq += 1
|
||||||
|
|
||||||
response += recv
|
response += recv
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
from fido2.client import CtapError
|
from fido2.client import CtapError
|
||||||
from fido2.cose import ES256, ES384, ES512, EdDSA
|
from fido2.cose import ES256, ES384, ES512, EdDSA
|
||||||
import fido2.features
|
|
||||||
fido2.features.webauthn_json_mapping.enabled = False
|
|
||||||
from utils import ES256K
|
from utils import ES256K
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
|
|||||||
|
|
||||||
def test_missing_user(device):
|
def test_missing_user(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user=None)
|
device.MC(user=None)
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
def test_bad_type_user_user(device):
|
def test_bad_type_user_user(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user=b"12345678")
|
device.MC(user=b"12345678")
|
||||||
|
|
||||||
def test_missing_rp(device):
|
def test_missing_rp(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
|
|||||||
|
|
||||||
def test_missing_pubKeyCredParams(device):
|
def test_missing_pubKeyCredParams(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(key_params=None)
|
device.MC(key_params=None)
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
@@ -93,35 +91,23 @@ def test_bad_type_options(device):
|
|||||||
|
|
||||||
def test_bad_type_rp_name(device):
|
def test_bad_type_rp_name(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
|
device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"})
|
||||||
|
|
||||||
def test_bad_type_rp_id(device):
|
def test_bad_type_rp_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
|
device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
|
||||||
|
|
||||||
def test_bad_type_rp_icon(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
|
|
||||||
|
|
||||||
def test_bad_type_user_name(device):
|
def test_bad_type_user_name(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user={"id": b"user_id", "name": 8})
|
device.MC(user={"id": b"user_id", "name": 8})
|
||||||
|
|
||||||
def test_bad_type_user_id(device):
|
def test_bad_type_user_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user={"id": "user_id", "name": "name"})
|
device.MC(user={"id": "user_id", "name": "name"})
|
||||||
|
|
||||||
def test_bad_type_user_displayName(device):
|
def test_bad_type_user_displayName(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
|
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
|
||||||
|
|
||||||
def test_bad_type_user_icon(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
|
|
||||||
|
|
||||||
def test_bad_type_pubKeyCredParams(device):
|
|
||||||
with pytest.raises(CtapError) as e:
|
|
||||||
device.doMC(key_params=["wrong"])
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
|
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
|
||||||
@@ -132,13 +118,13 @@ def test_algorithms(device, info, alg):
|
|||||||
|
|
||||||
def test_missing_pubKeyCredParams_type(device):
|
def test_missing_pubKeyCredParams_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
|
device.MC(key_params=[{"alg": ES256.ALGORITHM}])
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.INVALID_CBOR
|
assert e.value.code == CtapError.ERR.INVALID_CBOR
|
||||||
|
|
||||||
def test_missing_pubKeyCredParams_alg(device):
|
def test_missing_pubKeyCredParams_alg(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(key_params=[{"type": "public-key"}])
|
device.MC(key_params=[{"type": "public-key"}])
|
||||||
|
|
||||||
assert e.value.code in [
|
assert e.value.code in [
|
||||||
CtapError.ERR.INVALID_CBOR,
|
CtapError.ERR.INVALID_CBOR,
|
||||||
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
|
|||||||
|
|
||||||
def test_bad_type_pubKeyCredParams_alg(device):
|
def test_bad_type_pubKeyCredParams_alg(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
|
device.MC(key_params=[{"alg": "7", "type": "public-key"}])
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
|
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
|
||||||
|
|
||||||
@@ -158,26 +144,26 @@ def test_unsupported_algorithm(device):
|
|||||||
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
||||||
|
|
||||||
def test_exclude_list(resetdevice):
|
def test_exclude_list(resetdevice):
|
||||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
|
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}])
|
||||||
|
|
||||||
def test_exclude_list2(resetdevice):
|
def test_exclude_list2(resetdevice):
|
||||||
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
|
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
|
||||||
|
|
||||||
def test_bad_type_exclude_list(device):
|
def test_bad_type_exclude_list(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(exclude_list=["1234"])
|
device.MC(exclude_list=["1234"])
|
||||||
|
|
||||||
def test_missing_exclude_list_type(device):
|
def test_missing_exclude_list_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(exclude_list=[{"id": b"1234"}])
|
device.MC(exclude_list=[{"id": b"1234"}])
|
||||||
|
|
||||||
def test_missing_exclude_list_id(device):
|
def test_missing_exclude_list_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(exclude_list=[{"type": "public-key"}])
|
device.MC(exclude_list=[{"type": "public-key"}])
|
||||||
|
|
||||||
def test_bad_type_exclude_list_id(device):
|
def test_bad_type_exclude_list_id(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
|
device.MC(exclude_list=[{"type": "public-key", "id": "1234"}])
|
||||||
|
|
||||||
def test_bad_type_exclude_list_type(device):
|
def test_bad_type_exclude_list_type(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ def test_authenticate(device):
|
|||||||
AUTRes = device.authenticate(credentials)
|
AUTRes = device.authenticate(credentials)
|
||||||
|
|
||||||
def test_assertion_auth_data(GARes):
|
def test_assertion_auth_data(GARes):
|
||||||
assert len(GARes['res'].get_response(0).authenticator_data) == 37
|
assert len(GARes['res'].get_response(0).response.authenticator_data) == 37
|
||||||
|
|
||||||
def test_Check_that_AT_flag_is_not_set(GARes):
|
def test_Check_that_AT_flag_is_not_set(GARes):
|
||||||
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
|
assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0
|
||||||
|
|
||||||
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
|
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
|
||||||
res = device.GA(allow_list=[
|
res = device.GA(allow_list=[
|
||||||
@@ -63,8 +63,8 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
|
|||||||
""" Check that authenticator filters and stores items in allow list correctly """
|
""" Check that authenticator filters and stores items in allow list correctly """
|
||||||
allow_list = []
|
allow_list = []
|
||||||
|
|
||||||
rp1 = {"id": "rp1.com", "name": "rp1.com"}
|
rp1 = {"id": "example.com", "name": "rp1.com"}
|
||||||
rp2 = {"id": "rp2.com", "name": "rp2.com"}
|
rp2 = {"id": "example.com", "name": "rp2.com"}
|
||||||
|
|
||||||
rp1_registrations = []
|
rp1_registrations = []
|
||||||
rp2_registrations = []
|
rp2_registrations = []
|
||||||
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
|
|||||||
rp_id += ".com"
|
rp_id += ".com"
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(rp_id=rp_id)
|
device.GA(rp_id=rp_id)
|
||||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||||
|
|
||||||
def test_missing_rp(device):
|
def test_missing_rp(device):
|
||||||
@@ -137,7 +137,7 @@ def test_missing_rp(device):
|
|||||||
|
|
||||||
def test_bad_rp(device):
|
def test_bad_rp(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(rp_id={"id": {"type": "wrong"}})
|
device.GA(rp_id={"id": {"type": "wrong"}})
|
||||||
|
|
||||||
def test_missing_cdh(device):
|
def test_missing_cdh(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
|
|||||||
|
|
||||||
def test_bad_allow_list(device):
|
def test_bad_allow_list(device):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(allow_list={"type": "wrong"})
|
device.GA(allow_list={"type": "wrong"})
|
||||||
|
|
||||||
def test_bad_allow_list_item(device, MCRes):
|
def test_bad_allow_list_item(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(allow_list=["wrong"] + [
|
device.GA(allow_list=["wrong"] + [
|
||||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -177,7 +177,7 @@ def test_option_up(device, info, GARes):
|
|||||||
assert res.auth_data.flags & (1 << 0)
|
assert res.auth_data.flags & (1 << 0)
|
||||||
|
|
||||||
def test_allow_list_fake_item(device, MCRes):
|
def test_allow_list_fake_item(device, MCRes):
|
||||||
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
|
device.GA(allow_list=[{"type": "rot13", "id": b"1234"}]
|
||||||
+ [
|
+ [
|
||||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
],
|
],
|
||||||
@@ -185,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes):
|
|||||||
|
|
||||||
def test_allow_list_missing_field(device, MCRes):
|
def test_allow_list_missing_field(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(allow_list=[{"id": b"1234"}] + [
|
device.GA(allow_list=[{"id": b"1234"}] + [
|
||||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -200,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
|
|||||||
|
|
||||||
def test_allow_list_id_wrong_type(device, MCRes):
|
def test_allow_list_id_wrong_type(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
|
device.GA(allow_list=[{"type": "public-key", "id": 42}]
|
||||||
+ [
|
+ [
|
||||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
]
|
]
|
||||||
@@ -208,7 +208,7 @@ def test_allow_list_id_wrong_type(device, MCRes):
|
|||||||
|
|
||||||
def test_allow_list_missing_id(device, MCRes):
|
def test_allow_list_missing_id(device, MCRes):
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doGA(allow_list=[{"type": "public-key"}] + [
|
device.GA(allow_list=[{"type": "public-key"}] + [
|
||||||
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
|
|||||||
auths = []
|
auths = []
|
||||||
regs = []
|
regs = []
|
||||||
# Use unique RP to not collide with other credentials
|
# Use unique RP to not collide with other credentials
|
||||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
rp = {"id": "example.com", "name": "Example"}
|
||||||
for i in range(0, 3):
|
for i in range(0, 3):
|
||||||
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||||
regs.append(res)
|
regs.append(res)
|
||||||
@@ -116,7 +116,7 @@ def test_rk_maximum_size_nodisplay(device):
|
|||||||
auths = resGA.get_assertions()
|
auths = resGA.get_assertions()
|
||||||
|
|
||||||
user_max_GA = auths[0]
|
user_max_GA = auths[0]
|
||||||
print(auths)
|
|
||||||
for y in ("name", "displayName", "id"):
|
for y in ("name", "displayName", "id"):
|
||||||
if (y in user_max_GA):
|
if (y in user_max_GA):
|
||||||
assert user_max_GA.user[y] == user_max[y]
|
assert user_max_GA.user[y] == user_max[y]
|
||||||
@@ -126,7 +126,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
|||||||
"""
|
"""
|
||||||
Test maximum returned capacity of the RK for the given RP
|
Test maximum returned capacity of the RK for the given RP
|
||||||
"""
|
"""
|
||||||
|
device.reset()
|
||||||
# Try to determine from get_info, or default to 19.
|
# Try to determine from get_info, or default to 19.
|
||||||
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
RK_CAPACITY_PER_RP = info.max_creds_in_list
|
||||||
if not RK_CAPACITY_PER_RP:
|
if not RK_CAPACITY_PER_RP:
|
||||||
@@ -140,7 +140,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
# Use unique RP to not collide with other credentials from other tests.
|
# Use unique RP to not collide with other credentials from other tests.
|
||||||
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
|
rp = {"id": "example.com", "name": "Example"}
|
||||||
|
|
||||||
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
|
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
|
||||||
# res = device.sendGA(*req.toGA())
|
# res = device.sendGA(*req.toGA())
|
||||||
@@ -183,10 +183,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
rk_rp = {"id": "rk-cred.org", "name": "Example"}
|
rk_rp = {"id": "rk-cred.org", "name": "Example"}
|
||||||
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
|
rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res']
|
||||||
|
|
||||||
server_rp = {"id": "server-cred.com", "name": "Example"}
|
server_rp = {"id": "server-cred.com", "name": "Example"}
|
||||||
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
|
server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res']
|
||||||
|
|
||||||
allow_list_with_different_rp_cred = [
|
allow_list_with_different_rp_cred = [
|
||||||
{
|
{
|
||||||
@@ -197,7 +197,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
|
|||||||
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
|
res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
|
||||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||||
|
|
||||||
|
|
||||||
@@ -208,10 +208,10 @@ def test_same_userId_overwrites_rk(resetdevice):
|
|||||||
rp = {"id": "overwrite.org", "name": "Example"}
|
rp = {"id": "overwrite.org", "name": "Example"}
|
||||||
user = generate_random_user()
|
user = generate_random_user()
|
||||||
|
|
||||||
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
|
||||||
|
|
||||||
# Should overwrite the first credential.
|
# Should overwrite the first credential.
|
||||||
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
|
mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
|
||||||
|
|
||||||
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
|
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
|
||||||
|
|
||||||
@@ -227,7 +227,7 @@ def test_larger_icon_than_128(device):
|
|||||||
user = generate_random_user()
|
user = generate_random_user()
|
||||||
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
|
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
|
||||||
|
|
||||||
device.doMC(rp = rp, rk=True, user = user)
|
device.MC(rp = rp, options={"rk":True}, user = user)
|
||||||
|
|
||||||
|
|
||||||
def test_returned_credential(device):
|
def test_returned_credential(device):
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from fido2.ctap import CtapError
|
from fido2.ctap import CtapError
|
||||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||||
|
from fido2.utils import websafe_decode
|
||||||
from utils import verify
|
from utils import verify
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def MCLBK(device):
|
def MCLBK(device):
|
||||||
res = device.doMC(
|
mc = device.doMC(
|
||||||
rk=True,
|
rk=True,
|
||||||
extensions={'largeBlob':{'support':'required'}}
|
extensions={'largeBlob':{'support':'required'}}
|
||||||
)['res']
|
)
|
||||||
return res
|
res = mc['res']
|
||||||
|
ext = mc['client_extension_results']
|
||||||
|
return {'res': res, 'ext': ext}
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBRead(device, MCLBK):
|
def GALBRead(device, MCLBK):
|
||||||
res = device.doGA(
|
res = device.doGA(
|
||||||
allow_list=[
|
allow_list=[
|
||||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
],extensions={'largeBlob':{'read': True}}
|
],extensions={'largeBlob':{'read': True}}
|
||||||
)
|
)
|
||||||
assertions = res['res'].get_assertions()
|
assertions = res['res'].get_assertions()
|
||||||
for a in assertions:
|
for a in assertions:
|
||||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
|
||||||
return res['res']
|
return res['res']
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
|
|||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBReadLB(GALBRead):
|
def GALBReadLB(GALBRead):
|
||||||
|
print(GALBRead.get_response(0))
|
||||||
return GALBRead.get_response(0)
|
return GALBRead.get_response(0)
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def GALBWrite(device, MCLBK):
|
def GALBWrite(device, MCLBK):
|
||||||
res = device.doGA(
|
res = device.doGA(
|
||||||
allow_list=[
|
allow_list=[
|
||||||
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
||||||
)
|
)
|
||||||
assertions = res['res'].get_assertions()
|
assertions = res['res'].get_assertions()
|
||||||
for a in assertions:
|
for a in assertions:
|
||||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
|
||||||
return res['res'].get_response(0)
|
return res['res'].get_response(0)
|
||||||
|
|
||||||
def test_supports_credblob(info):
|
def test_supports_credblob(info):
|
||||||
@@ -136,15 +140,17 @@ def test_supports_largeblobs(info):
|
|||||||
assert info.max_large_blob is None or (info.max_large_blob > 1024)
|
assert info.max_large_blob is None or (info.max_large_blob > 1024)
|
||||||
|
|
||||||
def test_get_largeblobkey_mc(MCLBK):
|
def test_get_largeblobkey_mc(MCLBK):
|
||||||
assert 'supported' in MCLBK.extension_results
|
assert 'largeBlob' in MCLBK['ext']
|
||||||
assert MCLBK.extension_results['supported'] is True
|
assert 'supported' in MCLBK['ext']['largeBlob']
|
||||||
|
assert MCLBK['ext']['largeBlob']['supported'] is True
|
||||||
|
|
||||||
def test_get_largeblobkey_ga(GALBReadLBK):
|
def test_get_largeblobkey_ga(GALBReadLBK):
|
||||||
assert GALBReadLBK.large_blob_key is not None
|
assert GALBReadLBK.large_blob_key is not None
|
||||||
|
|
||||||
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
|
||||||
assert 'written' in GALBWrite.extension_results
|
assert 'largeBlob' in GALBWrite.client_extension_results
|
||||||
assert GALBWrite.extension_results['written'] is True
|
assert 'written' in GALBWrite.client_extension_results['largeBlob']
|
||||||
|
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
|
||||||
|
|
||||||
assert 'blob' in GALBReadLB.extension_results
|
assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
|
||||||
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
|
assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
|
|||||||
]
|
]
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
|
device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
|
||||||
|
|
||||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||||
|
|
||||||
@@ -123,10 +123,10 @@ def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
# works
|
# works
|
||||||
res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
|
res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
|
||||||
assert res1.number_of_credentials in (None, 2)
|
assert res1.number_of_credentials in (None, 2)
|
||||||
|
|
||||||
results = device.doGA(allow_list=allow_list)['res'].get_assertions()
|
results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
|
||||||
|
|
||||||
# the required credProtect is not returned.
|
# the required credProtect is not returned.
|
||||||
for res in results:
|
for res in results:
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension
|
|||||||
from fido2.utils import hmac_sha256
|
from fido2.utils import hmac_sha256
|
||||||
from fido2.ctap2.pin import PinProtocolV2
|
from fido2.ctap2.pin import PinProtocolV2
|
||||||
from fido2.webauthn import UserVerificationRequirement
|
from fido2.webauthn import UserVerificationRequirement
|
||||||
|
from fido2.client import ClientError
|
||||||
from utils import *
|
from utils import *
|
||||||
|
|
||||||
salt1 = b"\xa5" * 32
|
salt1 = b"\xa5" * 32
|
||||||
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
|
|||||||
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
|
||||||
return res['res'].attestation_object
|
return res['res'].attestation_object
|
||||||
|
|
||||||
@pytest.fixture(scope="class")
|
|
||||||
def hmac(resetdevice):
|
|
||||||
return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
|
|
||||||
|
|
||||||
def test_hmac_secret_make_credential(MCHmacSecret):
|
def test_hmac_secret_make_credential(MCHmacSecret):
|
||||||
assert MCHmacSecret.auth_data.extensions
|
assert MCHmacSecret.auth_data.extensions
|
||||||
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
|
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
|
||||||
@@ -55,51 +52,51 @@ def test_fake_extension(device):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||||
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
|
def test_hmac_secret_entropy(device, MCHmacSecret, salts
|
||||||
):
|
):
|
||||||
hout = {'salt1':salts[0]}
|
hout = {'salt1':salts[0]}
|
||||||
if (len(salts) > 1):
|
if (len(salts) > 1):
|
||||||
hout['salt2'] = salts[1]
|
hout['salt2'] = salts[1]
|
||||||
|
|
||||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
ext = auth.extension_results
|
ext = auth.client_extension_results
|
||||||
assert ext
|
assert ext
|
||||||
assert "hmacGetSecret" in ext
|
assert "hmacGetSecret" in ext
|
||||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||||
|
|
||||||
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
|
#print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']))
|
||||||
if len(salts) == 1:
|
if len(salts) == 1:
|
||||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
|
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||||
if len(salts) == 2:
|
if len(salts) == 2:
|
||||||
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
|
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
|
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
|
||||||
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
|
assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5
|
||||||
|
|
||||||
def get_output(device, MCHmacSecret, hmac, salts):
|
def get_output(device, MCHmacSecret, salts):
|
||||||
hout = {'salt1':salts[0]}
|
hout = {'salt1':salts[0]}
|
||||||
if (len(salts) > 1):
|
if (len(salts) > 1):
|
||||||
hout['salt2'] = salts[1]
|
hout['salt2'] = salts[1]
|
||||||
|
|
||||||
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
|
|
||||||
ext = auth.extension_results
|
ext = auth.client_extension_results
|
||||||
assert ext
|
assert ext
|
||||||
assert "hmacGetSecret" in ext
|
assert "hmacGetSecret" in ext
|
||||||
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
|
||||||
|
|
||||||
if len(salts) == 2:
|
if len(salts) == 2:
|
||||||
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
|
return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
|
||||||
else:
|
else:
|
||||||
return ext["hmacGetSecret"]['output1']
|
return ext.hmac_get_secret.output1
|
||||||
|
|
||||||
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
def test_hmac_secret_sanity(device, MCHmacSecret):
|
||||||
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
|
output1 = get_output(device, MCHmacSecret, (salt1,))
|
||||||
output12 = get_output(
|
output12 = get_output(
|
||||||
device, MCHmacSecret, hmac, (salt1, salt2)
|
device, MCHmacSecret, (salt1, salt2)
|
||||||
)
|
)
|
||||||
output21 = get_output(
|
output21 = get_output(
|
||||||
device, MCHmacSecret, hmac, (salt2, salt1)
|
device, MCHmacSecret, (salt2, salt1)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert output12[0] == output1
|
assert output12[0] == output1
|
||||||
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
|
|||||||
assert output21[0] == output12[1]
|
assert output21[0] == output12[1]
|
||||||
assert output12[0] != output12[1]
|
assert output12[0] != output12[1]
|
||||||
|
|
||||||
def test_missing_keyAgreement(device, hmac):
|
def test_missing_keyAgreement(device):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError):
|
with pytest.raises(CtapError):
|
||||||
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
|
device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}})
|
||||||
|
|
||||||
def test_missing_saltAuth(device, hmac):
|
def test_missing_saltAuth(device):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
|
device.GA(extensions={"hmac-secret": {2: b'1234'}})
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
def test_missing_saltEnc(device, hmac):
|
def test_missing_saltEnc(device,):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
|
device.GA(extensions={"hmac-secret": { 3: b'1234'}})
|
||||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||||
|
|
||||||
def test_bad_auth(device, hmac, MCHmacSecret):
|
def test_bad_auth(device, MCHmacSecret):
|
||||||
|
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
|
key_agreement = {
|
||||||
bad_auth = list(hout[3][:])
|
1: 2,
|
||||||
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
|
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
|
||||||
bad_auth = bytes(bad_auth)
|
-1: 1,
|
||||||
|
-2: b'\x00'*32,
|
||||||
|
-3: b'\x00'*32,
|
||||||
|
}
|
||||||
|
|
||||||
with pytest.raises(CtapError) as e:
|
with pytest.raises(CtapError) as e:
|
||||||
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
|
device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}})
|
||||||
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
|
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
|
||||||
|
|
||||||
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
|
||||||
def test_invalid_salt_length(device, hmac, salts):
|
def test_invalid_salt_length(device, salts):
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises((CtapError,ClientError)) as e:
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
|
|
||||||
device.doGA(extensions={"hmacGetSecret": hout})
|
device.doGA(extensions={"hmacGetSecret": hout})
|
||||||
|
|
||||||
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
|
||||||
def test_get_next_assertion_has_extension(
|
def test_get_next_assertion_has_extension(
|
||||||
device, hmac, salts
|
device, salts
|
||||||
):
|
):
|
||||||
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
accounts = 3
|
accounts = 3
|
||||||
regs = []
|
regs = []
|
||||||
auths = []
|
auths = []
|
||||||
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
|
rp = {"id": f"example.com", "name": "ExampleRP_2"}
|
||||||
fixed_users = [generate_random_user() for _ in range(accounts)]
|
fixed_users = [generate_random_user() for _ in range(accounts)]
|
||||||
for i in range(accounts):
|
for i in range(accounts):
|
||||||
res = device.doMC(extensions={"hmacCreateSecret": True},
|
res = device.doMC(extensions={"hmacCreateSecret": True},
|
||||||
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
|
|||||||
assert "hmac-secret" in ext
|
assert "hmac-secret" in ext
|
||||||
assert isinstance(ext["hmac-secret"], bytes)
|
assert isinstance(ext["hmac-secret"], bytes)
|
||||||
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
|
||||||
key = hmac.process_get_output(x)
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
|
||||||
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
|
||||||
salts = [salt1]
|
salts = [salt1]
|
||||||
if (len(salts) == 2):
|
if (len(salts) == 2):
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
|
hout = {"salt1":salts[0],"salt2":salts[1]}
|
||||||
else:
|
else:
|
||||||
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
|
hout = {"salt1":salts[0]}
|
||||||
|
|
||||||
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
|
auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
|
||||||
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
|
assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0
|
||||||
|
|
||||||
ext_no_uv = auth_no_uv.auth_data.extensions
|
ext_no_uv = auth_no_uv.response.authenticator_data.extensions
|
||||||
assert ext_no_uv
|
assert ext_no_uv
|
||||||
assert "hmac-secret" in ext_no_uv
|
assert "hmac-secret" in ext_no_uv
|
||||||
assert isinstance(ext_no_uv["hmac-secret"], bytes)
|
assert isinstance(ext_no_uv["hmac-secret"], bytes)
|
||||||
@@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
|
|||||||
hout['salt2'] = salts[1]
|
hout['salt2'] = salts[1]
|
||||||
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
|
||||||
|
|
||||||
assert auth_uv.authenticator_data.flags & (1 << 2)
|
assert auth_uv.response.authenticator_data.flags & (1 << 2)
|
||||||
ext_uv = auth_uv.extension_results
|
ext_uv = auth_uv.client_extension_results
|
||||||
assert ext_uv
|
assert ext_uv
|
||||||
assert "hmacGetSecret" in ext_uv
|
assert "hmacGetSecret" in ext_uv
|
||||||
assert len(ext_uv["hmacGetSecret"]) == len(salts)
|
assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts)
|
||||||
|
|
||||||
# Now see if the hmac-secrets are different
|
# Now see if the hmac-secrets are different
|
||||||
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
|
|||||||
res = device.doGA(ctap1=False, allow_list=[
|
res = device.doGA(ctap1=False, allow_list=[
|
||||||
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||||
])
|
])
|
||||||
assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
|
||||||
|
|
||||||
|
|
||||||
# Test FIDO2 register works with U2F auth
|
# Test FIDO2 register works with U2F auth
|
||||||
|
|||||||
Reference in New Issue
Block a user