Skip to content

Commit 77f8cdd

Browse files
authored
feat: add PQ only policy support (#5545)
1 parent a1ed358 commit 77f8cdd

File tree

6 files changed

+174
-22
lines changed

6 files changed

+174
-22
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: test_pq_only
2+
min version: TLS1.3
3+
rules:
4+
- Perfect Forward Secrecy: no
5+
- FIPS 140-3 (2019): no
6+
cipher suites:
7+
- TLS_AES_128_GCM_SHA256
8+
- TLS_AES_256_GCM_SHA384
9+
- TLS_CHACHA20_POLY1305_SHA256
10+
signature schemes:
11+
- ecdsa_sha256
12+
- ecdsa_sha384
13+
- ecdsa_sha512
14+
- rsa_pss_pss_sha256
15+
- rsa_pss_pss_sha384
16+
- rsa_pss_pss_sha512
17+
- rsa_pss_rsae_sha256
18+
- rsa_pss_rsae_sha384
19+
- rsa_pss_rsae_sha512
20+
- rsa_pkcs1_sha256
21+
- rsa_pkcs1_sha384
22+
- rsa_pkcs1_sha512
23+
curves:
24+
certificate signature schemes:
25+
- ecdsa_sha256
26+
- ecdsa_sha384
27+
- ecdsa_sha512
28+
- rsa_pss_pss_sha256
29+
- rsa_pss_pss_sha384
30+
- rsa_pss_pss_sha512
31+
- rsa_pss_rsae_sha256
32+
- rsa_pss_rsae_sha384
33+
- rsa_pss_rsae_sha512
34+
- rsa_pkcs1_sha256
35+
- rsa_pkcs1_sha384
36+
- rsa_pkcs1_sha512
37+
pq:
38+
- revision: 5
39+
- kem groups:
40+
-- X25519MLKEM768
41+
-- SecP256r1MLKEM768
42+
-- SecP384r1MLKEM1024

tests/unit/s2n_pq_mlkem_policies_test.c

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,86 @@ int main(int argc, char **argv)
191191
}
192192
}
193193

194+
/* Test configuring a PQ only policy on different libcryptos. */
195+
{
196+
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
197+
EXPECT_NOT_NULL(config);
198+
const char *policy = "test_pq_only";
199+
200+
if (!s2n_is_tls13_fully_supported()) {
201+
EXPECT_FAILURE_WITH_ERRNO(s2n_config_set_cipher_preferences(config, policy), S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
202+
} else if (!s2n_libcrypto_supports_mlkem()) {
203+
EXPECT_FAILURE_WITH_ERRNO(s2n_config_set_cipher_preferences(config, policy), S2N_ERR_INVALID_SECURITY_POLICY);
204+
} else {
205+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, policy));
206+
}
207+
}
208+
209+
/* Self-talk tests for PQ only policies on supported libcryptos. */
210+
if (s2n_is_tls13_fully_supported() && s2n_libcrypto_supports_mlkem()) {
211+
DEFER_CLEANUP(struct s2n_cert_chain_and_key *chain_and_key = NULL, s2n_cert_chain_and_key_ptr_free);
212+
EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key,
213+
S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY));
214+
215+
/* Test a PQ-only policy is able to negotiate. */
216+
{
217+
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
218+
EXPECT_NOT_NULL(config);
219+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));
220+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
221+
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "test_pq_only"));
222+
223+
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
224+
EXPECT_NOT_NULL(client_conn);
225+
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
226+
EXPECT_SUCCESS(s2n_set_server_name(client_conn, "localhost"));
227+
228+
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
229+
EXPECT_NOT_NULL(server_conn);
230+
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));
231+
232+
DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
233+
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
234+
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
235+
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));
236+
237+
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
238+
/* Assert classical ECC is not negotiated & kem group is negotiated. */
239+
EXPECT_NULL(server_conn->kex_params.server_ecc_evp_params.negotiated_curve);
240+
EXPECT_NOT_NULL(server_conn->kex_params.server_kem_group_params.kem_group);
241+
}
242+
243+
/* Expect failure when a non-PQ client attempts to negotiate with a PQ-only server. */
244+
{
245+
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
246+
EXPECT_NOT_NULL(config);
247+
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));
248+
EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_DEFAULT_TEST_CERT_CHAIN, NULL));
249+
250+
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free);
251+
EXPECT_NOT_NULL(client_conn);
252+
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
253+
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(client_conn, "20240503"));
254+
EXPECT_SUCCESS(s2n_set_server_name(client_conn, "localhost"));
255+
256+
DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free);
257+
EXPECT_NOT_NULL(server_conn);
258+
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));
259+
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(server_conn, "test_pq_only"));
260+
EXPECT_SUCCESS(s2n_connection_set_blinding(server_conn, S2N_SELF_SERVICE_BLINDING));
261+
262+
DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
263+
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
264+
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
265+
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));
266+
267+
EXPECT_FAILURE_WITH_ERRNO(s2n_negotiate_test_server_and_client(server_conn, client_conn),
268+
S2N_ERR_ECDHE_UNSUPPORTED_CURVE);
269+
/* Assert server negotiated_curve and kem_group are both NULL. */
270+
EXPECT_NULL(server_conn->kex_params.server_ecc_evp_params.negotiated_curve);
271+
EXPECT_NULL(server_conn->kex_params.server_kem_group_params.kem_group);
272+
}
273+
}
274+
194275
END_TEST();
195276
}

