tba-protocol-webauthn 0.4.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tba_protocol_webauthn-0.4.0.dev2/PKG-INFO +27 -0
- tba_protocol_webauthn-0.4.0.dev2/README.md +5 -0
- tba_protocol_webauthn-0.4.0.dev2/pyproject.toml +21 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/__init__.py +131 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/attestation/__init__.py +8 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/attestation/adapters.py +51 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/attestation/registry.py +59 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/bindings.py +46 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/ceremonies/__init__.py +21 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/ceremonies/authentication.py +169 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/ceremonies/registration.py +220 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/claims.py +5 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/codecs/__init__.py +23 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/codecs/attestation_object.py +55 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/codecs/authenticator_data.py +120 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/codecs/client_data.py +107 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/codecs/credential_public_key.py +10 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/compatibility.py +11 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/configuration.py +33 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/errors.py +29 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/extensions/__init__.py +17 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/extensions/registry.py +68 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/features.py +41 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/migrations.py +23 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/protocol.py +40 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/py.typed +0 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/related_origins.py +21 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/schemas.py +29 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/versions.py +54 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/wire_schemas/__init__.py +27 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/wire_schemas/common.py +113 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/wire_schemas/level2.py +29 -0
- tba_protocol_webauthn-0.4.0.dev2/src/tba_protocol_webauthn/wire_schemas/level3.py +29 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tba-protocol-webauthn
|
|
3
|
+
Version: 0.4.0.dev2
|
|
4
|
+
Summary: Versioned W3C Web Authentication relying-party protocol implementation.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.10,<3.15
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: cbor2 (==6.1.3)
|
|
14
|
+
Requires-Dist: tba-capability-public-key-authentication (==0.4.0.dev2)
|
|
15
|
+
Requires-Dist: tba-capability-public-key-registration (==0.4.0.dev2)
|
|
16
|
+
Requires-Dist: tba-concrete-cose (==0.4.0.dev2)
|
|
17
|
+
Requires-Dist: tba-contracts-identity (==0.4.0.dev2)
|
|
18
|
+
Requires-Dist: tba-primitives-identity (==0.4.0.dev2)
|
|
19
|
+
Requires-Dist: tba-provider-cose-cryptography (==0.4.0.dev2)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# tba-protocol-webauthn
|
|
23
|
+
|
|
24
|
+
Versioned W3C WebAuthn relying-party schemas, codecs, registration verification, authentication verification, feature selection, and capability bindings.
|
|
25
|
+
|
|
26
|
+
This package does not implement CTAP transports or durable storage.
|
|
27
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tba-protocol-webauthn"
|
|
3
|
+
version = "0.4.0.dev2"
|
|
4
|
+
description = "Versioned W3C Web Authentication relying-party protocol implementation."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10,<3.15"
|
|
7
|
+
license = "Apache-2.0"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"cbor2==6.1.3",
|
|
10
|
+
"tba-primitives-identity==0.4.0.dev2",
|
|
11
|
+
"tba-contracts-identity==0.4.0.dev2",
|
|
12
|
+
"tba-concrete-cose==0.4.0.dev2",
|
|
13
|
+
"tba-provider-cose-cryptography==0.4.0.dev2",
|
|
14
|
+
"tba-capability-public-key-registration==0.4.0.dev2",
|
|
15
|
+
"tba-capability-public-key-authentication==0.4.0.dev2",
|
|
16
|
+
]
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry-core>=1.0.0"]
|
|
19
|
+
build-backend = "poetry.core.masonry.api"
|
|
20
|
+
[tool.poetry]
|
|
21
|
+
packages = [{ include = "tba_protocol_webauthn", from = "src" }]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Versioned W3C Web Authentication relying-party protocol."""
|
|
2
|
+
|
|
3
|
+
from .attestation import (
|
|
4
|
+
AttestationVerificationInput,
|
|
5
|
+
AttestationVerifierRegistry,
|
|
6
|
+
build_attestation_registry,
|
|
7
|
+
)
|
|
8
|
+
from .bindings import CAPABILITY_REQUIREMENTS
|
|
9
|
+
from .ceremonies import (
|
|
10
|
+
AuthenticationExpectation,
|
|
11
|
+
RegistrationExpectation,
|
|
12
|
+
build_creation_options,
|
|
13
|
+
build_request_options,
|
|
14
|
+
verify_authentication_response,
|
|
15
|
+
verify_registration_response,
|
|
16
|
+
)
|
|
17
|
+
from .claims import CLAIM_NAMES
|
|
18
|
+
from .codecs import (
|
|
19
|
+
AttestationObject,
|
|
20
|
+
AttestedCredentialData,
|
|
21
|
+
AuthenticatorData,
|
|
22
|
+
AuthenticatorDataFlags,
|
|
23
|
+
ParsedClientData,
|
|
24
|
+
decode_attestation_object,
|
|
25
|
+
decode_authenticator_data,
|
|
26
|
+
decode_credential_public_key,
|
|
27
|
+
parse_client_data,
|
|
28
|
+
)
|
|
29
|
+
from .compatibility import compatible_features
|
|
30
|
+
from .configuration import WebAuthnConfiguration
|
|
31
|
+
from .errors import (
|
|
32
|
+
AuthenticationVerificationError,
|
|
33
|
+
AttestationObjectError,
|
|
34
|
+
AuthenticatorDataError,
|
|
35
|
+
ClientDataError,
|
|
36
|
+
RegistrationVerificationError,
|
|
37
|
+
WebAuthnProtocolError,
|
|
38
|
+
WebAuthnVerificationError,
|
|
39
|
+
)
|
|
40
|
+
from .extensions import (
|
|
41
|
+
ExtensionRegistry,
|
|
42
|
+
validate_appid,
|
|
43
|
+
validate_cred_props,
|
|
44
|
+
validate_credential_protection,
|
|
45
|
+
validate_large_blob,
|
|
46
|
+
validate_payment,
|
|
47
|
+
)
|
|
48
|
+
from .features import BASE_FEATURES, LEVEL_3_FEATURES, WebAuthnFeature, features_for
|
|
49
|
+
from .migrations import migration_path
|
|
50
|
+
from .protocol import WebAuthnProtocol
|
|
51
|
+
from .related_origins import origin_is_allowed
|
|
52
|
+
from .schemas import (
|
|
53
|
+
AuthenticatorAssertionResponse,
|
|
54
|
+
AuthenticatorAttestationResponse,
|
|
55
|
+
AuthenticatorSelectionCriteria,
|
|
56
|
+
CollectedClientData,
|
|
57
|
+
PublicKeyCredential,
|
|
58
|
+
PublicKeyCredentialCreationOptions,
|
|
59
|
+
PublicKeyCredentialDescriptor,
|
|
60
|
+
PublicKeyCredentialParameters,
|
|
61
|
+
PublicKeyCredentialRequestOptions,
|
|
62
|
+
PublicKeyCredentialRpEntity,
|
|
63
|
+
PublicKeyCredentialUserEntity,
|
|
64
|
+
)
|
|
65
|
+
from .versions import (
|
|
66
|
+
CURRENT_VERSION,
|
|
67
|
+
WEB_AUTHN_REVISIONS,
|
|
68
|
+
WebAuthnRevision,
|
|
69
|
+
WebAuthnVersion,
|
|
70
|
+
resolve_revision,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"AttestationObject",
|
|
75
|
+
"AttestationVerificationInput",
|
|
76
|
+
"AttestationVerifierRegistry",
|
|
77
|
+
"AttestedCredentialData",
|
|
78
|
+
"AuthenticationExpectation",
|
|
79
|
+
"AuthenticationVerificationError",
|
|
80
|
+
"AuthenticatorAssertionResponse",
|
|
81
|
+
"AuthenticatorAttestationResponse",
|
|
82
|
+
"AuthenticatorData",
|
|
83
|
+
"AuthenticatorDataError",
|
|
84
|
+
"AuthenticatorDataFlags",
|
|
85
|
+
"AuthenticatorSelectionCriteria",
|
|
86
|
+
"BASE_FEATURES",
|
|
87
|
+
"CAPABILITY_REQUIREMENTS",
|
|
88
|
+
"CLAIM_NAMES",
|
|
89
|
+
"CURRENT_VERSION",
|
|
90
|
+
"ClientDataError",
|
|
91
|
+
"CollectedClientData",
|
|
92
|
+
"ExtensionRegistry",
|
|
93
|
+
"LEVEL_3_FEATURES",
|
|
94
|
+
"ParsedClientData",
|
|
95
|
+
"PublicKeyCredential",
|
|
96
|
+
"PublicKeyCredentialCreationOptions",
|
|
97
|
+
"PublicKeyCredentialDescriptor",
|
|
98
|
+
"PublicKeyCredentialParameters",
|
|
99
|
+
"PublicKeyCredentialRequestOptions",
|
|
100
|
+
"PublicKeyCredentialRpEntity",
|
|
101
|
+
"PublicKeyCredentialUserEntity",
|
|
102
|
+
"RegistrationExpectation",
|
|
103
|
+
"RegistrationVerificationError",
|
|
104
|
+
"WEB_AUTHN_REVISIONS",
|
|
105
|
+
"WebAuthnConfiguration",
|
|
106
|
+
"WebAuthnFeature",
|
|
107
|
+
"WebAuthnProtocol",
|
|
108
|
+
"WebAuthnProtocolError",
|
|
109
|
+
"WebAuthnRevision",
|
|
110
|
+
"WebAuthnVerificationError",
|
|
111
|
+
"WebAuthnVersion",
|
|
112
|
+
"build_attestation_registry",
|
|
113
|
+
"build_creation_options",
|
|
114
|
+
"build_request_options",
|
|
115
|
+
"compatible_features",
|
|
116
|
+
"decode_attestation_object",
|
|
117
|
+
"decode_authenticator_data",
|
|
118
|
+
"decode_credential_public_key",
|
|
119
|
+
"features_for",
|
|
120
|
+
"migration_path",
|
|
121
|
+
"origin_is_allowed",
|
|
122
|
+
"parse_client_data",
|
|
123
|
+
"resolve_revision",
|
|
124
|
+
"validate_appid",
|
|
125
|
+
"validate_cred_props",
|
|
126
|
+
"validate_credential_protection",
|
|
127
|
+
"validate_large_blob",
|
|
128
|
+
"validate_payment",
|
|
129
|
+
"verify_authentication_response",
|
|
130
|
+
"verify_registration_response",
|
|
131
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Normalize provider-specific attestation verifier call signatures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from .registry import AttestationVerifierRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_attestation_registry(
|
|
11
|
+
*,
|
|
12
|
+
packed: Callable[..., object] | None = None,
|
|
13
|
+
u2f: Callable[..., object] | None = None,
|
|
14
|
+
tpm: Callable[..., object] | None = None,
|
|
15
|
+
) -> AttestationVerifierRegistry:
|
|
16
|
+
registry = AttestationVerifierRegistry()
|
|
17
|
+
if packed:
|
|
18
|
+
registry.register(
|
|
19
|
+
"packed",
|
|
20
|
+
lambda value: packed(
|
|
21
|
+
statement=value.statement,
|
|
22
|
+
authenticator_data=value.authenticator_data,
|
|
23
|
+
client_data_hash=value.client_data_hash,
|
|
24
|
+
credential_public_key=value.credential_public_key,
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
if u2f:
|
|
28
|
+
registry.register(
|
|
29
|
+
"fido-u2f",
|
|
30
|
+
lambda value: u2f(
|
|
31
|
+
statement=value.statement,
|
|
32
|
+
rp_id_hash=value.rp_id_hash,
|
|
33
|
+
client_data_hash=value.client_data_hash,
|
|
34
|
+
credential_id=value.credential_id,
|
|
35
|
+
credential_public_key=value.credential_public_key,
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
if tpm:
|
|
39
|
+
registry.register(
|
|
40
|
+
"tpm",
|
|
41
|
+
lambda value: tpm(
|
|
42
|
+
statement=value.statement,
|
|
43
|
+
authenticator_data=value.authenticator_data,
|
|
44
|
+
client_data_hash=value.client_data_hash,
|
|
45
|
+
credential_public_key=value.credential_public_key,
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
return registry
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
__all__ = ["build_attestation_registry"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Attestation format dispatch without provider dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from tba_contracts_identity.public_key_authentication import AttestationTrustResult
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class AttestationVerificationInput:
|
|
13
|
+
format: str
|
|
14
|
+
statement: Mapping[object, object]
|
|
15
|
+
authenticator_data: bytes
|
|
16
|
+
client_data_hash: bytes
|
|
17
|
+
rp_id_hash: bytes
|
|
18
|
+
credential_id: bytes
|
|
19
|
+
credential_public_key: bytes
|
|
20
|
+
aaguid: bytes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
AttestationVerifier = Callable[[AttestationVerificationInput], AttestationTrustResult]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AttestationVerifierRegistry:
|
|
27
|
+
def __init__(
|
|
28
|
+
self, verifiers: Mapping[str, AttestationVerifier] | None = None
|
|
29
|
+
) -> None:
|
|
30
|
+
self._verifiers = dict(verifiers or {})
|
|
31
|
+
|
|
32
|
+
def register(self, format_identifier: str, verifier: AttestationVerifier) -> None:
|
|
33
|
+
if format_identifier in self._verifiers:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"attestation verifier already registered: {format_identifier}"
|
|
36
|
+
)
|
|
37
|
+
self._verifiers[format_identifier] = verifier
|
|
38
|
+
|
|
39
|
+
def verify(self, value: AttestationVerificationInput) -> AttestationTrustResult:
|
|
40
|
+
try:
|
|
41
|
+
verifier = self._verifiers[value.format]
|
|
42
|
+
except KeyError as exc:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"attestation format requires a configured provider: {value.format}"
|
|
45
|
+
) from exc
|
|
46
|
+
result = verifier(value)
|
|
47
|
+
if not isinstance(result, AttestationTrustResult):
|
|
48
|
+
raise TypeError("attestation verifier must return AttestationTrustResult")
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
def supported_formats(self) -> tuple[str, ...]:
|
|
52
|
+
return tuple(sorted(self._verifiers))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"AttestationVerificationInput",
|
|
57
|
+
"AttestationVerifier",
|
|
58
|
+
"AttestationVerifierRegistry",
|
|
59
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Map WebAuthn wire ceremonies to stable semantic capability operations."""
|
|
2
|
+
|
|
3
|
+
from tba_contracts_identity.capabilities import ProtocolCapabilityRequirement
|
|
4
|
+
|
|
5
|
+
from .versions import CURRENT_VERSION
|
|
6
|
+
|
|
7
|
+
CAPABILITY_REQUIREMENTS = (
|
|
8
|
+
ProtocolCapabilityRequirement(
|
|
9
|
+
"webauthn",
|
|
10
|
+
CURRENT_VERSION.identifier,
|
|
11
|
+
"webauthn-registration-begin",
|
|
12
|
+
"PublicKeyCredentialCreationOptions",
|
|
13
|
+
"credential.public-key.registration",
|
|
14
|
+
"begin_public_key_registration",
|
|
15
|
+
"public-key-registration-options",
|
|
16
|
+
),
|
|
17
|
+
ProtocolCapabilityRequirement(
|
|
18
|
+
"webauthn",
|
|
19
|
+
CURRENT_VERSION.identifier,
|
|
20
|
+
"webauthn-registration-complete",
|
|
21
|
+
"AuthenticatorAttestationResponse",
|
|
22
|
+
"credential.public-key.registration",
|
|
23
|
+
"complete_public_key_registration",
|
|
24
|
+
"verified-public-key-registration",
|
|
25
|
+
),
|
|
26
|
+
ProtocolCapabilityRequirement(
|
|
27
|
+
"webauthn",
|
|
28
|
+
CURRENT_VERSION.identifier,
|
|
29
|
+
"webauthn-authentication-begin",
|
|
30
|
+
"PublicKeyCredentialRequestOptions",
|
|
31
|
+
"credential.public-key.authentication",
|
|
32
|
+
"begin_public_key_authentication",
|
|
33
|
+
"public-key-authentication-options",
|
|
34
|
+
),
|
|
35
|
+
ProtocolCapabilityRequirement(
|
|
36
|
+
"webauthn",
|
|
37
|
+
CURRENT_VERSION.identifier,
|
|
38
|
+
"webauthn-authentication-complete",
|
|
39
|
+
"AuthenticatorAssertionResponse",
|
|
40
|
+
"credential.public-key.authentication",
|
|
41
|
+
"complete_public_key_authentication",
|
|
42
|
+
"verified-public-key-assertion",
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
__all__ = ["CAPABILITY_REQUIREMENTS"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""WebAuthn registration and authentication ceremonies."""
|
|
2
|
+
|
|
3
|
+
from .authentication import (
|
|
4
|
+
AuthenticationExpectation,
|
|
5
|
+
build_request_options,
|
|
6
|
+
verify_authentication_response,
|
|
7
|
+
)
|
|
8
|
+
from .registration import (
|
|
9
|
+
RegistrationExpectation,
|
|
10
|
+
build_creation_options,
|
|
11
|
+
verify_registration_response,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"AuthenticationExpectation",
|
|
16
|
+
"RegistrationExpectation",
|
|
17
|
+
"build_creation_options",
|
|
18
|
+
"build_request_options",
|
|
19
|
+
"verify_authentication_response",
|
|
20
|
+
"verify_registration_response",
|
|
21
|
+
]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""WebAuthn Level 2 authentication ceremony processing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import secrets
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from tba_contracts_identity.public_key_authentication import (
|
|
10
|
+
AuthenticatorProvenanceEvidence,
|
|
11
|
+
CredentialBackupEvidence,
|
|
12
|
+
CredentialLifecycleStatus,
|
|
13
|
+
PublicKeyAuthenticationEvidence,
|
|
14
|
+
PublicKeyCredentialSource,
|
|
15
|
+
UserPresenceEvidence,
|
|
16
|
+
UserVerificationEvidence,
|
|
17
|
+
VerifiedPublicKeyAssertion,
|
|
18
|
+
VerifierBindingEvidence,
|
|
19
|
+
)
|
|
20
|
+
from tba_provider_cose_cryptography import verify_detached_signature
|
|
21
|
+
|
|
22
|
+
from ..codecs import decode_authenticator_data, parse_client_data
|
|
23
|
+
from ..errors import AuthenticationVerificationError
|
|
24
|
+
from ..schemas import (
|
|
25
|
+
PublicKeyCredential,
|
|
26
|
+
PublicKeyCredentialDescriptor,
|
|
27
|
+
PublicKeyCredentialRequestOptions,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class AuthenticationExpectation:
|
|
33
|
+
ceremony_id: str
|
|
34
|
+
rp_id: str
|
|
35
|
+
origin: str
|
|
36
|
+
challenge: bytes
|
|
37
|
+
credential: PublicKeyCredentialSource
|
|
38
|
+
user_verification: str = "preferred"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_request_options(
|
|
42
|
+
*,
|
|
43
|
+
rp_id: str,
|
|
44
|
+
allowed_credentials: tuple[PublicKeyCredentialSource, ...] = (),
|
|
45
|
+
user_verification: str = "preferred",
|
|
46
|
+
timeout_ms: int = 300_000,
|
|
47
|
+
challenge: bytes | None = None,
|
|
48
|
+
) -> PublicKeyCredentialRequestOptions:
|
|
49
|
+
return PublicKeyCredentialRequestOptions(
|
|
50
|
+
challenge=challenge or secrets.token_bytes(32),
|
|
51
|
+
timeout=timeout_ms,
|
|
52
|
+
rp_id=rp_id,
|
|
53
|
+
allow_credentials=tuple(
|
|
54
|
+
PublicKeyCredentialDescriptor(
|
|
55
|
+
id=item.external_id, transports=item.transports
|
|
56
|
+
)
|
|
57
|
+
for item in allowed_credentials
|
|
58
|
+
),
|
|
59
|
+
user_verification=user_verification,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def verify_authentication_response(
|
|
64
|
+
credential: PublicKeyCredential,
|
|
65
|
+
*,
|
|
66
|
+
expected: AuthenticationExpectation,
|
|
67
|
+
) -> VerifiedPublicKeyAssertion:
|
|
68
|
+
try:
|
|
69
|
+
source = expected.credential
|
|
70
|
+
if source.status is not CredentialLifecycleStatus.ACTIVE:
|
|
71
|
+
raise AuthenticationVerificationError("credential is not active")
|
|
72
|
+
if source.binding.rp_id != expected.rp_id:
|
|
73
|
+
raise AuthenticationVerificationError(
|
|
74
|
+
"credential is not bound to the relying party"
|
|
75
|
+
)
|
|
76
|
+
if credential.type != "public-key" or not secrets.compare_digest(
|
|
77
|
+
credential.raw_id, source.external_id
|
|
78
|
+
):
|
|
79
|
+
raise AuthenticationVerificationError(
|
|
80
|
+
"asserted credential identifier does not match"
|
|
81
|
+
)
|
|
82
|
+
response = credential.response
|
|
83
|
+
if not hasattr(response, "signature"):
|
|
84
|
+
raise AuthenticationVerificationError(
|
|
85
|
+
"authentication requires an assertion response"
|
|
86
|
+
)
|
|
87
|
+
client = parse_client_data(
|
|
88
|
+
response.client_data_json,
|
|
89
|
+
expected_type="webauthn.get",
|
|
90
|
+
expected_challenge=expected.challenge,
|
|
91
|
+
expected_origin=expected.origin,
|
|
92
|
+
)
|
|
93
|
+
if client.value.cross_origin:
|
|
94
|
+
raise AuthenticationVerificationError(
|
|
95
|
+
"cross-origin authentication is not enabled"
|
|
96
|
+
)
|
|
97
|
+
auth = decode_authenticator_data(response.authenticator_data)
|
|
98
|
+
if not auth.matches_rp_id_hash(
|
|
99
|
+
hashlib.sha256(expected.rp_id.encode("utf-8")).digest()
|
|
100
|
+
):
|
|
101
|
+
raise AuthenticationVerificationError(
|
|
102
|
+
"relying-party identifier hash does not match"
|
|
103
|
+
)
|
|
104
|
+
if not auth.user_present:
|
|
105
|
+
raise AuthenticationVerificationError(
|
|
106
|
+
"authenticator did not report user presence"
|
|
107
|
+
)
|
|
108
|
+
if expected.user_verification == "required" and not auth.user_verified:
|
|
109
|
+
raise AuthenticationVerificationError(
|
|
110
|
+
"authenticator did not report user verification"
|
|
111
|
+
)
|
|
112
|
+
if response.user_handle is not None and source.binding.user_handle is not None:
|
|
113
|
+
if not secrets.compare_digest(
|
|
114
|
+
response.user_handle, source.binding.user_handle
|
|
115
|
+
):
|
|
116
|
+
raise AuthenticationVerificationError(
|
|
117
|
+
"asserted user handle does not match"
|
|
118
|
+
)
|
|
119
|
+
if not verify_detached_signature(
|
|
120
|
+
public_key=source.public_key,
|
|
121
|
+
algorithm=source.algorithm,
|
|
122
|
+
message=response.authenticator_data + client.digest,
|
|
123
|
+
signature=response.signature,
|
|
124
|
+
):
|
|
125
|
+
raise AuthenticationVerificationError("assertion signature is invalid")
|
|
126
|
+
if (
|
|
127
|
+
source.sign_count
|
|
128
|
+
and auth.sign_count
|
|
129
|
+
and auth.sign_count <= source.sign_count
|
|
130
|
+
):
|
|
131
|
+
raise AuthenticationVerificationError(
|
|
132
|
+
"authenticator counter did not advance"
|
|
133
|
+
)
|
|
134
|
+
principal_id = source.binding.principal_id
|
|
135
|
+
if principal_id is None:
|
|
136
|
+
raise AuthenticationVerificationError(
|
|
137
|
+
"credential has no resolved principal binding"
|
|
138
|
+
)
|
|
139
|
+
evidence = PublicKeyAuthenticationEvidence(
|
|
140
|
+
credential_id=source.credential_id,
|
|
141
|
+
user_presence=UserPresenceEvidence(True),
|
|
142
|
+
user_verification=UserVerificationEvidence(auth.user_verified),
|
|
143
|
+
verifier_binding=VerifierBindingEvidence(
|
|
144
|
+
expected.rp_id, expected.origin, True
|
|
145
|
+
),
|
|
146
|
+
backup=CredentialBackupEvidence(
|
|
147
|
+
eligible=bool(auth.flags & 0x08),
|
|
148
|
+
backed_up=bool(auth.flags & 0x10),
|
|
149
|
+
),
|
|
150
|
+
provenance=AuthenticatorProvenanceEvidence(),
|
|
151
|
+
)
|
|
152
|
+
return VerifiedPublicKeyAssertion(
|
|
153
|
+
ceremony_id=expected.ceremony_id,
|
|
154
|
+
credential_id=source.credential_id,
|
|
155
|
+
principal_id=principal_id,
|
|
156
|
+
sign_count=auth.sign_count,
|
|
157
|
+
evidence=evidence,
|
|
158
|
+
)
|
|
159
|
+
except AuthenticationVerificationError:
|
|
160
|
+
raise
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
raise AuthenticationVerificationError(str(exc)) from exc
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
__all__ = [
|
|
166
|
+
"AuthenticationExpectation",
|
|
167
|
+
"build_request_options",
|
|
168
|
+
"verify_authentication_response",
|
|
169
|
+
]
|