From 3f80acc81b130ac1f920f76e6c72711530198c14 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Tue, 4 Oct 2022 11:42:54 +0200 Subject: [PATCH] Added ctap1 interoperability test. Signed-off-by: Pol Henarejos --- tests/conftest.py | 47 +++++++++++++++++++++++---- tests/pico-fido/test_ctap1_interop.py | 25 ++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 tests/pico-fido/test_ctap1_interop.py diff --git a/tests/conftest.py b/tests/conftest.py index c44150c..d23dd61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,13 @@ from http import client from fido2.hid import CtapHidDevice -from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError +from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError, _Ctap1ClientBackend +from fido2.attestation import FidoU2FAttestation from fido2.ctap2.pin import ClientPin from fido2.server import Fido2Server from fido2.ctap import CtapError from fido2.webauthn import CollectedClientData, AttestedCredentialData -from getpass import getpass from utils import * +from fido2.cose import ES256 import sys import pytest import os @@ -37,6 +38,7 @@ class Device(): self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv) self.__set_server(rp=rp, attestation=attestation) + def __set_client(self, origin, user_interaction, uv): self.__uv = uv self.__dev = None @@ -68,6 +70,9 @@ class Device(): self.__uv = "preferred" print("Authenticator supports User Verification") + self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction) + self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction) + def __set_server(self, rp, attestation): self.__rp = rp self.__attestation = attestation @@ -128,14 +133,18 @@ class Device(): 'user':user, 'key_params':key_params}} - def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None): + def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False): client_data = client_data if client_data is not Ellipsis else CollectedClientData.create( type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) ) rp = rp if rp is not Ellipsis else self.__rp user = user if user is not Ellipsis else self.user() key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms - result = self.__client._backend.do_make_credential( + if (ctap1 is True): + client = self.__client1 + else: + client = self.__client + result = client._backend.do_make_credential( client_data=client_data, rp=rp, user=user, @@ -233,13 +242,17 @@ class Device(): def GNA(self): return self.__client._backend.ctap2.get_next_assertion() - def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None): + def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False): client_data = client_data if client_data is not Ellipsis else CollectedClientData.create( type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) ) rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id'] + if (ctap1 is True): + client = self.__client1 + else: + client = self.__client try: - result = self.__client._backend.do_get_assertion( + result = client._backend.do_get_assertion( client_data=client_data, rp_id=rp_id, allow_list=allow_list, @@ -251,7 +264,7 @@ class Device(): if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED): client_pin = ClientPin(self.__client._backend.ctap2) client_pin.set_pin(DEFAULT_PIN) - result = self.__client._backend.do_get_assertion( + result = client._backend.do_get_assertion( client_data=client_data, rp_id=rp_id, allow_list=allow_list, @@ -259,6 +272,8 @@ class Device(): user_verification=user_verification, event=event ) + else: + raise return {'res':result,'req':{'client_data':client_data, 'rp_id':rp_id}} @@ -304,3 +319,21 @@ def GARes_DC(device, MCRes_DC, *args): verify(MCRes_DC['res'].attestation_object, res['res'], res['req']['client_data_hash']) return res + +@pytest.fixture(scope="module") +def RegRes(resetdevice, *args): + res = resetdevice.doMC(ctap1=True, *args) + att = FidoU2FAttestation() + att.verify(res['res'].attestation_object.att_stmt, res['res'].attestation_object.auth_data, res['req']['client_data'].hash) + return res + + +@pytest.fixture(scope="class") +def AuthRes(device, RegRes, *args): + res = device.doGA(ctap1=True, allow_list=[ + {"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + ], *args) + aut_data = res['res'].get_response(0) + m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash + ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature) + return aut_data diff --git a/tests/pico-fido/test_ctap1_interop.py b/tests/pico-fido/test_ctap1_interop.py new file mode 100644 index 0000000..0260e76 --- /dev/null +++ b/tests/pico-fido/test_ctap1_interop.py @@ -0,0 +1,25 @@ +from fido2.attestation import FidoU2FAttestation + +# Test U2F register works with FIDO2 auth +def test_ctap1_register(RegRes): + pass + +def test_ctap1_authenticate(RegRes, AuthRes): + pass + +def test_authenticate_ctap1_through_ctap2(device, RegRes): + res = device.doGA(allow_list=[ + {"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} + ]) + assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id + + +# Test FIDO2 register works with U2F auth +def test_ctap1_authenticate_attestation(MCRes, device): + key_handle = MCRes['res'].attestation_object.auth_data.credential_data.credential_id + if len(key_handle) <= 255: + res = device.doGA(ctap1=True,allow_list=[ + {"id": key_handle, "type": "public-key"} + ]) + else: + print("ctap2 credId is longer than 255 bytes, cannot use with U2F.")