From 5ed597eb28c408c5968e6dfb839880ba5fa17ba1 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Fri, 6 Dec 2024 09:53:18 +0900 Subject: [PATCH] groups: represent hybrid groups with an array of IDs Previously, the supported_groups array contained externally defined elements, which is legitimate in C99 but caused error with Clang: groups.c:93:2: error: initializer element is not a compile-time constant group_x25519, ^~~~~~~~~~~~ This reworks the array definition of indirection through group IDs (gnutls_group_t, i.e., integer). This also makes pqc-hybrid-kx test more exhaustive. Signed-off-by: Daiki Ueno Upstream: https://gitlab.com/gnutls/gnutls/-/commit/9cc9d5556d258d23a399abfe45715773e719d134 Signed-off-by: Brandon Maier --- lib/algorithms.h | 7 ++ lib/algorithms/groups.c | 161 ++++++++++++++++++++------------ lib/ext/key_share.c | 81 ++++++++++++---- lib/ext/supported_groups.c | 45 +++++---- lib/gnutls_int.h | 8 +- lib/includes/gnutls/gnutls.h.in | 4 +- lib/priority.c | 25 ++--- lib/session.c | 6 +- tests/pqc-hybrid-kx.sh | 101 +++++++++++++++++--- 9 files changed, 315 insertions(+), 123 deletions(-) diff --git a/lib/algorithms.h b/lib/algorithms.h index 2e1b694c6..c4af571ce 100644 --- a/lib/algorithms.h +++ b/lib/algorithms.h @@ -55,6 +55,9 @@ #define IS_KEM(x) \ (((x) == GNUTLS_PK_MLKEM768) || ((x) == GNUTLS_PK_EXP_KYBER768)) + +#define IS_GROUP_HYBRID(group) ((group)->ids[0] != GNUTLS_GROUP_INVALID) + #define SIG_SEM_PRE_TLS12 (1 << 1) #define SIG_SEM_TLS13 (1 << 2) #define SIG_SEM_DEFAULT (SIG_SEM_PRE_TLS12 | SIG_SEM_TLS13) @@ -493,6 +496,10 @@ const gnutls_group_entry_st *_gnutls_tls_id_to_group(unsigned num); const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id); gnutls_group_t _gnutls_group_get_id(const char *name); +int _gnutls_group_expand( + const gnutls_group_entry_st *group, + const gnutls_group_entry_st *subgroups[MAX_HYBRID_GROUPS + 1]); + gnutls_ecc_curve_t _gnutls_ecc_bits_to_curve(gnutls_pk_algorithm_t pk, int bits); #define MAX_ECC_CURVE_SIZE 66 diff --git a/lib/algorithms/groups.c b/lib/algorithms/groups.c index 88d0cf630..2fbe7b8ec 100644 --- a/lib/algorithms/groups.c +++ b/lib/algorithms/groups.c @@ -30,30 +30,6 @@ /* Supported ECC curves */ -#ifdef HAVE_LIBOQS -static const gnutls_group_entry_st group_mlkem768 = { - .name = "MLKEM768", - .id = GNUTLS_GROUP_INVALID, - .curve = GNUTLS_ECC_CURVE_INVALID, - .pk = GNUTLS_PK_MLKEM768, -}; - -static const gnutls_group_entry_st group_kyber768 = { - .name = "KYBER768", - .id = GNUTLS_GROUP_INVALID, - .curve = GNUTLS_ECC_CURVE_INVALID, - .pk = GNUTLS_PK_EXP_KYBER768, -}; -#endif - -static const gnutls_group_entry_st group_x25519 = { - .name = "X25519", - .id = GNUTLS_GROUP_X25519, - .curve = GNUTLS_ECC_CURVE_X25519, - .tls_id = 29, - .pk = GNUTLS_PK_ECDH_X25519, -}; - static const gnutls_group_entry_st supported_groups[] = { { .name = "SECP192R1", @@ -90,7 +66,13 @@ static const gnutls_group_entry_st supported_groups[] = { .tls_id = 25, .pk = GNUTLS_PK_ECDSA, }, - group_x25519, + { + .name = "X25519", + .id = GNUTLS_GROUP_X25519, + .curve = GNUTLS_ECC_CURVE_X25519, + .tls_id = 29, + .pk = GNUTLS_PK_ECDH_X25519, + }, #ifdef ENABLE_GOST /* draft-smyshlyaev-tls12-gost-suites-06, Section 6 */ { @@ -191,24 +173,33 @@ static const gnutls_group_entry_st supported_groups[] = { .tls_id = 0x104 }, #endif #ifdef HAVE_LIBOQS + { + .name = "MLKEM768", + .id = GNUTLS_GROUP_EXP_MLKEM768, + .pk = GNUTLS_PK_MLKEM768, + /* absense of .tls_id means that this group alone cannot be used in TLS */ + }, + { + .name = "KYBER768", + .id = GNUTLS_GROUP_EXP_KYBER768, + .pk = GNUTLS_PK_EXP_KYBER768, + /* absense of .tls_id means that this group alone cannot be used in TLS */ + }, { .name = "SECP256R1-MLKEM768", .id = GNUTLS_GROUP_EXP_SECP256R1_MLKEM768, - .curve = GNUTLS_ECC_CURVE_SECP256R1, - .pk = GNUTLS_PK_ECDSA, - .tls_id = 0x11EB, - .next = &group_mlkem768 }, + .ids = { GNUTLS_GROUP_SECP256R1, GNUTLS_GROUP_EXP_MLKEM768, + GNUTLS_GROUP_INVALID }, + .tls_id = 0x11EB }, { .name = "X25519-MLKEM768", .id = GNUTLS_GROUP_EXP_X25519_MLKEM768, - .curve = GNUTLS_ECC_CURVE_INVALID, - .pk = GNUTLS_PK_MLKEM768, - .tls_id = 0x11EC, - .next = &group_x25519 }, + .ids = { GNUTLS_GROUP_EXP_MLKEM768, GNUTLS_GROUP_X25519, + GNUTLS_GROUP_INVALID }, + .tls_id = 0x11EC }, { .name = "X25519-KYBER768", .id = GNUTLS_GROUP_EXP_X25519_KYBER768, - .curve = GNUTLS_ECC_CURVE_X25519, - .pk = GNUTLS_PK_ECDH_X25519, - .tls_id = 0x6399, - .next = &group_kyber768 }, + .ids = { GNUTLS_GROUP_X25519, GNUTLS_GROUP_EXP_KYBER768, + GNUTLS_GROUP_INVALID }, + .tls_id = 0x6399 }, #endif { 0, 0, 0 } }; @@ -221,14 +212,46 @@ static const gnutls_group_entry_st supported_groups[] = { } \ } +static inline const gnutls_group_entry_st *group_to_entry(gnutls_group_t group) +{ + if (group == 0) + return NULL; + + GNUTLS_GROUP_LOOP(if (p->id == group) { return p; }); + + return NULL; +} + +static inline bool +group_is_supported_standalone(const gnutls_group_entry_st *group) +{ + return group->pk != 0 && _gnutls_pk_exists(group->pk) && + (group->curve == 0 || + _gnutls_ecc_curve_is_supported(group->curve)); +} + +static inline bool group_is_supported(const gnutls_group_entry_st *group) +{ + if (!IS_GROUP_HYBRID(group)) + return group_is_supported_standalone(group); + + for (size_t i = 0; + i < MAX_HYBRID_GROUPS && group->ids[i] != GNUTLS_GROUP_INVALID; + i++) { + const gnutls_group_entry_st *p = group_to_entry(group->ids[i]); + if (!p || !group_is_supported_standalone(p)) + return false; + } + + return true; +} + /* Returns the TLS id of the given curve */ const gnutls_group_entry_st *_gnutls_tls_id_to_group(unsigned num) { GNUTLS_GROUP_LOOP( - if (p->tls_id == num && - (p->curve == 0 || - _gnutls_ecc_curve_is_supported(p->curve))) { return p; }); + if (p->tls_id == num && group_is_supported(p)) { return p; }); return NULL; } @@ -239,10 +262,7 @@ const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id) return NULL; GNUTLS_GROUP_LOOP( - if (p->id == id && (p->curve == 0 || - _gnutls_ecc_curve_is_supported(p->curve))) { - return p; - }); + if (p->id == id && group_is_supported(p)) { return p; }); return NULL; } @@ -261,27 +281,17 @@ const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id) **/ const gnutls_group_t *gnutls_group_list(void) { - static gnutls_group_t groups[MAX_ALGOS] = { 0 }; + static gnutls_group_t groups[MAX_ALGOS + 1] = { 0 }; if (groups[0] == 0) { - int i = 0; + size_t i = 0; - const gnutls_group_entry_st *p; - - for (p = supported_groups; p->name != NULL; p++) { - const gnutls_group_entry_st *pp; - - for (pp = p; pp != NULL; pp = pp->next) { - if ((pp->curve != 0 && - !_gnutls_ecc_curve_is_supported( - pp->curve)) || - (pp->pk != 0 && !_gnutls_pk_exists(pp->pk))) - break; - } - if (pp == NULL) + for (const gnutls_group_entry_st *p = supported_groups; + p->name != NULL; p++) { + if (group_is_supported(p)) groups[i++] = p->id; } - groups[i++] = 0; + groups[i++] = GNUTLS_GROUP_INVALID; } return groups; @@ -344,3 +354,34 @@ const char *gnutls_group_get_name(gnutls_group_t group) return NULL; } + +/* Expand GROUP into hybrid SUBGROUPS if any, otherwise an array + * containing the GROUP itself. The result will be written to + * SUBGROUPS, which will be NUL-terminated. + */ +int _gnutls_group_expand( + const gnutls_group_entry_st *group, + const gnutls_group_entry_st *subgroups[MAX_HYBRID_GROUPS + 1]) +{ + size_t pos = 0; + + if (IS_GROUP_HYBRID(group)) { + for (size_t i = 0; i < MAX_HYBRID_GROUPS && + group->ids[i] != GNUTLS_GROUP_INVALID; + i++) { + const gnutls_group_entry_st *p = + group_to_entry(group->ids[i]); + /* This shouldn't happen, as GROUP is assumed + * to be supported before calling this + * function. */ + if (unlikely(!p)) + return gnutls_assert_val( + GNUTLS_E_INTERNAL_ERROR); + subgroups[pos++] = p; + } + } else { + subgroups[pos++] = group; + } + subgroups[pos] = NULL; + return 0; +} diff --git a/lib/ext/key_share.c b/lib/ext/key_share.c index 574521157..8fbe2d2bd 100644 --- a/lib/ext/key_share.c +++ b/lib/ext/key_share.c @@ -232,6 +232,9 @@ static int client_gen_key_share(gnutls_session_t session, gnutls_buffer_st *extdata) { unsigned int length_pos; + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; int ret; _gnutls_handshake_log("EXT[%p]: sending key share for %s\n", session, @@ -247,8 +250,12 @@ static int client_gen_key_share(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) { - ret = client_gen_key_share_single(session, p, extdata); + ret = _gnutls_group_expand(group, groups); + if (ret < 0) + return gnutls_assert_val(ret); + + for (size_t i = 0; groups[i]; i++) { + ret = client_gen_key_share_single(session, groups[i], extdata); if (ret < 0) return gnutls_assert_val(ret); } @@ -345,6 +352,9 @@ static int server_gen_key_share(gnutls_session_t session, gnutls_buffer_st *extdata) { unsigned int length_pos; + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; int ret; _gnutls_handshake_log("EXT[%p]: sending key share for %s\n", session, @@ -360,8 +370,12 @@ static int server_gen_key_share(gnutls_session_t session, if (ret < 0) return gnutls_assert_val(ret); - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) { - ret = server_gen_key_share_single(session, p, extdata); + ret = _gnutls_group_expand(group, groups); + if (ret < 0) + return gnutls_assert_val(ret); + + for (size_t i = 0; groups[i]; i++) { + ret = server_gen_key_share_single(session, groups[i], extdata); if (ret < 0) return gnutls_assert_val(ret); } @@ -594,13 +608,19 @@ static int server_use_key_share(gnutls_session_t session, const uint8_t *data, size_t data_size) { gnutls_buffer_st buffer; + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; + int ret; _gnutls_ro_buffer_init(&buffer, data, data_size); - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) { - int ret; + ret = _gnutls_group_expand(group, groups); + if (ret < 0) + return gnutls_assert_val(ret); - ret = server_use_key_share_single(session, p, &buffer); + for (size_t i = 0; groups[i]; i++) { + ret = server_use_key_share_single(session, groups[i], &buffer); if (ret < 0) return gnutls_assert_val(ret); } @@ -775,13 +795,19 @@ static int client_use_key_share(gnutls_session_t session, const uint8_t *data, size_t data_size) { gnutls_buffer_st buffer; + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; + int ret; _gnutls_ro_buffer_init(&buffer, data, data_size); - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) { - int ret; + ret = _gnutls_group_expand(group, groups); + if (ret < 0) + return gnutls_assert_val(ret); - ret = client_use_key_share_single(session, p, &buffer); + for (size_t i = 0; groups[i]; i++) { + ret = client_use_key_share_single(session, groups[i], &buffer); if (ret < 0) return gnutls_assert_val(ret); } @@ -958,18 +984,39 @@ static int key_share_recv_params(gnutls_session_t session, const uint8_t *data, return 0; } +static inline bool pk_types_overlap_single(const gnutls_group_entry_st *a, + const gnutls_group_entry_st *b) +{ + return a->pk == b->pk || (IS_ECDHX(a->pk) && IS_ECDHX(b->pk)) || + (IS_KEM(a->pk) && IS_KEM(b->pk)); +} + static inline bool pk_types_overlap(const gnutls_group_entry_st *a, const gnutls_group_entry_st *b) { - const gnutls_group_entry_st *pa; + const gnutls_group_entry_st *sa[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; + const gnutls_group_entry_st *sb[MAX_HYBRID_GROUPS + 1] = { + NULL, + }; + int ret; + + ret = _gnutls_group_expand(a, sa); + if (ret < 0) { + gnutls_assert(); + return false; + } - for (pa = a; pa != NULL; pa = pa->next) { - const gnutls_group_entry_st *pb; + ret = _gnutls_group_expand(b, sb); + if (ret < 0) { + gnutls_assert(); + return false; + } - for (pb = b; pb != NULL; pb = pb->next) { - if (pa->pk == pb->pk || - (IS_ECDHX(pa->pk) && IS_ECDHX(pb->pk)) || - (IS_KEM(pa->pk) && IS_KEM(pb->pk))) + for (size_t i = 0; sa[i]; i++) { + for (size_t j = 0; sb[j]; j++) { + if (pk_types_overlap_single(sa[i], sb[j])) return true; } } diff --git a/lib/ext/supported_groups.c b/lib/ext/supported_groups.c index 254ec4882..4c31d2f8f 100644 --- a/lib/ext/supported_groups.c +++ b/lib/ext/supported_groups.c @@ -106,9 +106,9 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, unsigned min_dh; unsigned j; int serv_ec_idx, serv_dh_idx, - serv_kem_idx; /* index in server's priority listing */ + serv_hybrid_idx; /* index in server's priority listing */ int cli_ec_pos, cli_dh_pos, - cli_kem_pos; /* position in listing sent by client */ + cli_hybrid_pos; /* position in listing sent by client */ if (session->security_parameters.entity == GNUTLS_CLIENT) { /* A client shouldn't receive this extension in TLS1.2. It is @@ -134,8 +134,8 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, /* we figure what is the minimum DH allowed for this session, if any */ min_dh = get_min_dh(session); - serv_ec_idx = serv_dh_idx = serv_kem_idx = -1; - cli_ec_pos = cli_dh_pos = cli_kem_pos = -1; + serv_ec_idx = serv_dh_idx = serv_hybrid_idx = -1; + cli_ec_pos = cli_dh_pos = cli_hybrid_pos = -1; /* This extension is being processed prior to a ciphersuite being selected, * so we cannot rely on ciphersuite information. */ @@ -180,14 +180,15 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, break; serv_ec_idx = j; cli_ec_pos = i; - } else if (IS_KEM(group->pk)) { - if (serv_kem_idx != + } else if (IS_GROUP_HYBRID( + group)) { + if (serv_hybrid_idx != -1 && (int)j > - serv_kem_idx) + serv_hybrid_idx) break; - serv_kem_idx = j; - cli_kem_pos = i; + serv_hybrid_idx = j; + cli_hybrid_pos = i; } } else { if (group->pk == GNUTLS_PK_DH) { @@ -200,11 +201,13 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, break; cli_ec_pos = i; serv_ec_idx = j; - } else if (IS_KEM(group->pk)) { - if (cli_kem_pos != -1) + } else if (IS_GROUP_HYBRID( + group)) { + if (cli_hybrid_pos != + -1) break; - cli_kem_pos = i; - serv_kem_idx = j; + cli_hybrid_pos = i; + serv_hybrid_idx = j; } } break; @@ -212,7 +215,7 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, } } - /* serv_{dh,ec,kem}_idx contain the index of the groups we want to use. + /* serv_{dh,ec,hybrid}_idx contain the index of the groups we want to use. */ if (serv_dh_idx != -1) { session->internals.cand_dh_group = @@ -236,18 +239,20 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session, } } - /* KEM can only be used in TLS 1.3, where no separation from - * ECDH and DH, and thus only cand_group is set here. + /* PQC hybrid key exchange groups can only be used in + * TLS 1.3, where no distinction between ECDH and DH + * in the group definitions, and thus only cand_group + * is set here. */ - if (serv_kem_idx != -1) { + if (serv_hybrid_idx != -1) { if (session->internals.cand_group == NULL || (session->internals.priorities->server_precedence && - serv_kem_idx < MIN(serv_ec_idx, serv_dh_idx)) || + serv_hybrid_idx < MIN(serv_ec_idx, serv_dh_idx)) || (!session->internals.priorities->server_precedence && - cli_kem_pos < MIN(cli_ec_pos, cli_dh_pos))) { + cli_hybrid_pos < MIN(cli_ec_pos, cli_dh_pos))) { session->internals.cand_group = session->internals.priorities->groups - .entry[serv_kem_idx]; + .entry[serv_hybrid_idx]; } } diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index fb2cacb54..01ef59729 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -756,6 +756,8 @@ typedef struct gnutls_cipher_suite_entry_st { gnutls_mac_algorithm_t prf; } gnutls_cipher_suite_entry_st; +#define MAX_HYBRID_GROUPS 2 + typedef struct gnutls_group_entry_st { const char *name; gnutls_group_t id; @@ -765,8 +767,12 @@ typedef struct gnutls_group_entry_st { const unsigned *q_bits; gnutls_ecc_curve_t curve; gnutls_pk_algorithm_t pk; + gnutls_group_t ids[MAX_HYBRID_GROUPS + 1]; /* IDs of subgroups + * comprising a + * hybrid group, + * terminated with + * GNUTLS_GROUP_INVALID */ unsigned tls_id; /* The RFC4492 namedCurve ID or TLS 1.3 group ID */ - const struct gnutls_group_entry_st *next; } gnutls_group_entry_st; #define GNUTLS_MAC_FLAG_PREIMAGE_INSECURE \ diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 8b3bb5213..1e44fdd91 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -1147,8 +1147,10 @@ typedef enum { GNUTLS_GROUP_EXP_X25519_KYBER768 = 512, GNUTLS_GROUP_EXP_SECP256R1_MLKEM768 = 513, GNUTLS_GROUP_EXP_X25519_MLKEM768 = 514, + GNUTLS_GROUP_EXP_KYBER768 = 515, + GNUTLS_GROUP_EXP_MLKEM768 = 516, GNUTLS_GROUP_EXP_MIN = GNUTLS_GROUP_EXP_X25519_KYBER768, - GNUTLS_GROUP_EXP_MAX = GNUTLS_GROUP_EXP_X25519_MLKEM768 + GNUTLS_GROUP_EXP_MAX = GNUTLS_GROUP_EXP_MLKEM768 } gnutls_group_t; /* macros to allow specifying a specific curve in gnutls_privkey_generate() diff --git a/lib/priority.c b/lib/priority.c index ac4ff2d8c..479dbccd6 100644 --- a/lib/priority.c +++ b/lib/priority.c @@ -2566,7 +2566,7 @@ static void add_dh(gnutls_priority_t priority_cache) } } -static void add_kem(gnutls_priority_t priority_cache) +static void add_hybrid(gnutls_priority_t priority_cache) { const gnutls_group_entry_st *ge; unsigned i; @@ -2579,7 +2579,7 @@ static void add_kem(gnutls_priority_t priority_cache) sizeof(priority_cache->groups.entry) / sizeof(priority_cache->groups.entry[0])) { /* do not add groups which do not correspond to enabled ciphersuites */ - if (!IS_KEM(ge->pk)) + if (!IS_GROUP_HYBRID(ge)) continue; priority_cache->groups .entry[priority_cache->groups.size++] = ge; @@ -2598,7 +2598,7 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) const gnutls_sign_entry_st *se; unsigned have_ec = 0; unsigned have_dh = 0; - unsigned have_kem = 0; + unsigned have_hybrid = 0; unsigned tls_sig_sem = 0; const version_entry_st *tlsmax = NULL, *vers; const version_entry_st *dtlsmax = NULL; @@ -2807,9 +2807,9 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) priority_cache->cs.entry[priority_cache->cs.size++] = ce; - if (!have_kem) { - have_kem = 1; - add_kem(priority_cache); + if (!have_hybrid) { + have_hybrid = 1; + add_hybrid(priority_cache); } } } @@ -2851,8 +2851,8 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) } } - if (have_tls13 && (!have_ec || !have_dh || !have_kem)) { - /* scan groups to determine have_{ec,dh,kem} */ + if (have_tls13 && (!have_ec || !have_dh || !have_hybrid)) { + /* scan groups to determine have_{ec,dh,hybrid} */ for (i = 0; i < priority_cache->_supported_ecc.num_priorities; i++) { const gnutls_group_entry_st *ge; @@ -2865,12 +2865,13 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) } else if (ge->prime && !have_dh) { add_dh(priority_cache); have_dh = 1; - } else if (IS_KEM(ge->pk) && !have_kem) { - add_kem(priority_cache); - have_kem = 1; + } else if (IS_GROUP_HYBRID(ge) && + !have_hybrid) { + add_hybrid(priority_cache); + have_hybrid = 1; } - if (have_dh && have_ec && have_kem) + if (have_dh && have_ec && have_hybrid) break; } } diff --git a/lib/session.c b/lib/session.c index a9049a464..7fcbe4fb4 100644 --- a/lib/session.c +++ b/lib/session.c @@ -415,7 +415,11 @@ char *gnutls_session_get_desc(gnutls_session_t session) snprintf(kx_name, sizeof(kx_name), "(PSK)"); } } else if (group && sign_str) { - if (group->curve) + if (IS_GROUP_HYBRID(group)) + snprintf(kx_name, sizeof(kx_name), + "(HYBRID-%s)-(%s)", group_name, + sign_str); + else if (group->curve) snprintf(kx_name, sizeof(kx_name), "(ECDHE-%s)-(%s)", group_name, sign_str); diff --git a/tests/pqc-hybrid-kx.sh b/tests/pqc-hybrid-kx.sh index da936cf04..4984cd4b4 100644 --- a/tests/pqc-hybrid-kx.sh +++ b/tests/pqc-hybrid-kx.sh @@ -33,34 +33,113 @@ . "${srcdir}/scripts/common.sh" +# First check any mismatch in the gnutls-cli --list if ! "${CLI}" --list | grep '^Groups: .*GROUP-X25519-KYBER768.*' >/dev/null; then if "${CLI}" --list | grep '^Public Key Systems: .*KYBER768.*' >/dev/null; then - fail "KYBER768 is in Public Key Systems, while GROUP-X25519-KYBER768 is NOT in Groups" + fail '' 'KYBER768 is in Public Key Systems, while GROUP-X25519-KYBER768 is NOT in Groups' fi - exit 77 else if ! "${CLI}" --list | grep '^Public Key Systems: .*KYBER768.*' >/dev/null; then - fail "KYBER768 is NOT in Public Key Systems, while GROUP-X25519-KYBER768 is in Groups" + fail '' 'KYBER768 is NOT in Public Key Systems, while GROUP-X25519-KYBER768 is in Groups' + fi +fi + +if ! "${CLI}" --list | grep '^Groups: .*GROUP-\(SECP256R1\|X25519\)-MLKEM768.*' >/dev/null; then + if "${CLI}" --list | grep '^Public Key Systems: .*ML-KEM-768.*' >/dev/null; then + fail '' 'ML-KEM-768 is in Public Key Systems, while GROUP-SECP256R1-MLKEM768 or GROUP-X25519-MLKEM768 is NOT in Groups' + fi +else + if ! "${CLI}" --list | grep '^Public Key Systems: .*ML-KEM-768.*' >/dev/null; then + fail '' 'ML-KEM-768 is NOT in Public Key Systems, while GROUP-SECP256R1-MLKEM768 or GROUP-X25519-MLKEM768 is in Groups' fi fi +# If none of those hybrid groups is supported, skip the test +if ! "${CLI}" --list | grep '^Groups: .*GROUP-\(X25519-KYBER768\|SECP256R1-MLKEM768\|X25519-MLKEM768\).*' >/dev/null; then + exit 77 +fi + testdir=`create_testdir pqc-hybrid-kx` KEY="$srcdir/../doc/credentials/x509/key-ecc.pem" CERT="$srcdir/../doc/credentials/x509/cert-ecc.pem" CACERT="$srcdir/../doc/credentials/x509/ca.pem" -eval "${GETPORT}" -launch_server --echo --priority NORMAL:-GROUP-ALL:+GROUP-X25519-KYBER768 --x509keyfile="$KEY" --x509certfile="$CERT" -PID=$! -wait_server ${PID} +# Test all supported hybrid groups +for group in X25519-KYBER768 SECP256R1-MLKEM768 X25519-MLKEM768; do + if ! "${CLI}" --list | grep "^Groups: .*GROUP-$group.*" >/dev/null; then + echo "$group is not supported, skipping" >&2 + continue + fi + + eval "${GETPORT}" + launch_server --echo --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509keyfile="$KEY" --x509certfile="$CERT" + PID=$! + wait_server ${PID} + + ${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509cafile="$CACERT" --logfile="$testdir/cli.log" /dev/null; then + "$group is not supported, skipping" + continue + fi + + eval "${GETPORT}" + launch_server --echo --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509keyfile="$KEY" --x509certfile="$CERT" + PID=$! + wait_server ${PID} + + ${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509cafile="$CACERT" --logfile="$testdir/cli.log" "$testdir/test.config" +[overrides] + +disabled-curve = x25519 +_EOF_ + +for group in X25519-KYBER768 SECP256R1-MLKEM768 X25519-MLKEM768; do + if ! "${CLI}" --list | grep "^Groups: .*GROUP-$group.*" >/dev/null; then + echo "$group is not supported, skipping" >&2 + continue + fi -${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority NORMAL:-GROUP-ALL:+GROUP-X25519-KYBER768 --x509cafile="$CACERT" --logfile="$testdir/cli.log"