tls/extensions/s2n_client_key_share.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ static int s2n_generate_default_ecc_key_share(struct s2n_connection *conn, struc
6767
POSIX_GUARD(s2n_connection_get_ecc_preferences(conn, &ecc_pref));
6868
POSIX_ENSURE_REF(ecc_pref);
6969

70+
/* Skip sending classical ECC curves for PQ only policies. */
71+
if (ecc_pref->count == 0) {
72+
return S2N_SUCCESS;
73+
}
74+
7075
/* We only ever send a single EC key share: either the share requested by the server
7176
* during a retry, or the most preferred share according to local preferences.
7277
*/

tls/s2n_client_hello.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "api/unstable/fingerprint.h"
2424
#include "crypto/s2n_fips.h"
2525
#include "crypto/s2n_hash.h"
26+
#include "crypto/s2n_pq.h"
2627
#include "error/s2n_errno.h"
2728
#include "stuffer/s2n_stuffer.h"
2829
#include "tls/extensions/s2n_client_server_name.h"
@@ -551,13 +552,13 @@ int s2n_process_client_hello(struct s2n_connection *conn)
551552
const struct s2n_ecc_preferences *ecc_pref = NULL;
552553
POSIX_GUARD(s2n_connection_get_ecc_preferences(conn, &ecc_pref));
553554
POSIX_ENSURE_REF(ecc_pref);
554-
POSIX_ENSURE_GT(ecc_pref->count, 0);
555-
if (s2n_ecc_preferences_includes_curve(ecc_pref, TLS_EC_CURVE_SECP_256_R1)) {
555+
556+
if (ecc_pref->count == 0) {
557+
conn->kex_params.server_ecc_evp_params.negotiated_curve = NULL;
558+
} else if (s2n_ecc_preferences_includes_curve(ecc_pref, TLS_EC_CURVE_SECP_256_R1)) {
556559
conn->kex_params.server_ecc_evp_params.negotiated_curve = &s2n_ecc_curve_secp256r1;
557560
} else {
558-
/* If P-256 isn't allowed by the current security policy, instead choose
559-
* the first / most preferred curve.
560-
*/
561+
/* If P-256 isn't allowed by the current security policy, choose the first / most preferred curve. */
561562
conn->kex_params.server_ecc_evp_params.negotiated_curve = ecc_pref->ecc_curves[0];
562563
}
563564

tls/s2n_security_policies.c

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "tls/s2n_security_policies.h"
1717

1818
#include "api/s2n.h"
19+
#include "crypto/s2n_pq.h"
1920
#include "tls/s2n_certificate_keys.h"
2021
#include "tls/s2n_connection.h"
2122
#include "utils/s2n_safety.h"
@@ -1398,6 +1399,15 @@ const struct s2n_security_policy security_policy_test_all_tls13 = {
13981399
},
13991400
};
14001401

1402+
const struct s2n_security_policy security_policy_test_pq_only = {
1403+
.minimum_protocol_version = S2N_TLS13,
1404+
.cipher_preferences = &cipher_preferences_cloudfront_upstream_2025_08_08_tls13,
1405+
.kem_preferences = &kem_preferences_pq_tls_1_3_ietf_2025_07,
1406+
.signature_preferences = &s2n_signature_preferences_20240501,
1407+
.certificate_signature_preferences = &s2n_signature_preferences_20240501,
1408+
.ecc_preferences = &s2n_ecc_preferences_null,
1409+
};
1410+
14011411
const struct s2n_security_policy security_policy_20200207 = {
14021412
.minimum_protocol_version = S2N_SSLv3,
14031413
.cipher_preferences = &cipher_preferences_test_all_tls13,
@@ -1570,6 +1580,7 @@ struct s2n_security_policy_selection security_policy_selection[] = {
15701580
{ .version = "test_ecdsa_priority", .security_policy = &security_policy_test_ecdsa_priority, .ecc_extension_required = 0, .pq_kem_extension_required = 0 },
15711581
{ .version = "test_all_tls12", .security_policy = &security_policy_test_all_tls12, .ecc_extension_required = 0, .pq_kem_extension_required = 0 },
15721582
{ .version = "test_all_tls13", .security_policy = &security_policy_test_all_tls13, .ecc_extension_required = 0, .pq_kem_extension_required = 0 },
1583+
{ .version = "test_pq_only", .security_policy = &security_policy_test_pq_only, .ecc_extension_required = 0, .pq_kem_extension_required = 0 },
15731584
{ .version = "null", .security_policy = &security_policy_null, .ecc_extension_required = 0, .pq_kem_extension_required = 0 },
15741585
{ .version = NULL, .security_policy = NULL, .ecc_extension_required = 0, .pq_kem_extension_required = 0 }
15751586
};
@@ -1615,7 +1626,7 @@ int s2n_find_security_policy_from_version(const char *version, const struct s2n_
16151626
POSIX_BAIL(S2N_ERR_INVALID_SECURITY_POLICY);
16161627
}
16171628

1618-
int s2n_config_set_security_policy(struct s2n_config *config, const struct s2n_security_policy *security_policy)
1629+
static int s2n_config_validate_security_policy(struct s2n_config *config, const struct s2n_security_policy *security_policy)
16191630
{
16201631
POSIX_ENSURE_REF(config);
16211632
POSIX_ENSURE_REF(security_policy);
@@ -1627,8 +1638,25 @@ int s2n_config_set_security_policy(struct s2n_config *config, const struct s2n_s
16271638
/* If the security policy's minimum version is higher than what libcrypto supports, return an error. */
16281639
POSIX_ENSURE((security_policy->minimum_protocol_version <= s2n_get_highest_fully_supported_tls_version()), S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
16291640

1641+
if (security_policy == &security_policy_null) {
1642+
return S2N_SUCCESS;
1643+
}
1644+
1645+
/* Ensure that an ECC or PQ key exchange can occur. */
1646+
uint32_t ecc_available = security_policy->ecc_preferences->count;
1647+
uint32_t kem_groups_available = 0;
1648+
POSIX_GUARD_RESULT(s2n_kem_preferences_groups_available(security_policy->kem_preferences, &kem_groups_available));
1649+
POSIX_ENSURE(ecc_available + kem_groups_available > 0, S2N_ERR_INVALID_SECURITY_POLICY);
1650+
16301651
/* If the config contains certificates violating the security policy cert preferences, return an error. */
16311652
POSIX_GUARD_RESULT(s2n_config_validate_loaded_certificates(config, security_policy));
1653+
return S2N_SUCCESS;
1654+
}
1655+
1656+
int s2n_config_set_security_policy(struct s2n_config *config, const struct s2n_security_policy *security_policy)
1657+
{
1658+
POSIX_ENSURE_REF(config);
1659+
POSIX_GUARD(s2n_config_validate_security_policy(config, security_policy));
16321660
config->security_policy = security_policy;
16331661
return 0;
16341662
}
@@ -1644,19 +1672,7 @@ int s2n_config_set_cipher_preferences(struct s2n_config *config, const char *ver
16441672
int s2n_connection_set_security_policy(struct s2n_connection *conn, const struct s2n_security_policy *security_policy)
16451673
{
16461674
POSIX_ENSURE_REF(conn);
1647-
POSIX_ENSURE_REF(security_policy);
1648-
POSIX_ENSURE_REF(security_policy->cipher_preferences);
1649-
POSIX_ENSURE_REF(security_policy->kem_preferences);
1650-
POSIX_ENSURE_REF(security_policy->signature_preferences);
1651-
POSIX_ENSURE_REF(security_policy->ecc_preferences);
1652-
1653-
/* If the security policy's minimum version is higher than what libcrypto supports, return an error. */
1654-
POSIX_ENSURE((security_policy->minimum_protocol_version <= s2n_get_highest_fully_supported_tls_version()), S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
1655-
1656-
/* If the certificates loaded in the config are incompatible with the security
1657-
* policy's certificate preferences, return an error. */
1658-
POSIX_GUARD_RESULT(s2n_config_validate_loaded_certificates(conn->config, security_policy));
1659-
1675+
POSIX_GUARD(s2n_config_validate_security_policy(conn->config, security_policy));
16601676
conn->security_policy_override = security_policy;
16611677
return 0;
16621678
}
@@ -1688,8 +1704,15 @@ int s2n_security_policies_init()
16881704
}
16891705

16901706
if (security_policy != &security_policy_null) {
1691-
/* All policies must have at least one ecc curve configured. */
1692-
S2N_ERROR_IF(ecc_preference->count == 0, S2N_ERR_INVALID_SECURITY_POLICY);
1707+
/* All policies must have at least one ecc curve or PQ kem group configured. */
1708+
bool ecc_kx_supported = ecc_preference->count > 0;
1709+
bool pq_kx_supported = kem_preference->tls13_kem_group_count > 0;
1710+
POSIX_ENSURE(ecc_kx_supported || pq_kx_supported, S2N_ERR_INVALID_SECURITY_POLICY);
1711+
1712+
/* A PQ key exchange is only supported in TLS 1.3, so PQ-only policies must require TLS 1.3.*/
1713+
if (!ecc_kx_supported) {
1714+
POSIX_ENSURE(security_policy->minimum_protocol_version >= S2N_TLS13, S2N_ERR_INVALID_SECURITY_POLICY);
1715+
}
16931716
}
16941717

16951718
for (int j = 0; j < cipher_preference->count; j++) {

tls/s2n_signature_scheme.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ extern const struct s2n_signature_preferences s2n_signature_preferences_20240521
9292
extern const struct s2n_signature_preferences s2n_signature_preferences_20250429;
9393
extern const struct s2n_signature_preferences s2n_signature_preferences_20250820;
9494
extern const struct s2n_signature_preferences s2n_signature_preferences_20250821;
95-
extern const struct s2n_signature_preferences s2n_certificate_signature_preferences_20250429;
9695
extern const struct s2n_signature_preferences s2n_signature_preferences_default_fips;
9796
extern const struct s2n_signature_preferences s2n_signature_preferences_null;
9897
extern const struct s2n_signature_preferences s2n_signature_preferences_test_all_fips;
9998
extern const struct s2n_signature_preferences s2n_signature_preferences_all;
10099

101100
extern const struct s2n_signature_preferences s2n_certificate_signature_preferences_20250512;
101+
extern const struct s2n_signature_preferences s2n_certificate_signature_preferences_20250429;
102102
extern const struct s2n_signature_preferences s2n_certificate_signature_preferences_20201110;

0 commit comments

Comments
 (0)