More test fixes.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2022-10-03 01:24:17 +02:00
parent 6e91694253
commit 6f226001df
4 changed files with 124 additions and 116 deletions

View File

@@ -4,7 +4,7 @@ from fido2.client import Fido2Client, WindowsClient, UserInteraction, ClientErro
from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server
from fido2.ctap import CtapError
from fido2.webauthn import CollectedClientData
from fido2.webauthn import CollectedClientData, AttestedCredentialData
from getpass import getpass
import sys
import pytest
@@ -106,11 +106,15 @@ class Device():
self.__set_server(rp=self.__rp, attestation=self.__attestation)
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):
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
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,
client_data_hash=client_data_hash,
rp=rp,
user=user,
key_params=key_params,
exclude_list=exclude_list,
extensions=extensions,
options=options,
@@ -118,17 +122,23 @@ class Device():
pin_uv_protocol=pin_uv_protocol,
enterprise_attestation=enterprise_attestation
)
return att_obj
return {'res':att_obj,'req':{'client_data_hash':client_data_hash,
'rp':rp,
'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):
result = self.__client._backend.do_make_credential(
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)
),
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,
)
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(
client_data=client_data,
rp=rp,
user=user,
key_params=key_params,
exclude_list=exclude_list,
extensions=extensions,
rk=rk,
@@ -136,7 +146,10 @@ class Device():
enterprise_attestation=enterprise_attestation,
event=event
)
return result
return {'res':result,'req':{'client_data':client_data,
'rp':rp,
'user':user,
'key_params':key_params}}
def try_make_credential(self, options=None):
if (options is None):
@@ -202,32 +215,38 @@ class Device():
print("AUTH DATA:", result.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):
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
client_data_hash = client_data_hash if client_data_hash is not Ellipsis else os.urandom(32)
att_obj = self.__client._backend.ctap2.get_assertion(
rp_id=rp_id if rp_id is not Ellipsis else self.__rp['id'],
client_data_hash=client_data_hash if client_data_hash is not Ellipsis else os.urandom(32),
rp_id=rp_id,
client_data_hash=client_data_hash,
allow_list=allow_list,
extensions=extensions,
options=options,
pin_uv_param=pin_uv_param,
pin_uv_protocol=pin_uv_protocol
)
return att_obj
return {'res':att_obj,'req':{'rp_id':rp_id,
'client_data_hash':client_data_hash}}
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):
result = self.__client._backend.do_get_assertion(
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)
),
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']
result = self.__client._backend.do_get_assertion(
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
return result
return {'res':result,'req':{'client_data':client_data,
'rp_id':rp_id}}
@pytest.fixture(scope="session")
@@ -241,7 +260,7 @@ def info(device):
@pytest.fixture(scope="session")
def MCRes(device, *args):
return device.doMC(*args).attestation_object
return device.doMC(*args)
@pytest.fixture(scope="session")
def resetdevice(device):
@@ -250,18 +269,24 @@ def resetdevice(device):
@pytest.fixture(scope="session")
def GARes(device, MCRes, *args):
r = device.doGA(allow_list=[
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
res = device.doGA(allow_list=[
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
return r
credential_data = AttestedCredentialData(MCRes['res'].attestation_object.auth_data.credential_data)
assertions = res['res'].get_assertions()
for a in assertions:
a.verify(res['req']['client_data'].hash, credential_data.public_key)
return res
@pytest.fixture(scope="session")
def MCRes_DC(device, *args):
return device.doMC(rk=True, *args).attestation_object
return device.doMC(rk=True, *args)
@pytest.fixture(scope="session")
def GARes_DC(device, MCRes_DC, *args):
r = device.GA(allow_list=[
{"id": MCRes_DC.auth_data.credential_data.credential_id, "type": "public-key"}
res = device.GA(allow_list=[
{"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
return r
credential_data = AttestedCredentialData(MCRes_DC['res'].attestation_object.auth_data.credential_data)
res['res'].verify(res['req']['client_data_hash'], credential_data.public_key)
return res

View File

@@ -10,17 +10,17 @@ def test_authenticate(device):
AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes):
assert len(GARes.get_response(0).authenticator_data) == 37
assert len(GARes['res'].get_response(0).authenticator_data) == 37
def test_Check_that_AT_flag_is_not_set(GARes):
assert (GARes.get_response(0).authenticator_data.flags & 0xF8) == 0
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
assert res.user == None
assert res.number_of_credentials == None
assert res['res'].user == None
assert res['res'].number_of_credentials == None
def test_empty_allowList(device):
with pytest.raises(CtapError) as e:
@@ -41,7 +41,7 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
l1 = 4
for i in range(0, l1):
res = device.doMC(rp=rp1).attestation_object
res = device.doMC(rp=rp1)['res'].attestation_object
rp1_registrations.append(res)
allow_list.append({
"id": res.auth_data.credential_data.credential_id[:],
@@ -50,7 +50,7 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
l2 = 6
for i in range(0, l2):
res = device.doMC(rp=rp2).attestation_object
res = device.doMC(rp=rp2)['res'].attestation_object
rp2_registrations.append(res)
allow_list.append({
"id": res.auth_data.credential_data.credential_id[:],
@@ -66,10 +66,10 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
# cached.
# Should authenticate to all credentials matching rp1
rp1_assertions = device.doGA(rp_id=rp1['id'], allow_list=allow_list).get_assertions()
rp1_assertions = device.doGA(rp_id=rp1['id'], allow_list=allow_list)['res'].get_assertions()
# Should authenticate to all credentials matching rp2
rp2_assertions = device.doGA(rp_id=rp2['id'], allow_list=allow_list).get_assertions()
rp2_assertions = device.doGA(rp_id=rp2['id'], allow_list=allow_list)['res'].get_assertions()
counts = (
len(rp1_assertions),
@@ -80,14 +80,14 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
def test_corrupt_credId(device, MCRes):
# apply bit flip
badid = list(MCRes.auth_data.credential_data.credential_id[:])
badid = list(MCRes['res'].attestation_object.auth_data.credential_data.credential_id[:])
badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1
badid = bytes(badid)
allow_list = [{"id": badid, "type": "public-key"}]
with pytest.raises(CtapError) as e:
device.doGA(allow_list=allow_list)
device.doGA(allow_list=allow_list)['res']
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_mismatched_rp(device, GARes):
@@ -124,38 +124,38 @@ def test_bad_allow_list(device):
def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=["wrong"] + [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_unknown_option(device, MCRes):
device.GA(options={"unknown": True}, allow_list=[
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
def test_option_uv(device, info, GARes):
if "uv" in info.options:
if info.options["uv"]:
res = device.doGA(options={"uv": True})
res = device.doGA(options={"uv": True})['res']
assert res.auth_data.flags & (1 << 2)
def test_option_up(device, info, GARes):
if "up" in info.options:
if info.options["up"]:
res = device.doGA(options={"up": True})
res = device.doGA(options={"up": True})['res']
assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes):
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],
)
def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"id": b"1234"}] + [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -163,7 +163,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": b"public-key", "id": b"1234"}]
+ [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -171,20 +171,20 @@ def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
+ [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key"}] + [
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
def test_user_presence_option_false(device, MCRes):
res = device.GA(options={"up": False}, allow_list=[
{"id": MCRes.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
def test_credential_resets(device, MCRes, GARes):

View File

@@ -46,11 +46,11 @@ def generate_user_maximum():
@pytest.mark.parametrize("do_reboot", [False, True])
def test_user_info_returned_when_using_allowlist(device, MCRes_DC, GARes_DC, do_reboot):
assert "id" in GARes_DC.user.keys()
assert "id" in GARes_DC['res'].user.keys()
allow_list = [
{
"id": MCRes_DC.auth_data.credential_data.credential_id[:],
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
@@ -58,23 +58,23 @@ def test_user_info_returned_when_using_allowlist(device, MCRes_DC, GARes_DC, do_
if do_reboot:
device.reboot()
ga_res = device.GA(allow_list=allow_list)
ga_res = device.GA(allow_list=allow_list)['res']
assert device.user()["id"] == ga_res.user["id"]
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
assert "id" in GARes_DC.user.keys()
assert "id" in GARes_DC['res'].user.keys()
allow_list = [
{
"id": MCRes_DC.auth_data.credential_data.credential_id[:],
"id": MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
ga_res = device.GA(allow_list=allow_list)
ga_res = device.GA(allow_list=allow_list)['res']
assert device.user()["id"] == ga_res.user["id"]
assert MCRes_DC["req"]["user"]["id"] == ga_res.user["id"]
device.reset()
@@ -91,16 +91,16 @@ def test_resident_key_auth(MCRes_DC, GARes_DC):
pass
def test_user_info_returned(device, MCRes_DC, GARes_DC):
assert "id" in GARes_DC.user.keys()
assert "id" in GARes_DC['res'].user.keys()
assert (
MCRes_DC.auth_data.credential_data.credential_id
== GARes_DC.credential["id"]
MCRes_DC['res'].attestation_object.auth_data.credential_data.credential_id
== GARes_DC['res'].credential["id"]
)
assert device.user()["id"] == GARes_DC.user["id"]
if not GARes_DC.number_of_credentials:
assert "id" in GARes_DC.user.keys() and len(GARes_DC.user.keys()) == 1
assert MCRes_DC["req"]["user"]["id"] == GARes_DC['res'].user["id"]
if not GARes_DC['res'].number_of_credentials:
assert "id" in GARes_DC['res'].user.keys() and len(GARes_DC['res'].user.keys()) == 1
else:
assert device.user() == GARes_DC.user
assert MCRes_DC["req"]["user"] == GARes_DC['res'].user
def test_multiple_rk_nodisplay(device, MCRes_DC):
@@ -113,7 +113,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
regs.append(res)
# time.sleep(2)
res = device.doGA(rp_id=rp['id'])
res = device.doGA(rp_id=rp['id'])['res']
auths = res.get_assertions()
assert len(regs) == 3
@@ -125,21 +125,20 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
print("FAIL: %s was not in user: " % y, x.user)
def test_rk_maximum_size_nodisplay(device, MCRes_DC):
def test_rk_maximum_size_nodisplay(device):
"""
Check the lengths of the fields according to the FIDO2 spec
https://github.com/solokeys/solo/issues/158#issue-426613303
https://www.w3.org/TR/webauthn/#dom-publickeycredentialuserentity-displayname
"""
auths = []
device.reset()
user_max = generate_user_maximum()
print(user_max)
resMC = device.doMC(user=user_max)
resGA = device.doGA()
resMC = device.doMC(user=user_max, rk=True)
resGA = device.doGA()['res']
auths = resGA.get_assertions()
user_max_GA = auths[0]
print(auths)
for y in ("name", "displayName", "id"):
assert user_max_GA.user[y] == user_max[y]
@@ -172,20 +171,18 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
regs = [MCRes_DC]
RK_to_generate = RK_CAPACITY_PER_RP - current_credentials_count
for i in range(RK_to_generate):
req = FidoRequest(MCRes_DC, user=get_user(), rp = rp)
res = device.sendMC(*req.toMC())
res = device.doMC(user=get_user(), rp=rp, rk=True)['res'].attestation_object
regs.append(res)
req = FidoRequest(MCRes_DC, options=None, user=generate_user_maximum(), rp = rp)
res = device.sendGA(*req.toGA())
res = device.GA(rp_id = rp['id'])['res']
assert res.number_of_credentials == RK_CAPACITY_PER_RP
auths.append(res)
for i in range(RK_CAPACITY_PER_RP - 1):
auths.append(device.ctap2.get_next_assertion())
auths.append(device.GNA())
with pytest.raises(CtapError) as e:
device.ctap2.get_next_assertion()
device.GNA()
auths = auths[::-1][-RK_to_generate:]
regs = regs[-RK_to_generate:]
@@ -193,29 +190,24 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
assert len(auths) == len(users)
if MCRes_DC.request.pin_protocol:
for x, u in zip(auths, users):
for y in ("name", "icon", "displayName", "id"):
assert y in x.user.keys()
assert x.user[y] == u[y]
for x, u in zip(auths, users):
for y in ("name", "displayName", "id"):
assert y in x.user.keys()
assert x.user[y] == u[y]
assert len(auths) == len(regs)
for x, y in zip(regs, auths):
verify(x, y, req.cdh)
def test_rk_with_allowlist_of_different_rp(resetDevice):
def test_rk_with_allowlist_of_different_rp(resetdevice):
"""
Test that a rk credential is not found when using an allowList item for a different RP
"""
rk_rp = {"id": "rk-cred.org", "name": "Example"}
rk_req = FidoRequest(rp = rk_rp, options={"rk": True})
rk_res = resetDevice.sendMC(*rk_req.toMC())
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
server_rp = {"id": "server-cred.com", "name": "Example"}
server_req = FidoRequest(rp = server_rp)
server_res = resetDevice.sendMC(*server_req.toMC())
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
allow_list_with_different_rp_cred = [
{
@@ -224,43 +216,39 @@ def test_rk_with_allowlist_of_different_rp(resetDevice):
}
]
test_req = FidoRequest(rp = rk_rp, allow_list = allow_list_with_different_rp_cred)
with pytest.raises(CtapError) as e:
res = resetDevice.sendGA(*test_req.toGA())
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_same_userId_overwrites_rk(resetDevice):
def test_same_userId_overwrites_rk(resetdevice):
"""
A make credential request with a UserId & Rp that is the same as an existing one should overwrite.
"""
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_user()
user = generate_random_user()
req = FidoRequest(rp = rp, options={"rk": True}, user = user)
mc_res1 = resetDevice.sendMC(*req.toMC())
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
# Should overwrite the first credential.
mc_res2 = resetDevice.sendMC(*req.toMC())
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
ga_res = resetDevice.sendGA(*req.toGA())
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
# If there's only one credential, this is None
assert ga_res.number_of_credentials == None
verify(mc_res2, ga_res, req.cdh)
def test_larger_icon_than_128(device):
"""
Test it works if we give an icon value larger than 128 bytes
"""
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_user()
user = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
req = FidoRequest(rp = rp, options={"rk": True}, user = user)
device.sendMC(*req.toMC())
device.doMC(rp = rp, rk=True, user = user)
def test_returned_credential(device):
@@ -269,29 +257,24 @@ def test_returned_credential(device):
only 1 will get returned.
"""
device.reset()
pin = '12345'
device.client.pin_protocol.set_pin(pin)
req = FidoRequest(pin = pin, options={"rk": True})
regs = []
allow_list = []
for i in range(0, 2):
req = FidoRequest(req, user = {
res = device.doMC(rk=True, user = {
"id": b'123456' + bytes([i]), "name": f'Test User {i}', "displayName": f'Test User display {i}'
})
res = device.sendMC(*req.toMC())
setattr(res, "request", req)
})['res'].attestation_object
regs.append(res)
allow_list.append({"id": res.auth_data.credential_data.credential_id[:], "type": "public-key"})
print('allow_list: ' , allow_list)
ga_req = FidoRequest(pin = pin, allow_list=allow_list)
ga_res = device.sendGA(*ga_req.toGA())
ga_res = device.GA(allow_list=allow_list)['res']
print(ga_res)
# No other credentials should be returned
with pytest.raises(CtapError) as e:
device.ctap2.get_next_assertion()
device.GNA()
# the returned credential should have user id in it
print(ga_res)

View File

@@ -11,10 +11,10 @@ def test_make_credential():
pass
def test_attestation_format(MCRes):
assert MCRes.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
assert MCRes['res'].attestation_object.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"]
def test_authdata_length(MCRes):
assert len(MCRes.auth_data) >= 77
assert len(MCRes['res'].attestation_object.auth_data) >= 77
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
@@ -152,7 +152,7 @@ def test_bad_type_exclude_list_type(device):
device.doMC(exclude_list=[{"type": b"public-key", "id": b"1234"}])
def test_exclude_list_excluded(device):
res = device.doMC().attestation_object
res = device.doMC()['res'].attestation_object
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[
{"id": res.auth_data.credential_data.credential_id, "type": "public-key"}