tba-protocol-cwt 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_cwt-0.4.0.dev2/PKG-INFO +35 -0
- tba_protocol_cwt-0.4.0.dev2/README.md +9 -0
- tba_protocol_cwt-0.4.0.dev2/pyproject.toml +24 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/__init__.py +73 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/bindings.py +37 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/claims.py +100 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/compatibility.py +31 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/errors.py +12 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/features.py +19 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/migrations.py +19 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/profile.py +18 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/protocol.py +21 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/py.typed +1 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/schemas.py +25 -0
- tba_protocol_cwt-0.4.0.dev2/src/tba_protocol_cwt/versions.py +30 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tba-protocol-cwt
|
|
3
|
+
Version: 0.4.0.dev2
|
|
4
|
+
Summary: Versioned RFC 8392 CWT claim-set composition.
|
|
5
|
+
Requires-Python: >=3.10,<3.15
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
12
|
+
Requires-Dist: tba-bases-claim (==0.4.0.dev2)
|
|
13
|
+
Requires-Dist: tba-concrete-claim-access-token-digest (==0.4.0.dev2)
|
|
14
|
+
Requires-Dist: tba-concrete-claim-audience (==0.4.0.dev2)
|
|
15
|
+
Requires-Dist: tba-concrete-claim-authorization-context (==0.4.0.dev2)
|
|
16
|
+
Requires-Dist: tba-concrete-claim-expiration (==0.4.0.dev2)
|
|
17
|
+
Requires-Dist: tba-concrete-claim-issued-at (==0.4.0.dev2)
|
|
18
|
+
Requires-Dist: tba-concrete-claim-issuer (==0.4.0.dev2)
|
|
19
|
+
Requires-Dist: tba-concrete-claim-jwt-id (==0.4.0.dev2)
|
|
20
|
+
Requires-Dist: tba-concrete-claim-not-before (==0.4.0.dev2)
|
|
21
|
+
Requires-Dist: tba-concrete-claim-subject (==0.4.0.dev2)
|
|
22
|
+
Requires-Dist: tba-concrete-cwt (==0.4.0.dev2)
|
|
23
|
+
Requires-Dist: tba-contracts-identity (==0.4.0.dev2)
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# tba-protocol-cwt
|
|
27
|
+
|
|
28
|
+
Owns RFC 8392 version history and composition of independently packaged integer-labelled CWT claims. It does not own CBOR/COSE cryptography or key resolution.
|
|
29
|
+
|
|
30
|
+
Draft `actx` and `ath` use text keys in `CWT_EXTENSION_CLAIMS`; both lookup maps
|
|
31
|
+
include these extensions without treating them as registered integer labels.
|
|
32
|
+
`build_cwt_claims(*claims, actx_field_labels=...)` builds a CBOR-ready
|
|
33
|
+
`CwtClaimsSet`, converting `actx` fields using explicit protocol labels and
|
|
34
|
+
`ath` to its 32-byte SHA-256 digest. This does not implement full DPoP verification.
|
|
35
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# tba-protocol-cwt
|
|
2
|
+
|
|
3
|
+
Owns RFC 8392 version history and composition of independently packaged integer-labelled CWT claims. It does not own CBOR/COSE cryptography or key resolution.
|
|
4
|
+
|
|
5
|
+
Draft `actx` and `ath` use text keys in `CWT_EXTENSION_CLAIMS`; both lookup maps
|
|
6
|
+
include these extensions without treating them as registered integer labels.
|
|
7
|
+
`build_cwt_claims(*claims, actx_field_labels=...)` builds a CBOR-ready
|
|
8
|
+
`CwtClaimsSet`, converting `actx` fields using explicit protocol labels and
|
|
9
|
+
`ath` to its 32-byte SHA-256 digest. This does not implement full DPoP verification.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name="tba-protocol-cwt"
|
|
3
|
+
version="0.4.0.dev2"
|
|
4
|
+
description="Versioned RFC 8392 CWT claim-set composition."
|
|
5
|
+
readme="README.md"
|
|
6
|
+
requires-python=">=3.10,<3.15"
|
|
7
|
+
dependencies=["tba-concrete-claim-authorization-context==0.4.0.dev2",
|
|
8
|
+
"tba-concrete-claim-access-token-digest==0.4.0.dev2",
|
|
9
|
+
"tba-concrete-cwt==0.4.0.dev2",
|
|
10
|
+
"tba-contracts-identity==0.4.0.dev2",
|
|
11
|
+
"tba-bases-claim==0.4.0.dev2",
|
|
12
|
+
"tba-concrete-claim-issuer==0.4.0.dev2",
|
|
13
|
+
"tba-concrete-claim-subject==0.4.0.dev2",
|
|
14
|
+
"tba-concrete-claim-audience==0.4.0.dev2",
|
|
15
|
+
"tba-concrete-claim-expiration==0.4.0.dev2",
|
|
16
|
+
"tba-concrete-claim-not-before==0.4.0.dev2",
|
|
17
|
+
"tba-concrete-claim-issued-at==0.4.0.dev2",
|
|
18
|
+
"tba-concrete-claim-jwt-id==0.4.0.dev2",
|
|
19
|
+
]
|
|
20
|
+
[build-system]
|
|
21
|
+
requires=["poetry-core>=1.0.0"]
|
|
22
|
+
build-backend="poetry.core.masonry.api"
|
|
23
|
+
[tool.poetry]
|
|
24
|
+
packages=[{include="tba_protocol_cwt",from="src"}]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Versioned CBOR Web Token protocol ownership."""
|
|
2
|
+
|
|
3
|
+
from tba_contracts_identity.protocol_processing import (
|
|
4
|
+
build_protocol_capability_report,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from .bindings import CAPABILITY_REQUIREMENTS
|
|
8
|
+
from .claims import (
|
|
9
|
+
CWT_CLAIM_BY_LABEL,
|
|
10
|
+
CWT_EXTENSION_CLAIMS,
|
|
11
|
+
build_cwt_claims,
|
|
12
|
+
CWT_LABEL_BY_CLAIM,
|
|
13
|
+
CWT_REGISTERED_CLAIMS,
|
|
14
|
+
CwtClaimBinding,
|
|
15
|
+
compose_cwt_claim_set,
|
|
16
|
+
cwt_label_for,
|
|
17
|
+
)
|
|
18
|
+
from .compatibility import COMPATIBILITY_PATHS, CwtCompatibility, compatibility
|
|
19
|
+
from .errors import CwtProtocolError, UnsupportedCwtMediaTypeError
|
|
20
|
+
from .features import FEATURES_BY_VERSION, supports
|
|
21
|
+
from .migrations import migrate_claims
|
|
22
|
+
from .profile import CwtProfile
|
|
23
|
+
from .protocol import CwtProtocol
|
|
24
|
+
from .schemas import CWT_CARRIER, CwtCarrier, select_carrier
|
|
25
|
+
from .versions import (
|
|
26
|
+
CURRENT_VERSION,
|
|
27
|
+
VERSION_HISTORY,
|
|
28
|
+
VERSION_PUBLISHED,
|
|
29
|
+
VERSION_STATUS,
|
|
30
|
+
CwtVersion,
|
|
31
|
+
select_version,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def capability_report() -> dict[str, object]:
|
|
36
|
+
return build_protocol_capability_report(
|
|
37
|
+
protocol="cwt",
|
|
38
|
+
revision=CURRENT_VERSION.value,
|
|
39
|
+
features=tuple(FEATURES_BY_VERSION[CURRENT_VERSION.value]),
|
|
40
|
+
evidence_links=("tests/unit/test_cwt_standalone_claim_packages.py",),
|
|
41
|
+
extra_requirements=CAPABILITY_REQUIREMENTS,
|
|
42
|
+
include_default_artifact_requirements=False,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"CAPABILITY_REQUIREMENTS",
|
|
48
|
+
"COMPATIBILITY_PATHS",
|
|
49
|
+
"CURRENT_VERSION",
|
|
50
|
+
"CWT_CARRIER",
|
|
51
|
+
"CWT_REGISTERED_CLAIMS",
|
|
52
|
+
"CWT_CLAIM_BY_LABEL",
|
|
53
|
+
"CWT_EXTENSION_CLAIMS",
|
|
54
|
+
"build_cwt_claims",
|
|
55
|
+
"CWT_LABEL_BY_CLAIM",
|
|
56
|
+
"CwtClaimBinding",
|
|
57
|
+
"FEATURES_BY_VERSION",
|
|
58
|
+
"VERSION_HISTORY",
|
|
59
|
+
"VERSION_PUBLISHED",
|
|
60
|
+
"VERSION_STATUS",
|
|
61
|
+
"CwtCarrier",
|
|
62
|
+
"CwtCompatibility",
|
|
63
|
+
"CwtVersion",
|
|
64
|
+
"UnsupportedCwtMediaTypeError",
|
|
65
|
+
"capability_report",
|
|
66
|
+
"compatibility",
|
|
67
|
+
"compose_cwt_claim_set",
|
|
68
|
+
"cwt_label_for",
|
|
69
|
+
"migrate_claims",
|
|
70
|
+
"select_carrier",
|
|
71
|
+
"select_version",
|
|
72
|
+
"supports",
|
|
73
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""RFC 8392 wire operations mapped to reportable capabilities."""
|
|
2
|
+
|
|
3
|
+
from tba_contracts_identity.capabilities import ProtocolCapabilityRequirement
|
|
4
|
+
|
|
5
|
+
from .versions import CURRENT_VERSION
|
|
6
|
+
|
|
7
|
+
CAPABILITY_REQUIREMENTS = (
|
|
8
|
+
ProtocolCapabilityRequirement(
|
|
9
|
+
"cwt",
|
|
10
|
+
CURRENT_VERSION.value,
|
|
11
|
+
"cwt-cbor-decode",
|
|
12
|
+
"CBOR map",
|
|
13
|
+
"artifact.processing",
|
|
14
|
+
"decode",
|
|
15
|
+
"cwt-claims",
|
|
16
|
+
),
|
|
17
|
+
ProtocolCapabilityRequirement(
|
|
18
|
+
"cwt",
|
|
19
|
+
CURRENT_VERSION.value,
|
|
20
|
+
"cwt-cose-validation",
|
|
21
|
+
"COSE protected CWT",
|
|
22
|
+
"artifact.processing",
|
|
23
|
+
"validate",
|
|
24
|
+
"protected-token-envelope",
|
|
25
|
+
),
|
|
26
|
+
ProtocolCapabilityRequirement(
|
|
27
|
+
"cwt",
|
|
28
|
+
CURRENT_VERSION.value,
|
|
29
|
+
"cwt-cbor-encode",
|
|
30
|
+
"application/cwt",
|
|
31
|
+
"artifact.processing",
|
|
32
|
+
"encode",
|
|
33
|
+
"cwt",
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = ["CAPABILITY_REQUIREMENTS"]
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""RFC 8392 integer-label mappings for canonical semantic claims."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TypeAlias
|
|
5
|
+
|
|
6
|
+
from tba_concrete_cwt import CwtClaimsSet
|
|
7
|
+
from tba_concrete_claim_authorization_context import AuthorizationContextClaim
|
|
8
|
+
from tba_concrete_claim_access_token_digest import AccessTokenDigestClaim
|
|
9
|
+
from tba_concrete_claim_audience import AudienceClaim
|
|
10
|
+
from tba_bases_claim import ClaimBase
|
|
11
|
+
from tba_concrete_claim_expiration import ExpirationClaim
|
|
12
|
+
from tba_concrete_claim_issued_at import IssuedAtClaim
|
|
13
|
+
from tba_concrete_claim_issuer import IssuerClaim
|
|
14
|
+
from tba_concrete_claim_jwt_id import JwtIdClaim
|
|
15
|
+
from tba_concrete_claim_not_before import NotBeforeClaim
|
|
16
|
+
from tba_concrete_claim_subject import SubjectClaim
|
|
17
|
+
from tba_contracts_identity.claims import ClaimSet
|
|
18
|
+
|
|
19
|
+
from .versions import CURRENT_VERSION, CwtVersion
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ClaimClass: TypeAlias = type[ClaimBase]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True, slots=True)
|
|
26
|
+
class CwtClaimBinding:
|
|
27
|
+
label: str | int
|
|
28
|
+
claim_class: ClaimClass
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def semantic_name(self) -> str:
|
|
32
|
+
return str(self.claim_class.claim_name)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
CWT_REGISTERED_CLAIMS = (
|
|
36
|
+
CwtClaimBinding(1, IssuerClaim),
|
|
37
|
+
CwtClaimBinding(2, SubjectClaim),
|
|
38
|
+
CwtClaimBinding(3, AudienceClaim),
|
|
39
|
+
CwtClaimBinding(4, ExpirationClaim),
|
|
40
|
+
CwtClaimBinding(5, NotBeforeClaim),
|
|
41
|
+
CwtClaimBinding(6, IssuedAtClaim),
|
|
42
|
+
CwtClaimBinding(7, JwtIdClaim),
|
|
43
|
+
)
|
|
44
|
+
# Draft extensions have no assigned integer labels; never invent IANA values.
|
|
45
|
+
CWT_EXTENSION_CLAIMS = (
|
|
46
|
+
CwtClaimBinding("actx", AuthorizationContextClaim),
|
|
47
|
+
CwtClaimBinding("ath", AccessTokenDigestClaim),
|
|
48
|
+
)
|
|
49
|
+
CWT_LABEL_BY_CLAIM = {
|
|
50
|
+
binding.claim_class: binding.label for binding in (*CWT_REGISTERED_CLAIMS, *CWT_EXTENSION_CLAIMS)
|
|
51
|
+
}
|
|
52
|
+
CWT_CLAIM_BY_LABEL = {
|
|
53
|
+
binding.label: binding.claim_class for binding in (*CWT_REGISTERED_CLAIMS, *CWT_EXTENSION_CLAIMS)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def compose_cwt_claim_set(
|
|
58
|
+
*claims: ClaimBase,
|
|
59
|
+
version: CwtVersion = CURRENT_VERSION,
|
|
60
|
+
) -> ClaimSet:
|
|
61
|
+
return ClaimSet(tuple(claims), "cwt", version.value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cwt_label_for(claim: ClaimBase | ClaimClass) -> str | int:
|
|
65
|
+
claim_class = claim if isinstance(claim, type) else type(claim)
|
|
66
|
+
try:
|
|
67
|
+
return CWT_LABEL_BY_CLAIM[claim_class]
|
|
68
|
+
except KeyError as exc:
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"claim is not registered by CWT: {claim_class.__name__}"
|
|
71
|
+
) from exc
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def build_cwt_claims(*claims: ClaimBase, actx_field_labels=None) -> CwtClaimsSet:
|
|
75
|
+
"""Map semantic objects to CWT keys and carrier-specific values."""
|
|
76
|
+
values = {}
|
|
77
|
+
for claim in claims:
|
|
78
|
+
label = cwt_label_for(claim)
|
|
79
|
+
if label in values:
|
|
80
|
+
raise ValueError(f"duplicate CWT claim: {label}")
|
|
81
|
+
if isinstance(claim, AuthorizationContextClaim):
|
|
82
|
+
value = claim.to_cwt_value(field_labels=actx_field_labels)
|
|
83
|
+
elif isinstance(claim, AccessTokenDigestClaim):
|
|
84
|
+
value = claim.to_cwt_value()
|
|
85
|
+
else:
|
|
86
|
+
value = claim.value
|
|
87
|
+
values[label] = value
|
|
88
|
+
return CwtClaimsSet(values)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
__all__ = [
|
|
92
|
+
"CWT_CLAIM_BY_LABEL",
|
|
93
|
+
"CWT_EXTENSION_CLAIMS",
|
|
94
|
+
"build_cwt_claims",
|
|
95
|
+
"CWT_LABEL_BY_CLAIM",
|
|
96
|
+
"CWT_REGISTERED_CLAIMS",
|
|
97
|
+
"CwtClaimBinding",
|
|
98
|
+
"compose_cwt_claim_set",
|
|
99
|
+
"cwt_label_for",
|
|
100
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""CWT revision compatibility paths."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from .versions import CURRENT_VERSION
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class CwtCompatibility:
|
|
10
|
+
source: str
|
|
11
|
+
target: str
|
|
12
|
+
compatible: bool
|
|
13
|
+
migration_required: bool
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
COMPATIBILITY_PATHS = (
|
|
17
|
+
CwtCompatibility(CURRENT_VERSION.value, CURRENT_VERSION.value, True, False),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def compatibility(
|
|
22
|
+
source: str,
|
|
23
|
+
target: str = CURRENT_VERSION.value,
|
|
24
|
+
) -> CwtCompatibility:
|
|
25
|
+
for path in COMPATIBILITY_PATHS:
|
|
26
|
+
if path.source == source and path.target == target:
|
|
27
|
+
return path
|
|
28
|
+
return CwtCompatibility(source, target, False, False)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["COMPATIBILITY_PATHS", "CwtCompatibility", "compatibility"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""CWT protocol mapping errors."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CwtProtocolError(ValueError):
|
|
5
|
+
"""Base error for an RFC 8392 mapping failure."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UnsupportedCwtMediaTypeError(CwtProtocolError):
|
|
9
|
+
"""Raised when the selected carrier is not `application/cwt`."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = ["CwtProtocolError", "UnsupportedCwtMediaTypeError"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""RFC 8392 feature ownership."""
|
|
2
|
+
|
|
3
|
+
from .versions import CURRENT_VERSION
|
|
4
|
+
|
|
5
|
+
FEATURES_BY_VERSION = {
|
|
6
|
+
CURRENT_VERSION.value: frozenset(
|
|
7
|
+
{"registered-claims", "cbor-map", "cose-protection", "application-cwt"}
|
|
8
|
+
)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def supports(feature: str, version: str = CURRENT_VERSION.value) -> bool:
|
|
13
|
+
try:
|
|
14
|
+
return feature in FEATURES_BY_VERSION[version]
|
|
15
|
+
except KeyError as exc:
|
|
16
|
+
raise ValueError(f"unsupported CWT version: {version}") from exc
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["FEATURES_BY_VERSION", "supports"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""CWT claim-map migration entry point."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
from .versions import CURRENT_VERSION
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def migrate_claims(
|
|
9
|
+
value: Mapping[str | int, Any],
|
|
10
|
+
*,
|
|
11
|
+
source: str,
|
|
12
|
+
target: str = CURRENT_VERSION.value,
|
|
13
|
+
) -> dict[str | int, Any]:
|
|
14
|
+
if source != CURRENT_VERSION.value or target != CURRENT_VERSION.value:
|
|
15
|
+
raise ValueError(f"unsupported CWT migration: {source} -> {target}")
|
|
16
|
+
return dict(value)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = ["migrate_claims"]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Mapping
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True, slots=True)
|
|
6
|
+
class CwtProfile:
|
|
7
|
+
name: str
|
|
8
|
+
required_claim_labels: frozenset[str | int]
|
|
9
|
+
allowed_cose_message_types: frozenset[str]
|
|
10
|
+
|
|
11
|
+
def validate(self, claims: Mapping[str | int, object], cose_message_type: str) -> None:
|
|
12
|
+
if cose_message_type not in self.allowed_cose_message_types:
|
|
13
|
+
raise ValueError(
|
|
14
|
+
f"COSE message type not allowed by {self.name}: {cose_message_type}"
|
|
15
|
+
)
|
|
16
|
+
missing = self.required_claim_labels.difference(claims)
|
|
17
|
+
if missing:
|
|
18
|
+
raise ValueError(f"missing required CWT claim labels: {sorted(missing, key=str)}")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from tba_concrete_cwt import CwtClaimsSet, decode_cwt_claims
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CwtProtocol:
|
|
5
|
+
def __init__(self, cose_protocol, profile):
|
|
6
|
+
self.cose_protocol, self.profile = cose_protocol, profile
|
|
7
|
+
|
|
8
|
+
def issue(
|
|
9
|
+
self, claims: CwtClaimsSet, *, key_reference: str, headers: dict[object, object]
|
|
10
|
+
):
|
|
11
|
+
return self.cose_protocol.sign1(
|
|
12
|
+
claims.encode(), key_reference=key_reference, headers=headers
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
def verify(self, envelope):
|
|
16
|
+
result = self.cose_protocol.verify1(envelope)
|
|
17
|
+
if result.payload is None:
|
|
18
|
+
raise ValueError("verified CWT has no payload")
|
|
19
|
+
claims = decode_cwt_claims(result.payload)
|
|
20
|
+
self.profile.validate(claims.claims, "Sign1")
|
|
21
|
+
return claims, result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""RFC 8392 carrier schema descriptors."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from tba_contracts_identity.tokens import TokenEnvelopeFormat
|
|
6
|
+
|
|
7
|
+
from .errors import UnsupportedCwtMediaTypeError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class CwtCarrier:
|
|
12
|
+
media_type: str
|
|
13
|
+
envelope_format: TokenEnvelopeFormat
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
CWT_CARRIER = CwtCarrier("application/cwt", TokenEnvelopeFormat.CWT)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def select_carrier(media_type: str) -> CwtCarrier:
|
|
20
|
+
if media_type != CWT_CARRIER.media_type:
|
|
21
|
+
raise UnsupportedCwtMediaTypeError(f"unsupported CWT media type: {media_type}")
|
|
22
|
+
return CWT_CARRIER
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = ["CWT_CARRIER", "CwtCarrier", "select_carrier"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""RFC 8392 version ownership."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CwtVersion(StrEnum):
|
|
7
|
+
RFC8392 = "RFC8392"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
CURRENT_VERSION = CwtVersion.RFC8392
|
|
11
|
+
VERSION_HISTORY = (CwtVersion.RFC8392,)
|
|
12
|
+
VERSION_STATUS = {CwtVersion.RFC8392.value: "standards-track"}
|
|
13
|
+
VERSION_PUBLISHED = {CwtVersion.RFC8392.value: "2018-05"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def select_version(identifier: str = CURRENT_VERSION.value) -> CwtVersion:
|
|
17
|
+
try:
|
|
18
|
+
return CwtVersion(identifier)
|
|
19
|
+
except ValueError as exc:
|
|
20
|
+
raise ValueError(f"unsupported CWT version: {identifier}") from exc
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"CURRENT_VERSION",
|
|
25
|
+
"VERSION_HISTORY",
|
|
26
|
+
"VERSION_PUBLISHED",
|
|
27
|
+
"VERSION_STATUS",
|
|
28
|
+
"CwtVersion",
|
|
29
|
+
"select_version",
|
|
30
|
+
]
|