diff --git a/pkgs/standards/swarmauri_cipher_suite_tls13/swarmauri_cipher_suite_tls13/Tls13CipherSuite.py b/pkgs/standards/swarmauri_cipher_suite_tls13/swarmauri_cipher_suite_tls13/Tls13CipherSuite.py index bb57d717c1..297ced8e22 100644 --- a/pkgs/standards/swarmauri_cipher_suite_tls13/swarmauri_cipher_suite_tls13/Tls13CipherSuite.py +++ b/pkgs/standards/swarmauri_cipher_suite_tls13/swarmauri_cipher_suite_tls13/Tls13CipherSuite.py @@ -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): @@ -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( @@ -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(), diff --git a/pkgs/standards/swarmauri_cipher_suite_tls13/tests/unit/test_Tls13CipherSuite_unit.py b/pkgs/standards/swarmauri_cipher_suite_tls13/tests/unit/test_Tls13CipherSuite_unit.py index 78643312a4..906a11a297 100644 --- a/pkgs/standards/swarmauri_cipher_suite_tls13/tests/unit/test_Tls13CipherSuite_unit.py +++ b/pkgs/standards/swarmauri_cipher_suite_tls13/tests/unit/test_Tls13CipherSuite_unit.py @@ -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 @@ -52,7 +53,7 @@ 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 @@ -60,14 +61,20 @@ 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 @@ -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"] == {} @@ -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")