Added ctap1 interoperability test.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2022-10-04 11:42:54 +02:00
parent 4cea6ebe87
commit 3f80acc81b
2 changed files with 65 additions and 7 deletions

View File

@@ -1,12 +1,13 @@
from http import client from http import client
from fido2.hid import CtapHidDevice 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.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, AttestedCredentialData from fido2.webauthn import CollectedClientData, AttestedCredentialData
from getpass import getpass
from utils import * from utils import *
from fido2.cose import ES256
import sys import sys
import pytest import pytest
import os import os
@@ -37,6 +38,7 @@ class Device():
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 __set_client(self, origin, user_interaction, uv): def __set_client(self, origin, user_interaction, uv):
self.__uv = uv self.__uv = uv
self.__dev = None self.__dev = None
@@ -68,6 +70,9 @@ class Device():
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._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
def __set_server(self, rp, attestation): def __set_server(self, rp, attestation):
self.__rp = rp self.__rp = rp
self.__attestation = attestation self.__attestation = attestation
@@ -128,14 +133,18 @@ class Device():
'user':user, 'user':user,
'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): 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 CollectedClientData.create(
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) 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
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, client_data=client_data,
rp=rp, rp=rp,
user=user, user=user,
@@ -233,13 +242,17 @@ class Device():
def GNA(self): def GNA(self):
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): 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( client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32) 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'] 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: try:
result = self.__client._backend.do_get_assertion( result = client._backend.do_get_assertion(
client_data=client_data, client_data=client_data,
rp_id=rp_id, rp_id=rp_id,
allow_list=allow_list, allow_list=allow_list,
@@ -251,7 +264,7 @@ class Device():
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED): if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
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 = self.__client._backend.do_get_assertion( result = client._backend.do_get_assertion(
client_data=client_data, client_data=client_data,
rp_id=rp_id, rp_id=rp_id,
allow_list=allow_list, allow_list=allow_list,
@@ -259,6 +272,8 @@ class Device():
user_verification=user_verification, user_verification=user_verification,
event=event event=event
) )
else:
raise
return {'res':result,'req':{'client_data':client_data, return {'res':result,'req':{'client_data':client_data,
'rp_id':rp_id}} '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']) verify(MCRes_DC['res'].attestation_object, res['res'], res['req']['client_data_hash'])
return res 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

View File

@@ -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.")