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.
@@ -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,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
+ ]