Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
[mypy]
plugins = pydantic.mypy, marshmallow_dataclass.mypy

[mypy-ndnkdf.*]
follow_untyped_imports = True

[mypy-fido_mds.*]
follow_untyped_imports = True
3 changes: 1 addition & 2 deletions src/eduid/vccs/server/password.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from binascii import unhexlify
from typing import cast

from ndnkdf import NDNKDF

Expand Down Expand Up @@ -106,4 +105,4 @@ async def calculate_cred_hash(

# PBKDF2 again with iter=1 to mix in the local_salt into the final H2.
H2 = kdf.pbkdf2_hmac_sha512(T2, 1, local_salt)
return cast(str, H2.hex())
return H2.hex()
6 changes: 5 additions & 1 deletion src/eduid/webapp/common/authn/webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ def get_authenticator_information(
user_present = att.auth_data.flags.user_present
user_verified = att.auth_data.flags.user_verified
authenticator_id = att.aaguid or att.certificate_key_identifier
if authenticator_id is None:
raise AttestationVerificationError("attestation contains no authenticator id (aaguid or certificate key identifier)")

# allow automatic tests to use any webauthn device
if is_backdoor:
Expand Down Expand Up @@ -159,6 +161,8 @@ def get_authenticator_information(

# create authenticator information from attestation and metadata
metadata_entry = fido_mds.get_entry(authenticator_id=authenticator_id)
if metadata_entry is None:
raise AttestationVerificationError(f"no metadata entry found for authenticator {authenticator_id}")
# mongodb does not support date
last_status_change = metadata_entry.time_of_last_status_change
user_verification_methods = [
Expand All @@ -178,7 +182,7 @@ def get_authenticator_information(

return AuthenticatorInformation(
attestation_format=att.fmt,
authenticator_id=att.aaguid or att.certificate_key_identifier,
authenticator_id=authenticator_id,
status=max(
metadata_entry.status_reports, key=lambda sr: sr.effective_date
).status, # latest status reports status
Expand Down
43 changes: 20 additions & 23 deletions src/eduid/webapp/security/tests/test_webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
RegistrationResponse,
UserVerificationRequirement,
)
from fido_mds import FidoMetadataStore
from fido_mds import Attestation, FidoMetadataStore
from fido_mds.models.webauthn import AttestationFormat
from fido_mds.tests.data import IPHONE_12, MICROSOFT_SURFACE_1796, NEXUS_5, NONE_ATTESTATION, YUBIKEY_4, YUBIKEY_5_NFC
from future.backports.datetime import timedelta
from pytest_mock import MockerFixture
from werkzeug.test import TestResponse
Expand All @@ -34,10 +36,22 @@

# CTAP1 test data

# result of calling Fido2Server.register_begin
from fido_mds import Attestation
from fido_mds.models.webauthn import AttestationFormat
from fido_mds.tests.data import IPHONE_12, MICROSOFT_SURFACE_1796, NEXUS_5, NONE_ATTESTATION, YUBIKEY_4, YUBIKEY_5_NFC

def _apple_special_verify_attestation(self: FidoMetadataStore, attestation: Attestation, client_data: bytes) -> bool:
if attestation.fmt is AttestationFormat.PACKED:
return self.verify_packed_attestation(attestation=attestation, client_data=client_data)
if attestation.fmt is AttestationFormat.APPLE:
# apple attestation cert in fido_mds test data is only valid for three days
return True
if attestation.fmt is AttestationFormat.TPM:
return self.verify_tpm_attestation(attestation=attestation, client_data=client_data)
if attestation.fmt is AttestationFormat.ANDROID_SAFETYNET:
# android attestation cert in fido_mds test data is only valid for three months
return True
if attestation.fmt is AttestationFormat.FIDO_U2F:
return self.verify_fido_u2f_attestation(attestation=attestation, client_data=client_data)
raise NotImplementedError(f"verification of {attestation.fmt.value} not implemented")


# CTAP1 security key
STATE = {"challenge": "u3zHzb7krB4c4wj0Uxuhsz2lCXqLnwV9ZxMhvL2lcfo", "user_verification": "discouraged"}
Expand Down Expand Up @@ -383,23 +397,6 @@ def _remove(
response2 = client.post("/webauthn/remove", json=data)
return user_token, response2

def _apple_special_verify_attestation(
self: FidoMetadataStore, attestation: Attestation, client_data: bytes
) -> bool:
if attestation.fmt is AttestationFormat.PACKED:
return cast(bool, self.verify_packed_attestation(attestation=attestation, client_data=client_data))
if attestation.fmt is AttestationFormat.APPLE:
# apple attestation cert in fido_mds test data is only valid for three days
return True
if attestation.fmt is AttestationFormat.TPM:
return cast(bool, self.verify_tpm_attestation(attestation=attestation, client_data=client_data))
if attestation.fmt is AttestationFormat.ANDROID_SAFETYNET:
# android attestation cert in fido_mds test data is only valid for three months
return True
if attestation.fmt is AttestationFormat.FIDO_U2F:
return cast(bool, self.verify_fido_u2f_attestation(attestation=attestation, client_data=client_data))
raise NotImplementedError(f"verification of {attestation.fmt.value} not implemented")

# actual tests

def test_begin_no_login(self) -> None:
Expand Down Expand Up @@ -636,7 +633,7 @@ def test_remove_wrong_csrf(self) -> None:

def test_authenticator_information(self, mocker: MockerFixture) -> None:
mocker.patch(
"fido_mds.FidoMetadataStore.verify_attestation", SecurityWebauthnTests._apple_special_verify_attestation
"fido_mds.FidoMetadataStore.verify_attestation", _apple_special_verify_attestation
)
authenticators = [YUBIKEY_4, YUBIKEY_5_NFC, MICROSOFT_SURFACE_1796, NEXUS_5, IPHONE_12, NONE_ATTESTATION]
for authenticator in authenticators:
Expand Down
Loading