diff --git a/tests/pico-fido/test_credprotect.py b/tests/pico-fido/test_credprotect.py new file mode 100644 index 0000000..c7403c6 --- /dev/null +++ b/tests/pico-fido/test_credprotect.py @@ -0,0 +1,152 @@ +import pytest +from fido2.ctap2.extensions import CredProtectExtension +from fido2.webauthn import UserVerificationRequirement +from fido2.ctap import CtapError + +class CredProtect: + UserVerificationOptional = 1 + UserVerificationOptionalWithCredentialId = 2 + UserVerificationRequired = 3 + +@pytest.fixture(scope="class") +def MCCredProtectOptional(resetdevice): + res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object + return res + +@pytest.fixture(scope="class") +def MCCredProtectOptionalList(resetdevice): + res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object + return res + +@pytest.fixture(scope="class") +def MCCredProtectRequired(resetdevice): + res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object + return res + + +def test_credprotect_make_credential_1(MCCredProtectOptional): + assert MCCredProtectOptional.auth_data.extensions + assert "credProtect" in MCCredProtectOptional.auth_data.extensions + assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1 + +def test_credprotect_make_credential_2(MCCredProtectOptionalList): + assert MCCredProtectOptionalList.auth_data.extensions + assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions + assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2 + +def test_credprotect_make_credential_3(MCCredProtectRequired): + assert MCCredProtectRequired.auth_data.extensions + assert "credProtect" in MCCredProtectRequired.auth_data.extensions + assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3 + +def test_credprotect_optional_excluded(device, MCCredProtectOptional): + """ CredProtectOptional Cred should be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] + + with pytest.raises(CtapError) as e: + device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list) + + assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED + +def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList): + """ CredProtectOptionalList Cred should be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] + + with pytest.raises(CtapError) as e: + device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list) + + assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED + +def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired): + """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """ + exclude_list = [ + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + } + ] + + # works + device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list) + +def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional): + + # works + res = device.doGA()['res'].get_assertions()[0] + + # If there's only one credential, this is None + assert res.number_of_credentials == None + +def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired): + allow_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + ] + # works + res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0] + assert res1.number_of_credentials in (None, 2) + + results = [res1] + if res1.number_of_credentials == 2: + res2 = device.GNA()['res'] + results.append(res2) + + # the required credProtect is not returned. + for res in results: + assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:] + +def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional +): + + res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object + + for ext in ["credProtect", "hmac-secret"]: + assert res.auth_data.extensions + assert ext in res.auth_data.extensions + assert res.auth_data.extensions[ext] == True + + +def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin): + allow_list = [ + { + "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + { + "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:], + "type": "public-key", + }, + ] + + pin = "12345678" + + client_pin.set_pin(pin) + + res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0] + + assert res1.number_of_credentials in (None, 3) +