Adding first tests.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
225
tests/conftest.py
Normal file
225
tests/conftest.py
Normal file
@@ -0,0 +1,225 @@
|
||||
from http import client
|
||||
from fido2.hid import CtapHidDevice
|
||||
from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientError
|
||||
from fido2.ctap2.pin import ClientPin
|
||||
from fido2.server import Fido2Server
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.webauthn import (
|
||||
Aaguid,
|
||||
AttestationObject,
|
||||
CollectedClientData,
|
||||
PublicKeyCredentialCreationOptions,
|
||||
PublicKeyCredentialRequestOptions,
|
||||
AuthenticatorSelectionCriteria,
|
||||
UserVerificationRequirement,
|
||||
AuthenticatorAttestationResponse,
|
||||
AuthenticatorAssertionResponse,
|
||||
AttestationConveyancePreference,
|
||||
)
|
||||
from getpass import getpass
|
||||
import sys
|
||||
import ctypes
|
||||
import pytest
|
||||
import os
|
||||
import inspect
|
||||
|
||||
DEFAULT_PIN='12345678'
|
||||
|
||||
class CliInteraction(UserInteraction):
|
||||
def prompt_up(self):
|
||||
print("\nTouch your authenticator device now...\n")
|
||||
|
||||
def request_pin(self, permissions, rd_id):
|
||||
return DEFAULT_PIN
|
||||
|
||||
def request_uv(self, permissions, rd_id):
|
||||
print("User Verification required.")
|
||||
return True
|
||||
|
||||
class DeviceSelectCredential:
|
||||
def __init__(self, number):
|
||||
pass
|
||||
|
||||
def __call__(self, status):
|
||||
pass
|
||||
|
||||
class Device():
|
||||
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.__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
|
||||
self.__origin = origin
|
||||
self.__user_interaction = user_interaction
|
||||
|
||||
# Locate a device
|
||||
self.__dev = next(CtapHidDevice.list_devices(), None)
|
||||
if self.__dev is not None:
|
||||
print("Use USB HID channel.")
|
||||
else:
|
||||
try:
|
||||
from fido2.pcsc import CtapPcscDevice
|
||||
|
||||
self.__dev = next(CtapPcscDevice.list_devices(), None)
|
||||
print("Use NFC channel.")
|
||||
except Exception as e:
|
||||
print("NFC channel search error:", e)
|
||||
|
||||
if not self.__dev:
|
||||
print("No FIDO device found")
|
||||
sys.exit(1)
|
||||
|
||||
# Set up a FIDO 2 client using the origin https://example.com
|
||||
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
|
||||
|
||||
# Prefer UV if supported and configured
|
||||
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
|
||||
self.__uv = "preferred"
|
||||
print("Authenticator supports User Verification")
|
||||
|
||||
def __set_server(self, rp, attestation):
|
||||
self.__rp = rp
|
||||
self.__attestation = attestation
|
||||
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
|
||||
|
||||
def client(self):
|
||||
return self.__client
|
||||
|
||||
def user(self, user=None):
|
||||
if (self.__user is None):
|
||||
self.__user = {"id": b"user_id", "name": "A. User"}
|
||||
if (user is not None):
|
||||
self.__user = user
|
||||
return self.__user
|
||||
|
||||
def reset(self):
|
||||
print("Resetting Authenticator...")
|
||||
try:
|
||||
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
|
||||
except CtapError:
|
||||
# Some authenticators need a power cycle
|
||||
print("Need to power cycle authentictor to reset..")
|
||||
self.reboot()
|
||||
self.__client._backend.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
|
||||
|
||||
def reboot(self):
|
||||
print("Please reboot authenticator and hit enter")
|
||||
input()
|
||||
self.__setup_client(self.__origin, self.__user_interaction, self.__uv)
|
||||
|
||||
def MC(self, client_data_hash=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None, enterprise_attestation=None):
|
||||
att_obj = self.__client._backend.ctap2.make_credential(
|
||||
client_data_hash=client_data_hash if client_data_hash is not Ellipsis else 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,
|
||||
exclude_list=exclude_list,
|
||||
extensions=extensions,
|
||||
options=options,
|
||||
pin_uv_param=pin_uv_param,
|
||||
pin_uv_protocol=pin_uv_protocol,
|
||||
enterprise_attestation=enterprise_attestation
|
||||
)
|
||||
return att_obj
|
||||
|
||||
def doMC(self, client_data=None, rp=None, user=None, key_params=None, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None):
|
||||
|
||||
result = self.__client._backend.do_make_credential(
|
||||
client_data=client_data or CollectedClientData.create(
|
||||
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
||||
),
|
||||
rp=rp or self.__rp,
|
||||
user=user or self.user(),
|
||||
key_params=key_params or self.__server.allowed_algorithms,
|
||||
exclude_list=exclude_list,
|
||||
extensions=extensions,
|
||||
rk=rk,
|
||||
user_verification=user_verification,
|
||||
enterprise_attestation=enterprise_attestation,
|
||||
event=event
|
||||
)
|
||||
return result
|
||||
|
||||
def try_make_credential(self, options=None):
|
||||
if (options is None):
|
||||
options, _ = self.__server.register_begin(
|
||||
self.user(), user_verification=self.__uv, authenticator_attachment="cross-platform"
|
||||
)
|
||||
try:
|
||||
result = self.__client.make_credential(options["publicKey"])
|
||||
except ClientError as e:
|
||||
if (e.code == ClientError.ERR.CONFIGURATION_UNSUPPORTED):
|
||||
client_pin = ClientPin(self.__client._backend.ctap2)
|
||||
client_pin.set_pin(DEFAULT_PIN)
|
||||
result = self.__client.make_credential(options["publicKey"])
|
||||
return result
|
||||
|
||||
def register(self, uv=None):
|
||||
# Prepare parameters for makeCredential
|
||||
create_options, state = self.__server.register_begin(
|
||||
self.user(), user_verification=self.__uv, authenticator_attachment="cross-platform"
|
||||
)
|
||||
# Create a credential
|
||||
result = self.try_make_credential(create_options)
|
||||
|
||||
# Complete registration
|
||||
auth_data = self.__server.register_complete(
|
||||
state, result.client_data, result.attestation_object
|
||||
)
|
||||
credentials = [auth_data.credential_data]
|
||||
|
||||
print("New credential created!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print("ATTESTATION OBJECT:", result.attestation_object)
|
||||
print()
|
||||
print("CREDENTIAL DATA:", auth_data.credential_data)
|
||||
|
||||
return (result, auth_data)
|
||||
|
||||
def authenticate(self, credentials):
|
||||
# Prepare parameters for getAssertion
|
||||
request_options, state = self.__server.authenticate_begin(credentials, user_verification=self.__uv)
|
||||
|
||||
# Authenticate the credential
|
||||
result = self.__client.get_assertion(request_options["publicKey"])
|
||||
|
||||
# Only one cred in allowCredentials, only one response.
|
||||
result = result.get_response(0)
|
||||
|
||||
# Complete authenticator
|
||||
self.__server.authenticate_complete(
|
||||
state,
|
||||
credentials,
|
||||
result.credential_id,
|
||||
result.client_data,
|
||||
result.authenticator_data,
|
||||
result.signature,
|
||||
)
|
||||
|
||||
print("Credential authenticated!")
|
||||
|
||||
print("CLIENT DATA:", result.client_data)
|
||||
print()
|
||||
print("AUTH DATA:", result.authenticator_data)
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def device():
|
||||
dev = Device()
|
||||
return dev
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def info(device):
|
||||
return device.client()._backend.info
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def MCRes(device, *args):
|
||||
return device.doMC(*args).attestation_object
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def resetdevice(device):
|
||||
device.reset()
|
||||
return device
|
||||
7
tests/pico-fido/test_authenticate.py
Normal file
7
tests/pico-fido/test_authenticate.py
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
def test_authenticate(device):
|
||||
device.reset()
|
||||
REGRes,AUTData = device.register()
|
||||
|
||||
credentials = [AUTData.credential_data]
|
||||
AUTRes = device.authenticate(credentials)
|
||||
27
tests/pico-fido/test_getinfo.py
Normal file
27
tests/pico-fido/test_getinfo.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pytest
|
||||
from fido2.client import CtapError
|
||||
|
||||
|
||||
def test_getinfo(device):
|
||||
pass
|
||||
|
||||
|
||||
def test_get_info_version(info):
|
||||
assert "FIDO_2_0" in info.versions
|
||||
|
||||
|
||||
def test_Check_pin_protocols_field(info):
|
||||
if len(info.pin_uv_protocols):
|
||||
assert sum(info.pin_uv_protocols) > 0
|
||||
|
||||
|
||||
def test_Check_options_field(info):
|
||||
for x in info.options:
|
||||
assert info.options[x] in [True, False]
|
||||
|
||||
|
||||
def test_Check_up_option(device, info):
|
||||
if "up" not in info.options or info.options["up"]:
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(options={"up": True})
|
||||
assert e.value.code == CtapError.ERR.INVALID_OPTION
|
||||
260
tests/pico-fido/test_register.py
Normal file
260
tests/pico-fido/test_register.py
Normal file
@@ -0,0 +1,260 @@
|
||||
from fido2.client import CtapError
|
||||
import pytest
|
||||
|
||||
|
||||
def test_register(device):
|
||||
device.reset()
|
||||
REGRes,AUTData = device.register()
|
||||
|
||||
def test_make_credential(device, MCRes):
|
||||
pass
|
||||
|
||||
def test_attestation_format(device, MCRes):
|
||||
assert MCRes.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
|
||||
|
||||
def test_authdata_length(device, MCRes):
|
||||
assert len(MCRes.auth_data) >= 77
|
||||
|
||||
def test_missing_cdh(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(client_data_hash=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_cdh(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(client_data_hash=b'\xff')
|
||||
|
||||
def test_missing_user(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(user=None)
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_user_user(device, MCRes):
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.MC(user=b"12345678")
|
||||
|
||||
def test_missing_rp(device, MCRes):
|
||||
req = FidoRequest(MCRes, rp=None)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_rp(device, MCRes):
|
||||
req = FidoRequest(MCRes, rp=b"1234abcdef")
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_missing_pubKeyCredParams(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=None)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_bad_type_pubKeyCredParams(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=b"1234a")
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_excludeList(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=8)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_extensions(device, MCRes):
|
||||
req = FidoRequest(MCRes, extensions=8)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_options(device, MCRes):
|
||||
req = FidoRequest(MCRes, options=8)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_rp_name(device, MCRes):
|
||||
req = FidoRequest(MCRes, rp={"id": "test.org", "name": 8, "icon": "icon"})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_rp_id(device, MCRes):
|
||||
req = FidoRequest(MCRes, rp={"id": 8, "name": "name", "icon": "icon"})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_rp_icon(device, MCRes):
|
||||
req = FidoRequest(MCRes, rp={"id": "test.org", "name": "name", "icon": 8})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_user_name(device, MCRes):
|
||||
req = FidoRequest(MCRes, user={"id": b"user_id", "name": 8})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_user_id(device, MCRes):
|
||||
req = FidoRequest(MCRes, user={"id": "user_id", "name": "name"})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_user_displayName(device, MCRes):
|
||||
req = FidoRequest(
|
||||
MCRes, user={"id": "user_id", "name": "name", "displayName": 8}
|
||||
)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_user_icon(device, MCRes):
|
||||
req = FidoRequest(MCRes, user={"id": "user_id", "name": "name", "icon": 8})
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_pubKeyCredParams(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=["wrong"])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_missing_pubKeyCredParams_type(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=[{"alg": ES256.ALGORITHM}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
|
||||
|
||||
def test_missing_pubKeyCredParams_alg(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=[{"type": "public-key"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code in [
|
||||
CtapError.ERR.MISSING_PARAMETER,
|
||||
CtapError.ERR.UNSUPPORTED_ALGORITHM,
|
||||
]
|
||||
|
||||
def test_bad_type_pubKeyCredParams_alg(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=[{"alg": "7", "type": "public-key"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_unsupported_algorithm(device, MCRes):
|
||||
req = FidoRequest(MCRes, key_params=[{"alg": 1337, "type": "public-key"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
|
||||
|
||||
def test_exclude_list(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=[{"id": b"1234", "type": "rot13"}])
|
||||
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_exclude_list2(device, MCRes):
|
||||
req = FidoRequest(
|
||||
MCRes,
|
||||
exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}],
|
||||
)
|
||||
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_exclude_list(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=["1234"])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_missing_exclude_list_type(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=[{"id": b"1234"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_missing_exclude_list_id(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=[{"type": "public-key"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_exclude_list_id(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=[{"type": "public-key", "id": "1234"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_bad_type_exclude_list_type(device, MCRes):
|
||||
req = FidoRequest(MCRes, exclude_list=[{"type": b"public-key", "id": b"1234"}])
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_exclude_list_excluded(device, MCRes, GARes):
|
||||
req = FidoRequest(MCRes, exclude_list=GARes.request.allow_list)
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
|
||||
|
||||
def test_unknown_option(device, MCRes):
|
||||
req = FidoRequest(MCRes, options={"unknown": False})
|
||||
print("MC", req.toMC())
|
||||
device.sendMC(*req.toMC())
|
||||
|
||||
def test_eddsa(device):
|
||||
mc_req = FidoRequest(
|
||||
key_params=[{"type": "public-key", "alg": EdDSA.ALGORITHM}]
|
||||
)
|
||||
try:
|
||||
mc_res = device.sendMC(*mc_req.toMC())
|
||||
except CtapError as e:
|
||||
if e.code == CtapError.ERR.UNSUPPORTED_ALGORITHM:
|
||||
print("ed25519 is not supported. Skip this test.")
|
||||
return
|
||||
|
||||
setattr(mc_res, "request", mc_req)
|
||||
|
||||
allow_list = [
|
||||
{
|
||||
"id": mc_res.auth_data.credential_data.credential_id[:],
|
||||
"type": "public-key",
|
||||
}
|
||||
]
|
||||
|
||||
ga_req = FidoRequest(allow_list=allow_list)
|
||||
ga_res = device.sendGA(*ga_req.toGA())
|
||||
setattr(ga_res, "request", ga_req)
|
||||
|
||||
try:
|
||||
verify(mc_res, ga_res)
|
||||
except:
|
||||
# Print out extra details on failure
|
||||
from binascii import hexlify
|
||||
|
||||
print("authdata", hexlify(ga_res.auth_data))
|
||||
print("cdh", hexlify(ga_res.request.cdh))
|
||||
print("sig", hexlify(ga_res.signature))
|
||||
from fido2.ctap2 import AttestedCredentialData
|
||||
|
||||
credential_data = AttestedCredentialData(mc_res.auth_data.credential_data)
|
||||
print("public key:", hexlify(credential_data.public_key[-2]))
|
||||
verify(mc_res, ga_res)
|
||||
Reference in New Issue
Block a user