Skip to content

Commit

Permalink
handle connection errors in FIDO MDS initialization; add switch for d…
Browse files Browse the repository at this point in the history
…isabling MDS
  • Loading branch information
byewokko committed Feb 29, 2024
1 parent 3fa5e7a commit 02d708b
Showing 1 changed file with 34 additions and 11 deletions.
45 changes: 34 additions & 11 deletions seacatauth/authn/webauthn/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@

asab.Config.add_defaults({
"seacatauth:webauthn": {
"attestation": "direct"
"attestation": "direct",

# Authenticator metadata source file, for details see https://fidoalliance.org/metadata
# Possible values:
# - HTTPS or HTTP address of a JWT MDS file (defaults to "https://mds3.fidoalliance.org")
# - Relative or absolute path of a JWT MDS file
# - Empty value or "DISABLED" (disables the metadata service)
"metadata_service_url": "https://mds3.fidoalliance.org",
}
})

Expand All @@ -37,7 +44,6 @@ class WebAuthnService(asab.Service):
WebAuthnCredentialCollection = "wa"
WebAuthnRegistrationChallengeCollection = "warc"
FidoMetadataServiceCollection = "fms"
FidoMetadataServiceUrl = "https://mds3.fidoalliance.org"

def __init__(self, app, service_name="seacatauth.WebAuthnService"):
super().__init__(app, service_name)
Expand Down Expand Up @@ -69,41 +75,54 @@ def __init__(self, app, service_name="seacatauth.WebAuthnService"):
webauthn.helpers.structs.COSEAlgorithmIdentifier(-7) # Es256
]

self.FidoMetadataServiceUrl = asab.Config.get("seacatauth:webauthn", "metadata_service_url")
if self.FidoMetadataServiceUrl in ("", "DISABLED"):
self.FidoMetadataServiceUrl = None
self._FidoMetadataByAAGUID: typing.Dict[int, dict] | None = None

app.PubSub.subscribe("Application.housekeeping!", self._on_housekeeping)


async def initialize(self, app):
await self._load_fido_metadata()
try:
await self._load_fido_metadata()
except Exception as e:
L.exception("Failed to fetch FIDO Alliance Metadata ({}: {})".format(e.__class__.__name__, e))


async def _on_housekeeping(self, event_name):
await self._delete_expired_challenges()
await self._load_fido_metadata(only_if_empty=False)
try:
await self._load_fido_metadata(force_reload=True)
except Exception as e:
L.info("Failed to fetch FIDO Alliance Metadata: {}".format(e))


async def _load_fido_metadata(self, only_if_empty=True):
async def _load_fido_metadata(self, *, force_reload: bool = False):
"""
Download and decode FIDO metadata from FIDO Alliance Metadata Service (MDS) and prepare a lookup dictionary.
"""
if only_if_empty:
if not self.FidoMetadataServiceUrl:
return

if not force_reload:
coll = await self.StorageService.collection(self.FidoMetadataServiceCollection)
count = await coll.estimated_document_count()
if count > 0:
return

try:
if self.FidoMetadataServiceUrl.startswith("https://") or self.FidoMetadataServiceUrl.startswith("http://"):
async with aiohttp.ClientSession() as session:
async with session.get(self.FidoMetadataServiceUrl) as resp:
if resp.status != 200:
text = await resp.text()
L.error("Failed to fetch FIDO metadata:\n{}".format(text[:1000]))
return
jwt = await resp.text()
except ConnectionError:
L.error("Cannot connect to FIDO Alliance Metadata Service.")
return
else:
# Load from local file
with open(self.FidoMetadataServiceUrl) as f:
jwt = f.read()

jwt = jwcrypto.jwt.JWT(jwt=jwt)
cert_chain = jwt.token.jose_header.get("x5c", [])
Expand Down Expand Up @@ -179,7 +198,11 @@ async def create_webauthn_credential(
L.log(asab.LOG_NOTICE, "WebAuthn credential created", struct_data={"wacid": wacid.hex()})

async def _get_authenticator_metadata(self, verified_registration):
await self._load_fido_metadata()
try:
await self._load_fido_metadata()
except Exception as e:
L.info("Failed to fetch FIDO Alliance Metadata: {}".format(e))

aaguid = bytes.fromhex(verified_registration.aaguid.replace("-", ""))
if aaguid == 0:
# Authenticators with other identifiers than AAGUID are not supported
Expand Down

0 comments on commit 02d708b

Please sign in to comment.