From d0d0213a9cb046bd4ff22f9037a3ca95cfa7813c Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 22 Sep 2025 13:53:00 -0700 Subject: [PATCH 01/12] expose cipher suite TLSv1_2_2025_07 --- awscrt/io.py | 4 ++++ test/test_mqtt5.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/awscrt/io.py b/awscrt/io.py index 494b53711..128a13fb2 100644 --- a/awscrt/io.py +++ b/awscrt/io.py @@ -277,6 +277,10 @@ class TlsCipherPref(IntEnum): PQ_DEFAULT = 8 # : """Recommended default policy with post-quantum algorithm support. This policy may change over time.""" + TLSv1_2_2025_07 = 9 + """A TLS Cipher Preference ordering that supports TLS 1.2 through TLS 1.3, and does not include CBC cipher suites. + It is FIPS-complaint.""" + def is_supported(self): """Return whether this Cipher Preference is available in the underlying platform's TLS implementation""" return _awscrt.is_tls_cipher_supported(self.value) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index d52da2abc..c6696f826 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -252,6 +252,26 @@ def test_direct_connect_tls(self): client.stop() callbacks.future_stopped.result(TIMEOUT) + def test_direct_connect_tls_with_TLSv1_2_2025_07(self): + input_host_name = _get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_HOST") + input_port = int(_get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_PORT")) + + client_options = mqtt5.ClientOptions( + host_name=input_host_name, + port=input_port + ) + tls_ctx_options = io.TlsContextOptions() + tls_ctx_options.verify_peer = False + tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 + client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) + + callbacks = Mqtt5TestCallbacks() + client = self._create_client(client_options=client_options, callbacks=callbacks) + client.start() + callbacks.future_connection_success.result(TIMEOUT) + client.stop() + callbacks.future_stopped.result(TIMEOUT) + def test_direct_connect_mutual_tls(self): input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") From 3523a85c241f9a36aed1238910e10f52437d6c52 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 22 Sep 2025 14:10:15 -0700 Subject: [PATCH 02/12] update cipher suit test --- awscrt/io.py | 2 +- test/test_mqtt5.py | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/awscrt/io.py b/awscrt/io.py index 128a13fb2..d52e1768c 100644 --- a/awscrt/io.py +++ b/awscrt/io.py @@ -278,7 +278,7 @@ class TlsCipherPref(IntEnum): """Recommended default policy with post-quantum algorithm support. This policy may change over time.""" TLSv1_2_2025_07 = 9 - """A TLS Cipher Preference ordering that supports TLS 1.2 through TLS 1.3, and does not include CBC cipher suites. + """A TLS Cipher Preference ordering that supports TLS 1.2 through TLS 1.3, and does not include CBC cipher suites. It is FIPS-complaint.""" def is_supported(self): diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index c6696f826..ffaa08ccd 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -252,17 +252,19 @@ def test_direct_connect_tls(self): client.stop() callbacks.future_stopped.result(TIMEOUT) - def test_direct_connect_tls_with_TLSv1_2_2025_07(self): - input_host_name = _get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_HOST") - input_port = int(_get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_PORT")) + def test_direct_connect_mutual_tls(self): + input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") + input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") + input_key = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_KEY") client_options = mqtt5.ClientOptions( host_name=input_host_name, - port=input_port + port=8883 + ) + tls_ctx_options = io.TlsContextOptions.create_client_with_mtls_from_path( + input_cert, + input_key ) - tls_ctx_options = io.TlsContextOptions() - tls_ctx_options.verify_peer = False - tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) callbacks = Mqtt5TestCallbacks() @@ -272,7 +274,7 @@ def test_direct_connect_tls_with_TLSv1_2_2025_07(self): client.stop() callbacks.future_stopped.result(TIMEOUT) - def test_direct_connect_mutual_tls(self): + def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") input_key = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_KEY") @@ -285,6 +287,7 @@ def test_direct_connect_mutual_tls(self): input_cert, input_key ) + tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) callbacks = Mqtt5TestCallbacks() From dabad265c6055778886ae110162747e3f45152f8 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Fri, 26 Sep 2025 14:36:24 -0700 Subject: [PATCH 03/12] validate the s2n policy on linux only --- test/test_mqtt5.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index ffaa08ccd..cfbfeabdb 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -5,7 +5,7 @@ from awscrt import mqtt5, io, http, exceptions from test import NativeResourceTest from threading import Lock -import os +import os, sys import unittest import uuid import time @@ -274,6 +274,7 @@ def test_direct_connect_mutual_tls(self): client.stop() callbacks.future_stopped.result(TIMEOUT) + @unittest.skipIf(sys.platform=="Linux", "s2n policy only available on linux") def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") From 85e86635ca8c011b6c14711e7647addb4f3e5ffa Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Fri, 26 Sep 2025 15:02:15 -0700 Subject: [PATCH 04/12] add validation for setting cipher suite --- test/test_mqtt5.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index cfbfeabdb..3b587d298 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -5,7 +5,8 @@ from awscrt import mqtt5, io, http, exceptions from test import NativeResourceTest from threading import Lock -import os, sys +import os +import sys import unittest import uuid import time @@ -274,7 +275,6 @@ def test_direct_connect_mutual_tls(self): client.stop() callbacks.future_stopped.result(TIMEOUT) - @unittest.skipIf(sys.platform=="Linux", "s2n policy only available on linux") def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") @@ -288,8 +288,19 @@ def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_cert, input_key ) - tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 - client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) + exception_occurred = None + try: + tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 + client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) + except Exception as e: + if sys.platform.startswith("Linux"): + exception_occurred = e + # The cipher suite is only supported on Linux with s2n so far. If we are + # not on Linux, we should get AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED error + elif 'AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED' not in str(e): + exception_occurred = e + + self.assertIsNone(exception_occurred, "Exception on set tls cipher preference.") callbacks = Mqtt5TestCallbacks() client = self._create_client(client_options=client_options, callbacks=callbacks) From 552d71accd1c9acf6040803211d635134804ea28 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 29 Sep 2025 11:54:46 -0700 Subject: [PATCH 05/12] update test --- test/test_mqtt5.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index 3b587d298..01aaf6476 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -288,19 +288,18 @@ def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_cert, input_key ) - exception_occurred = None + try: tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) except Exception as e: - if sys.platform.startswith("Linux"): - exception_occurred = e - # The cipher suite is only supported on Linux with s2n so far. If we are - # not on Linux, we should get AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED error - elif 'AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED' not in str(e): - exception_occurred = e - - self.assertIsNone(exception_occurred, "Exception on set tls cipher preference.") + if sys.platform.startswith("linux"): + # On Linux, this should not fail + self.fail(f"Unexpected error on Linux: {e}") + else: + # On non-Linux platforms, verify we get the expected error and skip + self.assertIn('AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED', str(e)) + self.skipTest(f"TLSv1_2_2025_07 not supported on {sys.platform}") callbacks = Mqtt5TestCallbacks() client = self._create_client(client_options=client_options, callbacks=callbacks) From e5bbe2d95977fc18f0b3569d160612cf1c8c1248 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 29 Sep 2025 11:59:37 -0700 Subject: [PATCH 06/12] update lint --- test/test_mqtt5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index 01aaf6476..6739bb7ed 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -288,7 +288,7 @@ def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): input_cert, input_key ) - + try: tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) From 4b072e1a635c934b4f281de5a411a84e1afdd1e4 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 29 Sep 2025 14:05:08 -0700 Subject: [PATCH 07/12] test with fixed aws-c-io --- crt/aws-c-io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crt/aws-c-io b/crt/aws-c-io index db7a1bddc..a4befc0d9 160000 --- a/crt/aws-c-io +++ b/crt/aws-c-io @@ -1 +1 @@ -Subproject commit db7a1bddc9a29eca18734d0af189c3924775dcf1 +Subproject commit a4befc0d9ef5c9828c7e8b3796bee1c84c15a036 From 91b7925cb73b38e888d32fc45ac633a154839df2 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 29 Sep 2025 16:22:46 -0700 Subject: [PATCH 08/12] update aws-c-io to v0.22.1 --- crt/aws-c-io | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crt/aws-c-io b/crt/aws-c-io index a4befc0d9..9b8d716b3 160000 --- a/crt/aws-c-io +++ b/crt/aws-c-io @@ -1 +1 @@ -Subproject commit a4befc0d9ef5c9828c7e8b3796bee1c84c15a036 +Subproject commit 9b8d716b3b12de435fbabed834ef435fd3f153c2 From 9865fb61689b6c50c885251550d5b49404bcc128 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Tue, 30 Sep 2025 09:18:50 -0700 Subject: [PATCH 09/12] update unit test to test TlsCipherPref.TLSv1_2_2025_07 --- test/test_io.py | 15 +++++++++++++++ test/test_mqtt5.py | 36 ------------------------------------ 2 files changed, 15 insertions(+), 36 deletions(-) diff --git a/test/test_io.py b/test/test_io.py index aa2274711..6e414d4ba 100644 --- a/test/test_io.py +++ b/test/test_io.py @@ -113,6 +113,21 @@ def test_override_default_trust_store_file(self): opt.override_default_trust_store_from_path(None, 'test/resources/ca.crt') ctx = ClientTlsContext(opt) + def test_set_cipher_preference_tlsv1_2_2025(self): + opt = TlsContextOptions() + opt.cipher_pref = TlsCipherPref.TLSv1_2_2025_07 + + try: + ctx = ClientTlsContext(opt) + except Exception as e: + if sys.platform.startswith("linux"): + # On Linux, this should not fail + self.fail(f"Unexpected error on Linux: {e}") + else: + # On non-Linux platforms, verify we get the expected error and skip + self.assertIn('AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED', str(e)) + self.skipTest(f"TLSv1_2_2025_07 not supported on {sys.platform}") + class TlsConnectionOptionsTest(NativeResourceTest): def test_init(self): diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index 84cee3580..a2562d783 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -289,42 +289,6 @@ def _test_direct_connect_mutual_tls(self): def test_direct_connect_mutual_tls(self): test_retry_wrapper(self._test_direct_connect_mutual_tls) - def test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): - test_retry_wrapper(self._test_direct_connect_mutual_tls_with_tlsv1_2_2025) - - def _test_direct_connect_mutual_tls_with_tlsv1_2_2025(self): - input_host_name = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_HOST") - input_cert = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_CERT") - input_key = _get_env_variable("AWS_TEST_MQTT5_IOT_CORE_RSA_KEY") - - client_options = mqtt5.ClientOptions( - host_name=input_host_name, - port=8883 - ) - tls_ctx_options = io.TlsContextOptions.create_client_with_mtls_from_path( - input_cert, - input_key - ) - - try: - tls_ctx_options.cipher_pref = io.TlsCipherPref.TLSv1_2_2025_07 - client_options.tls_ctx = io.ClientTlsContext(tls_ctx_options) - except Exception as e: - if sys.platform.startswith("linux"): - # On Linux, this should not fail - self.fail(f"Unexpected error on Linux: {e}") - else: - # On non-Linux platforms, verify we get the expected error and skip - self.assertIn('AWS_IO_TLS_CIPHER_PREF_UNSUPPORTED', str(e)) - self.skipTest(f"TLSv1_2_2025_07 not supported on {sys.platform}") - - callbacks = Mqtt5TestCallbacks() - client = self._create_client(client_options=client_options, callbacks=callbacks) - client.start() - callbacks.future_connection_success.result(TIMEOUT) - client.stop() - callbacks.future_stopped.result(TIMEOUT) - def _test_direct_connect_http_proxy_tls(self): input_host_name = _get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_HOST") input_port = int(_get_env_variable("AWS_TEST_MQTT5_DIRECT_MQTT_TLS_PORT")) From e70b491219a88a8086d3e1d9a89245fc4518804d Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Tue, 30 Sep 2025 09:20:23 -0700 Subject: [PATCH 10/12] remove unused import --- test/test_mqtt5.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_mqtt5.py b/test/test_mqtt5.py index a2562d783..83ee5928a 100644 --- a/test/test_mqtt5.py +++ b/test/test_mqtt5.py @@ -5,7 +5,6 @@ from awscrt import mqtt5, io, http, exceptions from test import test_retry_wrapper, NativeResourceTest import os -import sys import unittest import uuid import time From dcef5d936e8a6a65395109b0d9d17f87fc2369c4 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 20 Oct 2025 11:37:54 -0700 Subject: [PATCH 11/12] improve comments --- awscrt/io.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/awscrt/io.py b/awscrt/io.py index d52e1768c..faaa46844 100644 --- a/awscrt/io.py +++ b/awscrt/io.py @@ -278,8 +278,9 @@ class TlsCipherPref(IntEnum): """Recommended default policy with post-quantum algorithm support. This policy may change over time.""" TLSv1_2_2025_07 = 9 - """A TLS Cipher Preference ordering that supports TLS 1.2 through TLS 1.3, and does not include CBC cipher suites. - It is FIPS-complaint.""" + """A TLS Cipher Preference requiring TLS 1.2+ with FIPS compliance and perfect forward secrecy. This security policy + is based on the AWS-CRT-SDK-TLSv1.2-2023 s2n TLS policy with enhanced security restrictions. It supports AES-GCM and + ECDHE cipher suites with ECDSA and RSA-PSS signature schemes, and uses NIST P-256 and P-384 curves only.""" def is_supported(self): """Return whether this Cipher Preference is available in the underlying platform's TLS implementation""" From 817908c25e9d5aeabe2ac37f72fc463433679e78 Mon Sep 17 00:00:00 2001 From: Vera Xia Date: Mon, 20 Oct 2025 16:12:50 -0700 Subject: [PATCH 12/12] kick ci