0001-groups-represent-hybrid-groups-with-an-array-of-IDs.patch 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. From 5ed597eb28c408c5968e6dfb839880ba5fa17ba1 Mon Sep 17 00:00:00 2001
  2. From: Daiki Ueno <ueno@gnu.org>
  3. Date: Fri, 6 Dec 2024 09:53:18 +0900
  4. Subject: [PATCH] groups: represent hybrid groups with an array of IDs
  5. Previously, the supported_groups array contained externally defined
  6. elements, which is legitimate in C99 but caused error with Clang:
  7. groups.c:93:2: error: initializer element is not a compile-time constant
  8. group_x25519,
  9. ^~~~~~~~~~~~
  10. This reworks the array definition of indirection through group
  11. IDs (gnutls_group_t, i.e., integer).
  12. This also makes pqc-hybrid-kx test more exhaustive.
  13. Signed-off-by: Daiki Ueno <ueno@gnu.org>
  14. Upstream: https://gitlab.com/gnutls/gnutls/-/commit/9cc9d5556d258d23a399abfe45715773e719d134
  15. Signed-off-by: Brandon Maier <brandon.maier@collins.com>
  16. ---
  17. lib/algorithms.h | 7 ++
  18. lib/algorithms/groups.c | 161 ++++++++++++++++++++------------
  19. lib/ext/key_share.c | 81 ++++++++++++----
  20. lib/ext/supported_groups.c | 45 +++++----
  21. lib/gnutls_int.h | 8 +-
  22. lib/includes/gnutls/gnutls.h.in | 4 +-
  23. lib/priority.c | 25 ++---
  24. lib/session.c | 6 +-
  25. tests/pqc-hybrid-kx.sh | 101 +++++++++++++++++---
  26. 9 files changed, 315 insertions(+), 123 deletions(-)
  27. diff --git a/lib/algorithms.h b/lib/algorithms.h
  28. index 2e1b694c6..c4af571ce 100644
  29. --- a/lib/algorithms.h
  30. +++ b/lib/algorithms.h
  31. @@ -55,6 +55,9 @@
  32. #define IS_KEM(x) \
  33. (((x) == GNUTLS_PK_MLKEM768) || ((x) == GNUTLS_PK_EXP_KYBER768))
  34. +
  35. +#define IS_GROUP_HYBRID(group) ((group)->ids[0] != GNUTLS_GROUP_INVALID)
  36. +
  37. #define SIG_SEM_PRE_TLS12 (1 << 1)
  38. #define SIG_SEM_TLS13 (1 << 2)
  39. #define SIG_SEM_DEFAULT (SIG_SEM_PRE_TLS12 | SIG_SEM_TLS13)
  40. @@ -493,6 +496,10 @@ const gnutls_group_entry_st *_gnutls_tls_id_to_group(unsigned num);
  41. const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id);
  42. gnutls_group_t _gnutls_group_get_id(const char *name);
  43. +int _gnutls_group_expand(
  44. + const gnutls_group_entry_st *group,
  45. + const gnutls_group_entry_st *subgroups[MAX_HYBRID_GROUPS + 1]);
  46. +
  47. gnutls_ecc_curve_t _gnutls_ecc_bits_to_curve(gnutls_pk_algorithm_t pk,
  48. int bits);
  49. #define MAX_ECC_CURVE_SIZE 66
  50. diff --git a/lib/algorithms/groups.c b/lib/algorithms/groups.c
  51. index 88d0cf630..2fbe7b8ec 100644
  52. --- a/lib/algorithms/groups.c
  53. +++ b/lib/algorithms/groups.c
  54. @@ -30,30 +30,6 @@
  55. /* Supported ECC curves
  56. */
  57. -#ifdef HAVE_LIBOQS
  58. -static const gnutls_group_entry_st group_mlkem768 = {
  59. - .name = "MLKEM768",
  60. - .id = GNUTLS_GROUP_INVALID,
  61. - .curve = GNUTLS_ECC_CURVE_INVALID,
  62. - .pk = GNUTLS_PK_MLKEM768,
  63. -};
  64. -
  65. -static const gnutls_group_entry_st group_kyber768 = {
  66. - .name = "KYBER768",
  67. - .id = GNUTLS_GROUP_INVALID,
  68. - .curve = GNUTLS_ECC_CURVE_INVALID,
  69. - .pk = GNUTLS_PK_EXP_KYBER768,
  70. -};
  71. -#endif
  72. -
  73. -static const gnutls_group_entry_st group_x25519 = {
  74. - .name = "X25519",
  75. - .id = GNUTLS_GROUP_X25519,
  76. - .curve = GNUTLS_ECC_CURVE_X25519,
  77. - .tls_id = 29,
  78. - .pk = GNUTLS_PK_ECDH_X25519,
  79. -};
  80. -
  81. static const gnutls_group_entry_st supported_groups[] = {
  82. {
  83. .name = "SECP192R1",
  84. @@ -90,7 +66,13 @@ static const gnutls_group_entry_st supported_groups[] = {
  85. .tls_id = 25,
  86. .pk = GNUTLS_PK_ECDSA,
  87. },
  88. - group_x25519,
  89. + {
  90. + .name = "X25519",
  91. + .id = GNUTLS_GROUP_X25519,
  92. + .curve = GNUTLS_ECC_CURVE_X25519,
  93. + .tls_id = 29,
  94. + .pk = GNUTLS_PK_ECDH_X25519,
  95. + },
  96. #ifdef ENABLE_GOST
  97. /* draft-smyshlyaev-tls12-gost-suites-06, Section 6 */
  98. {
  99. @@ -191,24 +173,33 @@ static const gnutls_group_entry_st supported_groups[] = {
  100. .tls_id = 0x104 },
  101. #endif
  102. #ifdef HAVE_LIBOQS
  103. + {
  104. + .name = "MLKEM768",
  105. + .id = GNUTLS_GROUP_EXP_MLKEM768,
  106. + .pk = GNUTLS_PK_MLKEM768,
  107. + /* absense of .tls_id means that this group alone cannot be used in TLS */
  108. + },
  109. + {
  110. + .name = "KYBER768",
  111. + .id = GNUTLS_GROUP_EXP_KYBER768,
  112. + .pk = GNUTLS_PK_EXP_KYBER768,
  113. + /* absense of .tls_id means that this group alone cannot be used in TLS */
  114. + },
  115. { .name = "SECP256R1-MLKEM768",
  116. .id = GNUTLS_GROUP_EXP_SECP256R1_MLKEM768,
  117. - .curve = GNUTLS_ECC_CURVE_SECP256R1,
  118. - .pk = GNUTLS_PK_ECDSA,
  119. - .tls_id = 0x11EB,
  120. - .next = &group_mlkem768 },
  121. + .ids = { GNUTLS_GROUP_SECP256R1, GNUTLS_GROUP_EXP_MLKEM768,
  122. + GNUTLS_GROUP_INVALID },
  123. + .tls_id = 0x11EB },
  124. { .name = "X25519-MLKEM768",
  125. .id = GNUTLS_GROUP_EXP_X25519_MLKEM768,
  126. - .curve = GNUTLS_ECC_CURVE_INVALID,
  127. - .pk = GNUTLS_PK_MLKEM768,
  128. - .tls_id = 0x11EC,
  129. - .next = &group_x25519 },
  130. + .ids = { GNUTLS_GROUP_EXP_MLKEM768, GNUTLS_GROUP_X25519,
  131. + GNUTLS_GROUP_INVALID },
  132. + .tls_id = 0x11EC },
  133. { .name = "X25519-KYBER768",
  134. .id = GNUTLS_GROUP_EXP_X25519_KYBER768,
  135. - .curve = GNUTLS_ECC_CURVE_X25519,
  136. - .pk = GNUTLS_PK_ECDH_X25519,
  137. - .tls_id = 0x6399,
  138. - .next = &group_kyber768 },
  139. + .ids = { GNUTLS_GROUP_X25519, GNUTLS_GROUP_EXP_KYBER768,
  140. + GNUTLS_GROUP_INVALID },
  141. + .tls_id = 0x6399 },
  142. #endif
  143. { 0, 0, 0 }
  144. };
  145. @@ -221,14 +212,46 @@ static const gnutls_group_entry_st supported_groups[] = {
  146. } \
  147. }
  148. +static inline const gnutls_group_entry_st *group_to_entry(gnutls_group_t group)
  149. +{
  150. + if (group == 0)
  151. + return NULL;
  152. +
  153. + GNUTLS_GROUP_LOOP(if (p->id == group) { return p; });
  154. +
  155. + return NULL;
  156. +}
  157. +
  158. +static inline bool
  159. +group_is_supported_standalone(const gnutls_group_entry_st *group)
  160. +{
  161. + return group->pk != 0 && _gnutls_pk_exists(group->pk) &&
  162. + (group->curve == 0 ||
  163. + _gnutls_ecc_curve_is_supported(group->curve));
  164. +}
  165. +
  166. +static inline bool group_is_supported(const gnutls_group_entry_st *group)
  167. +{
  168. + if (!IS_GROUP_HYBRID(group))
  169. + return group_is_supported_standalone(group);
  170. +
  171. + for (size_t i = 0;
  172. + i < MAX_HYBRID_GROUPS && group->ids[i] != GNUTLS_GROUP_INVALID;
  173. + i++) {
  174. + const gnutls_group_entry_st *p = group_to_entry(group->ids[i]);
  175. + if (!p || !group_is_supported_standalone(p))
  176. + return false;
  177. + }
  178. +
  179. + return true;
  180. +}
  181. +
  182. /* Returns the TLS id of the given curve
  183. */
  184. const gnutls_group_entry_st *_gnutls_tls_id_to_group(unsigned num)
  185. {
  186. GNUTLS_GROUP_LOOP(
  187. - if (p->tls_id == num &&
  188. - (p->curve == 0 ||
  189. - _gnutls_ecc_curve_is_supported(p->curve))) { return p; });
  190. + if (p->tls_id == num && group_is_supported(p)) { return p; });
  191. return NULL;
  192. }
  193. @@ -239,10 +262,7 @@ const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id)
  194. return NULL;
  195. GNUTLS_GROUP_LOOP(
  196. - if (p->id == id && (p->curve == 0 ||
  197. - _gnutls_ecc_curve_is_supported(p->curve))) {
  198. - return p;
  199. - });
  200. + if (p->id == id && group_is_supported(p)) { return p; });
  201. return NULL;
  202. }
  203. @@ -261,27 +281,17 @@ const gnutls_group_entry_st *_gnutls_id_to_group(unsigned id)
  204. **/
  205. const gnutls_group_t *gnutls_group_list(void)
  206. {
  207. - static gnutls_group_t groups[MAX_ALGOS] = { 0 };
  208. + static gnutls_group_t groups[MAX_ALGOS + 1] = { 0 };
  209. if (groups[0] == 0) {
  210. - int i = 0;
  211. + size_t i = 0;
  212. - const gnutls_group_entry_st *p;
  213. -
  214. - for (p = supported_groups; p->name != NULL; p++) {
  215. - const gnutls_group_entry_st *pp;
  216. -
  217. - for (pp = p; pp != NULL; pp = pp->next) {
  218. - if ((pp->curve != 0 &&
  219. - !_gnutls_ecc_curve_is_supported(
  220. - pp->curve)) ||
  221. - (pp->pk != 0 && !_gnutls_pk_exists(pp->pk)))
  222. - break;
  223. - }
  224. - if (pp == NULL)
  225. + for (const gnutls_group_entry_st *p = supported_groups;
  226. + p->name != NULL; p++) {
  227. + if (group_is_supported(p))
  228. groups[i++] = p->id;
  229. }
  230. - groups[i++] = 0;
  231. + groups[i++] = GNUTLS_GROUP_INVALID;
  232. }
  233. return groups;
  234. @@ -344,3 +354,34 @@ const char *gnutls_group_get_name(gnutls_group_t group)
  235. return NULL;
  236. }
  237. +
  238. +/* Expand GROUP into hybrid SUBGROUPS if any, otherwise an array
  239. + * containing the GROUP itself. The result will be written to
  240. + * SUBGROUPS, which will be NUL-terminated.
  241. + */
  242. +int _gnutls_group_expand(
  243. + const gnutls_group_entry_st *group,
  244. + const gnutls_group_entry_st *subgroups[MAX_HYBRID_GROUPS + 1])
  245. +{
  246. + size_t pos = 0;
  247. +
  248. + if (IS_GROUP_HYBRID(group)) {
  249. + for (size_t i = 0; i < MAX_HYBRID_GROUPS &&
  250. + group->ids[i] != GNUTLS_GROUP_INVALID;
  251. + i++) {
  252. + const gnutls_group_entry_st *p =
  253. + group_to_entry(group->ids[i]);
  254. + /* This shouldn't happen, as GROUP is assumed
  255. + * to be supported before calling this
  256. + * function. */
  257. + if (unlikely(!p))
  258. + return gnutls_assert_val(
  259. + GNUTLS_E_INTERNAL_ERROR);
  260. + subgroups[pos++] = p;
  261. + }
  262. + } else {
  263. + subgroups[pos++] = group;
  264. + }
  265. + subgroups[pos] = NULL;
  266. + return 0;
  267. +}
  268. diff --git a/lib/ext/key_share.c b/lib/ext/key_share.c
  269. index 574521157..8fbe2d2bd 100644
  270. --- a/lib/ext/key_share.c
  271. +++ b/lib/ext/key_share.c
  272. @@ -232,6 +232,9 @@ static int client_gen_key_share(gnutls_session_t session,
  273. gnutls_buffer_st *extdata)
  274. {
  275. unsigned int length_pos;
  276. + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = {
  277. + NULL,
  278. + };
  279. int ret;
  280. _gnutls_handshake_log("EXT[%p]: sending key share for %s\n", session,
  281. @@ -247,8 +250,12 @@ static int client_gen_key_share(gnutls_session_t session,
  282. if (ret < 0)
  283. return gnutls_assert_val(ret);
  284. - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) {
  285. - ret = client_gen_key_share_single(session, p, extdata);
  286. + ret = _gnutls_group_expand(group, groups);
  287. + if (ret < 0)
  288. + return gnutls_assert_val(ret);
  289. +
  290. + for (size_t i = 0; groups[i]; i++) {
  291. + ret = client_gen_key_share_single(session, groups[i], extdata);
  292. if (ret < 0)
  293. return gnutls_assert_val(ret);
  294. }
  295. @@ -345,6 +352,9 @@ static int server_gen_key_share(gnutls_session_t session,
  296. gnutls_buffer_st *extdata)
  297. {
  298. unsigned int length_pos;
  299. + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = {
  300. + NULL,
  301. + };
  302. int ret;
  303. _gnutls_handshake_log("EXT[%p]: sending key share for %s\n", session,
  304. @@ -360,8 +370,12 @@ static int server_gen_key_share(gnutls_session_t session,
  305. if (ret < 0)
  306. return gnutls_assert_val(ret);
  307. - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) {
  308. - ret = server_gen_key_share_single(session, p, extdata);
  309. + ret = _gnutls_group_expand(group, groups);
  310. + if (ret < 0)
  311. + return gnutls_assert_val(ret);
  312. +
  313. + for (size_t i = 0; groups[i]; i++) {
  314. + ret = server_gen_key_share_single(session, groups[i], extdata);
  315. if (ret < 0)
  316. return gnutls_assert_val(ret);
  317. }
  318. @@ -594,13 +608,19 @@ static int server_use_key_share(gnutls_session_t session,
  319. const uint8_t *data, size_t data_size)
  320. {
  321. gnutls_buffer_st buffer;
  322. + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = {
  323. + NULL,
  324. + };
  325. + int ret;
  326. _gnutls_ro_buffer_init(&buffer, data, data_size);
  327. - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) {
  328. - int ret;
  329. + ret = _gnutls_group_expand(group, groups);
  330. + if (ret < 0)
  331. + return gnutls_assert_val(ret);
  332. - ret = server_use_key_share_single(session, p, &buffer);
  333. + for (size_t i = 0; groups[i]; i++) {
  334. + ret = server_use_key_share_single(session, groups[i], &buffer);
  335. if (ret < 0)
  336. return gnutls_assert_val(ret);
  337. }
  338. @@ -775,13 +795,19 @@ static int client_use_key_share(gnutls_session_t session,
  339. const uint8_t *data, size_t data_size)
  340. {
  341. gnutls_buffer_st buffer;
  342. + const gnutls_group_entry_st *groups[MAX_HYBRID_GROUPS + 1] = {
  343. + NULL,
  344. + };
  345. + int ret;
  346. _gnutls_ro_buffer_init(&buffer, data, data_size);
  347. - for (const gnutls_group_entry_st *p = group; p != NULL; p = p->next) {
  348. - int ret;
  349. + ret = _gnutls_group_expand(group, groups);
  350. + if (ret < 0)
  351. + return gnutls_assert_val(ret);
  352. - ret = client_use_key_share_single(session, p, &buffer);
  353. + for (size_t i = 0; groups[i]; i++) {
  354. + ret = client_use_key_share_single(session, groups[i], &buffer);
  355. if (ret < 0)
  356. return gnutls_assert_val(ret);
  357. }
  358. @@ -958,18 +984,39 @@ static int key_share_recv_params(gnutls_session_t session, const uint8_t *data,
  359. return 0;
  360. }
  361. +static inline bool pk_types_overlap_single(const gnutls_group_entry_st *a,
  362. + const gnutls_group_entry_st *b)
  363. +{
  364. + return a->pk == b->pk || (IS_ECDHX(a->pk) && IS_ECDHX(b->pk)) ||
  365. + (IS_KEM(a->pk) && IS_KEM(b->pk));
  366. +}
  367. +
  368. static inline bool pk_types_overlap(const gnutls_group_entry_st *a,
  369. const gnutls_group_entry_st *b)
  370. {
  371. - const gnutls_group_entry_st *pa;
  372. + const gnutls_group_entry_st *sa[MAX_HYBRID_GROUPS + 1] = {
  373. + NULL,
  374. + };
  375. + const gnutls_group_entry_st *sb[MAX_HYBRID_GROUPS + 1] = {
  376. + NULL,
  377. + };
  378. + int ret;
  379. +
  380. + ret = _gnutls_group_expand(a, sa);
  381. + if (ret < 0) {
  382. + gnutls_assert();
  383. + return false;
  384. + }
  385. - for (pa = a; pa != NULL; pa = pa->next) {
  386. - const gnutls_group_entry_st *pb;
  387. + ret = _gnutls_group_expand(b, sb);
  388. + if (ret < 0) {
  389. + gnutls_assert();
  390. + return false;
  391. + }
  392. - for (pb = b; pb != NULL; pb = pb->next) {
  393. - if (pa->pk == pb->pk ||
  394. - (IS_ECDHX(pa->pk) && IS_ECDHX(pb->pk)) ||
  395. - (IS_KEM(pa->pk) && IS_KEM(pb->pk)))
  396. + for (size_t i = 0; sa[i]; i++) {
  397. + for (size_t j = 0; sb[j]; j++) {
  398. + if (pk_types_overlap_single(sa[i], sb[j]))
  399. return true;
  400. }
  401. }
  402. diff --git a/lib/ext/supported_groups.c b/lib/ext/supported_groups.c
  403. index 254ec4882..4c31d2f8f 100644
  404. --- a/lib/ext/supported_groups.c
  405. +++ b/lib/ext/supported_groups.c
  406. @@ -106,9 +106,9 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  407. unsigned min_dh;
  408. unsigned j;
  409. int serv_ec_idx, serv_dh_idx,
  410. - serv_kem_idx; /* index in server's priority listing */
  411. + serv_hybrid_idx; /* index in server's priority listing */
  412. int cli_ec_pos, cli_dh_pos,
  413. - cli_kem_pos; /* position in listing sent by client */
  414. + cli_hybrid_pos; /* position in listing sent by client */
  415. if (session->security_parameters.entity == GNUTLS_CLIENT) {
  416. /* A client shouldn't receive this extension in TLS1.2. It is
  417. @@ -134,8 +134,8 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  418. /* we figure what is the minimum DH allowed for this session, if any */
  419. min_dh = get_min_dh(session);
  420. - serv_ec_idx = serv_dh_idx = serv_kem_idx = -1;
  421. - cli_ec_pos = cli_dh_pos = cli_kem_pos = -1;
  422. + serv_ec_idx = serv_dh_idx = serv_hybrid_idx = -1;
  423. + cli_ec_pos = cli_dh_pos = cli_hybrid_pos = -1;
  424. /* This extension is being processed prior to a ciphersuite being selected,
  425. * so we cannot rely on ciphersuite information. */
  426. @@ -180,14 +180,15 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  427. break;
  428. serv_ec_idx = j;
  429. cli_ec_pos = i;
  430. - } else if (IS_KEM(group->pk)) {
  431. - if (serv_kem_idx !=
  432. + } else if (IS_GROUP_HYBRID(
  433. + group)) {
  434. + if (serv_hybrid_idx !=
  435. -1 &&
  436. (int)j >
  437. - serv_kem_idx)
  438. + serv_hybrid_idx)
  439. break;
  440. - serv_kem_idx = j;
  441. - cli_kem_pos = i;
  442. + serv_hybrid_idx = j;
  443. + cli_hybrid_pos = i;
  444. }
  445. } else {
  446. if (group->pk == GNUTLS_PK_DH) {
  447. @@ -200,11 +201,13 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  448. break;
  449. cli_ec_pos = i;
  450. serv_ec_idx = j;
  451. - } else if (IS_KEM(group->pk)) {
  452. - if (cli_kem_pos != -1)
  453. + } else if (IS_GROUP_HYBRID(
  454. + group)) {
  455. + if (cli_hybrid_pos !=
  456. + -1)
  457. break;
  458. - cli_kem_pos = i;
  459. - serv_kem_idx = j;
  460. + cli_hybrid_pos = i;
  461. + serv_hybrid_idx = j;
  462. }
  463. }
  464. break;
  465. @@ -212,7 +215,7 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  466. }
  467. }
  468. - /* serv_{dh,ec,kem}_idx contain the index of the groups we want to use.
  469. + /* serv_{dh,ec,hybrid}_idx contain the index of the groups we want to use.
  470. */
  471. if (serv_dh_idx != -1) {
  472. session->internals.cand_dh_group =
  473. @@ -236,18 +239,20 @@ static int _gnutls_supported_groups_recv_params(gnutls_session_t session,
  474. }
  475. }
  476. - /* KEM can only be used in TLS 1.3, where no separation from
  477. - * ECDH and DH, and thus only cand_group is set here.
  478. + /* PQC hybrid key exchange groups can only be used in
  479. + * TLS 1.3, where no distinction between ECDH and DH
  480. + * in the group definitions, and thus only cand_group
  481. + * is set here.
  482. */
  483. - if (serv_kem_idx != -1) {
  484. + if (serv_hybrid_idx != -1) {
  485. if (session->internals.cand_group == NULL ||
  486. (session->internals.priorities->server_precedence &&
  487. - serv_kem_idx < MIN(serv_ec_idx, serv_dh_idx)) ||
  488. + serv_hybrid_idx < MIN(serv_ec_idx, serv_dh_idx)) ||
  489. (!session->internals.priorities->server_precedence &&
  490. - cli_kem_pos < MIN(cli_ec_pos, cli_dh_pos))) {
  491. + cli_hybrid_pos < MIN(cli_ec_pos, cli_dh_pos))) {
  492. session->internals.cand_group =
  493. session->internals.priorities->groups
  494. - .entry[serv_kem_idx];
  495. + .entry[serv_hybrid_idx];
  496. }
  497. }
  498. diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h
  499. index fb2cacb54..01ef59729 100644
  500. --- a/lib/gnutls_int.h
  501. +++ b/lib/gnutls_int.h
  502. @@ -756,6 +756,8 @@ typedef struct gnutls_cipher_suite_entry_st {
  503. gnutls_mac_algorithm_t prf;
  504. } gnutls_cipher_suite_entry_st;
  505. +#define MAX_HYBRID_GROUPS 2
  506. +
  507. typedef struct gnutls_group_entry_st {
  508. const char *name;
  509. gnutls_group_t id;
  510. @@ -765,8 +767,12 @@ typedef struct gnutls_group_entry_st {
  511. const unsigned *q_bits;
  512. gnutls_ecc_curve_t curve;
  513. gnutls_pk_algorithm_t pk;
  514. + gnutls_group_t ids[MAX_HYBRID_GROUPS + 1]; /* IDs of subgroups
  515. + * comprising a
  516. + * hybrid group,
  517. + * terminated with
  518. + * GNUTLS_GROUP_INVALID */
  519. unsigned tls_id; /* The RFC4492 namedCurve ID or TLS 1.3 group ID */
  520. - const struct gnutls_group_entry_st *next;
  521. } gnutls_group_entry_st;
  522. #define GNUTLS_MAC_FLAG_PREIMAGE_INSECURE \
  523. diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in
  524. index 8b3bb5213..1e44fdd91 100644
  525. --- a/lib/includes/gnutls/gnutls.h.in
  526. +++ b/lib/includes/gnutls/gnutls.h.in
  527. @@ -1147,8 +1147,10 @@ typedef enum {
  528. GNUTLS_GROUP_EXP_X25519_KYBER768 = 512,
  529. GNUTLS_GROUP_EXP_SECP256R1_MLKEM768 = 513,
  530. GNUTLS_GROUP_EXP_X25519_MLKEM768 = 514,
  531. + GNUTLS_GROUP_EXP_KYBER768 = 515,
  532. + GNUTLS_GROUP_EXP_MLKEM768 = 516,
  533. GNUTLS_GROUP_EXP_MIN = GNUTLS_GROUP_EXP_X25519_KYBER768,
  534. - GNUTLS_GROUP_EXP_MAX = GNUTLS_GROUP_EXP_X25519_MLKEM768
  535. + GNUTLS_GROUP_EXP_MAX = GNUTLS_GROUP_EXP_MLKEM768
  536. } gnutls_group_t;
  537. /* macros to allow specifying a specific curve in gnutls_privkey_generate()
  538. diff --git a/lib/priority.c b/lib/priority.c
  539. index ac4ff2d8c..479dbccd6 100644
  540. --- a/lib/priority.c
  541. +++ b/lib/priority.c
  542. @@ -2566,7 +2566,7 @@ static void add_dh(gnutls_priority_t priority_cache)
  543. }
  544. }
  545. -static void add_kem(gnutls_priority_t priority_cache)
  546. +static void add_hybrid(gnutls_priority_t priority_cache)
  547. {
  548. const gnutls_group_entry_st *ge;
  549. unsigned i;
  550. @@ -2579,7 +2579,7 @@ static void add_kem(gnutls_priority_t priority_cache)
  551. sizeof(priority_cache->groups.entry) /
  552. sizeof(priority_cache->groups.entry[0])) {
  553. /* do not add groups which do not correspond to enabled ciphersuites */
  554. - if (!IS_KEM(ge->pk))
  555. + if (!IS_GROUP_HYBRID(ge))
  556. continue;
  557. priority_cache->groups
  558. .entry[priority_cache->groups.size++] = ge;
  559. @@ -2598,7 +2598,7 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache)
  560. const gnutls_sign_entry_st *se;
  561. unsigned have_ec = 0;
  562. unsigned have_dh = 0;
  563. - unsigned have_kem = 0;
  564. + unsigned have_hybrid = 0;
  565. unsigned tls_sig_sem = 0;
  566. const version_entry_st *tlsmax = NULL, *vers;
  567. const version_entry_st *dtlsmax = NULL;
  568. @@ -2807,9 +2807,9 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache)
  569. priority_cache->cs.entry[priority_cache->cs.size++] =
  570. ce;
  571. - if (!have_kem) {
  572. - have_kem = 1;
  573. - add_kem(priority_cache);
  574. + if (!have_hybrid) {
  575. + have_hybrid = 1;
  576. + add_hybrid(priority_cache);
  577. }
  578. }
  579. }
  580. @@ -2851,8 +2851,8 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache)
  581. }
  582. }
  583. - if (have_tls13 && (!have_ec || !have_dh || !have_kem)) {
  584. - /* scan groups to determine have_{ec,dh,kem} */
  585. + if (have_tls13 && (!have_ec || !have_dh || !have_hybrid)) {
  586. + /* scan groups to determine have_{ec,dh,hybrid} */
  587. for (i = 0; i < priority_cache->_supported_ecc.num_priorities;
  588. i++) {
  589. const gnutls_group_entry_st *ge;
  590. @@ -2865,12 +2865,13 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache)
  591. } else if (ge->prime && !have_dh) {
  592. add_dh(priority_cache);
  593. have_dh = 1;
  594. - } else if (IS_KEM(ge->pk) && !have_kem) {
  595. - add_kem(priority_cache);
  596. - have_kem = 1;
  597. + } else if (IS_GROUP_HYBRID(ge) &&
  598. + !have_hybrid) {
  599. + add_hybrid(priority_cache);
  600. + have_hybrid = 1;
  601. }
  602. - if (have_dh && have_ec && have_kem)
  603. + if (have_dh && have_ec && have_hybrid)
  604. break;
  605. }
  606. }
  607. diff --git a/lib/session.c b/lib/session.c
  608. index a9049a464..7fcbe4fb4 100644
  609. --- a/lib/session.c
  610. +++ b/lib/session.c
  611. @@ -415,7 +415,11 @@ char *gnutls_session_get_desc(gnutls_session_t session)
  612. snprintf(kx_name, sizeof(kx_name), "(PSK)");
  613. }
  614. } else if (group && sign_str) {
  615. - if (group->curve)
  616. + if (IS_GROUP_HYBRID(group))
  617. + snprintf(kx_name, sizeof(kx_name),
  618. + "(HYBRID-%s)-(%s)", group_name,
  619. + sign_str);
  620. + else if (group->curve)
  621. snprintf(kx_name, sizeof(kx_name),
  622. "(ECDHE-%s)-(%s)", group_name,
  623. sign_str);
  624. diff --git a/tests/pqc-hybrid-kx.sh b/tests/pqc-hybrid-kx.sh
  625. index da936cf04..4984cd4b4 100644
  626. --- a/tests/pqc-hybrid-kx.sh
  627. +++ b/tests/pqc-hybrid-kx.sh
  628. @@ -33,34 +33,113 @@
  629. . "${srcdir}/scripts/common.sh"
  630. +# First check any mismatch in the gnutls-cli --list
  631. if ! "${CLI}" --list | grep '^Groups: .*GROUP-X25519-KYBER768.*' >/dev/null; then
  632. if "${CLI}" --list | grep '^Public Key Systems: .*KYBER768.*' >/dev/null; then
  633. - fail "KYBER768 is in Public Key Systems, while GROUP-X25519-KYBER768 is NOT in Groups"
  634. + fail '' 'KYBER768 is in Public Key Systems, while GROUP-X25519-KYBER768 is NOT in Groups'
  635. fi
  636. - exit 77
  637. else
  638. if ! "${CLI}" --list | grep '^Public Key Systems: .*KYBER768.*' >/dev/null; then
  639. - fail "KYBER768 is NOT in Public Key Systems, while GROUP-X25519-KYBER768 is in Groups"
  640. + fail '' 'KYBER768 is NOT in Public Key Systems, while GROUP-X25519-KYBER768 is in Groups'
  641. + fi
  642. +fi
  643. +
  644. +if ! "${CLI}" --list | grep '^Groups: .*GROUP-\(SECP256R1\|X25519\)-MLKEM768.*' >/dev/null; then
  645. + if "${CLI}" --list | grep '^Public Key Systems: .*ML-KEM-768.*' >/dev/null; then
  646. + fail '' 'ML-KEM-768 is in Public Key Systems, while GROUP-SECP256R1-MLKEM768 or GROUP-X25519-MLKEM768 is NOT in Groups'
  647. + fi
  648. +else
  649. + if ! "${CLI}" --list | grep '^Public Key Systems: .*ML-KEM-768.*' >/dev/null; then
  650. + fail '' 'ML-KEM-768 is NOT in Public Key Systems, while GROUP-SECP256R1-MLKEM768 or GROUP-X25519-MLKEM768 is in Groups'
  651. fi
  652. fi
  653. +# If none of those hybrid groups is supported, skip the test
  654. +if ! "${CLI}" --list | grep '^Groups: .*GROUP-\(X25519-KYBER768\|SECP256R1-MLKEM768\|X25519-MLKEM768\).*' >/dev/null; then
  655. + exit 77
  656. +fi
  657. +
  658. testdir=`create_testdir pqc-hybrid-kx`
  659. KEY="$srcdir/../doc/credentials/x509/key-ecc.pem"
  660. CERT="$srcdir/../doc/credentials/x509/cert-ecc.pem"
  661. CACERT="$srcdir/../doc/credentials/x509/ca.pem"
  662. -eval "${GETPORT}"
  663. -launch_server --echo --priority NORMAL:-GROUP-ALL:+GROUP-X25519-KYBER768 --x509keyfile="$KEY" --x509certfile="$CERT"
  664. -PID=$!
  665. -wait_server ${PID}
  666. +# Test all supported hybrid groups
  667. +for group in X25519-KYBER768 SECP256R1-MLKEM768 X25519-MLKEM768; do
  668. + if ! "${CLI}" --list | grep "^Groups: .*GROUP-$group.*" >/dev/null; then
  669. + echo "$group is not supported, skipping" >&2
  670. + continue
  671. + fi
  672. +
  673. + eval "${GETPORT}"
  674. + launch_server --echo --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509keyfile="$KEY" --x509certfile="$CERT"
  675. + PID=$!
  676. + wait_server ${PID}
  677. +
  678. + ${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509cafile="$CACERT" --logfile="$testdir/cli.log" </dev/null
  679. + kill ${PID}
  680. + wait
  681. +
  682. + grep -- "- Description: (TLS1.3-X.509)-(HYBRID-$group)-(ECDSA-SECP256R1-SHA256)-(AES-256-GCM)" "$testdir/cli.log" || { echo "unexpected handshake description"; cat "$testdir/cli.log"; exit 1; }
  683. +done
  684. +
  685. +# KEM based groups cannot be used standalone
  686. +for group in KYBER768 MLKEM768; do
  687. + if ! "${CLI}" --list | grep "^Groups: .*GROUP-$group.*" >/dev/null; then
  688. + "$group is not supported, skipping"
  689. + continue
  690. + fi
  691. +
  692. + eval "${GETPORT}"
  693. + launch_server --echo --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509keyfile="$KEY" --x509certfile="$CERT"
  694. + PID=$!
  695. + wait_server ${PID}
  696. +
  697. + ${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509cafile="$CACERT" --logfile="$testdir/cli.log" </dev/null
  698. + rc=$?
  699. + kill ${PID}
  700. + wait
  701. +
  702. + if test $rc -eq 0; then
  703. + fail '' 'Handshake succeeded with a standalone KEM group'
  704. + fi
  705. +done
  706. +
  707. +# Check if disabling a curve will also disables hybrid groups with it
  708. +cat <<_EOF_ > "$testdir/test.config"
  709. +[overrides]
  710. +
  711. +disabled-curve = x25519
  712. +_EOF_
  713. +
  714. +for group in X25519-KYBER768 SECP256R1-MLKEM768 X25519-MLKEM768; do
  715. + if ! "${CLI}" --list | grep "^Groups: .*GROUP-$group.*" >/dev/null; then
  716. + echo "$group is not supported, skipping" >&2
  717. + continue
  718. + fi
  719. -${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority NORMAL:-GROUP-ALL:+GROUP-X25519-KYBER768 --x509cafile="$CACERT" --logfile="$testdir/cli.log" </dev/null
  720. + eval "${GETPORT}"
  721. + GNUTLS_SYSTEM_PRIORITY_FILE="$testdir/test.config" launch_server --echo --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509keyfile="$KEY" --x509certfile="$CERT"
  722. + PID=$!
  723. + wait_server ${PID}
  724. -kill ${PID}
  725. -wait
  726. + ${VALGRIND} "${CLI}" -p "${PORT}" localhost --priority "NORMAL:-GROUP-ALL:+GROUP-$group" --x509cafile="$CACERT" --logfile="$testdir/cli.log" </dev/null
  727. + rc=$?
  728. + kill ${PID}
  729. + wait
  730. -grep -- '- Description: (TLS1.3-X.509)-(ECDHE-X25519-KYBER768)-(ECDSA-SECP256R1-SHA256)-(AES-256-GCM)' "$testdir/cli.log" || { echo "unexpected handshake description"; exit 1; }
  731. + case "$group" in
  732. + X25519*)
  733. + if test $rc -eq 0; then
  734. + fail '' 'Handshake succeeded with a hybrid group with X25519'
  735. + fi
  736. + ;;
  737. + *)
  738. + grep -- "- Description: (TLS1.3-X.509)-(HYBRID-$group)-(ECDSA-SECP256R1-SHA256)-(AES-256-GCM)" "$testdir/cli.log" || { echo "unexpected handshake description"; cat "$testdir/cli.log"; exit 1; }
  739. + ;;
  740. + esac
  741. +done
  742. rm -rf "$testdir"
  743. exit 0
  744. --
  745. 2.47.1