Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,20 @@
ParamMapping,
)

_TLS13 = (
_TLS13_CLASSIC = (
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
)

_TLS13_PQ_HYBRID = ("TLS_AES_256_GCM_SHA384_KYBER768",)

_TLS13_ALL = _TLS13_CLASSIC + _TLS13_PQ_HYBRID

_PQ_PROVIDER_HINTS: Mapping[str, str] = {
"TLS_AES_256_GCM_SHA384_KYBER768": "ML-KEM-768",
}


@ComponentBase.register_type(CipherSuiteBase, "Tls13CipherSuite")
class Tls13CipherSuite(CipherSuiteBase):
Expand All @@ -29,26 +37,33 @@ def suite_id(self) -> str:

def supports(self) -> Mapping[CipherOp, Iterable[Alg]]:
return {
"encrypt": _TLS13,
"decrypt": _TLS13,
"encrypt": _TLS13_ALL,
"decrypt": _TLS13_ALL,
}

def default_alg(self, op: CipherOp, *, for_key: Optional[KeyRef] = None) -> Alg:
return "TLS_AES_256_GCM_SHA384"
return "TLS_AES_256_GCM_SHA384_KYBER768"

def features(self) -> Features:
return {
"suite": "tls13",
"version": 1,
"dialects": {"tls": list(_TLS13)},
"dialects": {"tls": list(_TLS13_ALL)},
"ops": {
"encrypt": {
"default": self.default_alg("encrypt"),
"allowed": list(_TLS13),
"allowed": list(_TLS13_ALL),
}
},
"constraints": {"record_max": 16384, "aead": {"tagBits": 128}},
"compliance": {"fips": False},
"compliance": {
"fips": False,
"pq_ready": True,
"min_key_sizes": {
"symmetric": 256,
"kem": "ML-KEM-768",
},
},
}

def normalize(
Expand All @@ -66,11 +81,15 @@ def normalize(
raise ValueError(f"{chosen=} not supported for {op=} in TLS1.3")

resolved = dict(params or {})
provider_hint = _PQ_PROVIDER_HINTS.get(chosen, chosen)

mapped = {"tls": chosen, "provider": provider_hint}

return {
"op": op,
"alg": chosen,
"dialect": "tls" if dialect is None else dialect,
"mapped": {"tls": chosen, "provider": chosen},
"mapped": mapped,
"params": resolved,
"constraints": {"record_max": 16384, "tagBits": 128},
"policy": self.policy(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ def test_supports_expected_algorithms(cipher_suite: Tls13CipherSuite) -> None:
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384_KYBER768",
}
for operation in ("encrypt", "decrypt"):
assert set(supports[operation]) == expected
Expand All @@ -52,22 +53,28 @@ def test_supports_expected_algorithms(cipher_suite: Tls13CipherSuite) -> None:
@pytest.mark.unit
@pytest.mark.parametrize("operation", ["encrypt", "decrypt"])
def test_default_alg(cipher_suite: Tls13CipherSuite, operation: str) -> None:
assert cipher_suite.default_alg(operation) == "TLS_AES_256_GCM_SHA384"
assert cipher_suite.default_alg(operation) == "TLS_AES_256_GCM_SHA384_KYBER768"


@pytest.mark.unit
def test_features_descriptor(cipher_suite: Tls13CipherSuite) -> None:
features = cipher_suite.features()
assert features["suite"] == "tls13"
assert features["version"] == 1
assert features["ops"]["encrypt"]["default"] == "TLS_AES_256_GCM_SHA384"
assert features["ops"]["encrypt"]["default"] == "TLS_AES_256_GCM_SHA384_KYBER768"
assert features["ops"]["encrypt"]["allowed"] == [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
"TLS_AES_256_GCM_SHA384_KYBER768",
]
assert features["constraints"]["record_max"] == 16384
assert features["constraints"]["aead"] == {"tagBits": 128}
assert features["compliance"]["pq_ready"] is True
assert features["compliance"]["min_key_sizes"] == {
"symmetric": 256,
"kem": "ML-KEM-768",
}


@pytest.mark.unit
Expand All @@ -94,11 +101,11 @@ def test_normalize_with_explicit_alg(cipher_suite: Tls13CipherSuite) -> None:
def test_normalize_defaults(cipher_suite: Tls13CipherSuite) -> None:
descriptor = cipher_suite.normalize(op="decrypt")

assert descriptor["alg"] == "TLS_AES_256_GCM_SHA384"
assert descriptor["alg"] == "TLS_AES_256_GCM_SHA384_KYBER768"
assert descriptor["dialect"] == "tls"
assert descriptor["mapped"] == {
"tls": "TLS_AES_256_GCM_SHA384",
"provider": "TLS_AES_256_GCM_SHA384",
"tls": "TLS_AES_256_GCM_SHA384_KYBER768",
"provider": "ML-KEM-768",
}
assert descriptor["params"] == {}

Expand All @@ -107,3 +114,22 @@ def test_normalize_defaults(cipher_suite: Tls13CipherSuite) -> None:
def test_normalize_rejects_unsupported_alg(cipher_suite: Tls13CipherSuite) -> None:
with pytest.raises(ValueError):
cipher_suite.normalize(op="encrypt", alg="TLS_AES_128_CCM_SHA256")


@pytest.mark.unit
def test_normalize_accepts_pq_hybrid(cipher_suite: Tls13CipherSuite) -> None:
descriptor = cipher_suite.normalize(
op="encrypt",
alg="TLS_AES_256_GCM_SHA384_KYBER768",
)

assert descriptor["alg"] == "TLS_AES_256_GCM_SHA384_KYBER768"
assert descriptor["mapped"]["provider"] == "ML-KEM-768"


@pytest.mark.unit
def test_normalize_rejects_unsupported_pq_fallback(
cipher_suite: Tls13CipherSuite,
) -> None:
with pytest.raises(ValueError):
cipher_suite.normalize(op="encrypt", alg="TLS_AES_256_GCM_SHA384_KYBER512")
Loading