1 /*
2 * Copyright 2015-2025 The OpenSSL Project Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License 2.0 (the "License"). You may not use
5 * this file except in compliance with the License. You can obtain a copy
6 * in the file LICENSE in the source distribution or at
7 * https://www.openssl.org/source/license.html
8 */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <openssl/conf.h>
14 #include <openssl/crypto.h>
15 #include <openssl/err.h>
16 #include <openssl/evp.h>
17 #include <openssl/provider.h>
18 #include <openssl/core_names.h>
19 #include <openssl/params.h>
20 #include <openssl/param_build.h>
21 #include <openssl/rand.h>
22 #include <crypto/ml_kem.h>
23 #include "testutil.h"
24
25 static OSSL_LIB_CTX *testctx = NULL;
26
27 typedef enum OPTION_choice {
28 OPT_ERR = -1,
29 OPT_EOF = 0,
30 OPT_CONFIG_FILE,
31 OPT_TEST_RAND,
32 OPT_TEST_ENUM
33 } OPTION_CHOICE;
34
test_get_options(void)35 const OPTIONS *test_get_options(void)
36 {
37 static const OPTIONS options[] = {
38 OPT_TEST_OPTIONS_DEFAULT_USAGE,
39 { "test-rand", OPT_TEST_RAND, '-', "Test non-derandomised ML-KEM" },
40 { NULL }
41 };
42 return options;
43 }
44
45 static uint8_t gen_seed[64] = {
46 0x7c, 0x99, 0x35, 0xa0, 0xb0, 0x76, 0x94, 0xaa, 0x0c, 0x6d, 0x10, 0xe4,
47 0xdb, 0x6b, 0x1a, 0xdd, 0x2f, 0xd8, 0x1a, 0x25, 0xcc, 0xb1, 0x48, 0x03,
48 0x2d, 0xcd, 0x73, 0x99, 0x36, 0x73, 0x7f, 0x2d, 0x86, 0x26, 0xed, 0x79,
49 0xd4, 0x51, 0x14, 0x08, 0x00, 0xe0, 0x3b, 0x59, 0xb9, 0x56, 0xf8, 0x21,
50 0x0e, 0x55, 0x60, 0x67, 0x40, 0x7d, 0x13, 0xdc, 0x90, 0xfa, 0x9e, 0x8b,
51 0x87, 0x2b, 0xfb, 0x8f
52 };
53 static uint8_t enc_seed[32] = {
54 0x14, 0x7c, 0x03, 0xf7, 0xa5, 0xbe, 0xbb, 0xa4, 0x06, 0xc8, 0xfa, 0xe1,
55 0x87, 0x4d, 0x7f, 0x13, 0xc8, 0x0e, 0xfe, 0x79, 0xa3, 0xa9, 0xa8, 0x74,
56 0xcc, 0x09, 0xfe, 0x76, 0xf6, 0x99, 0x76, 0x15
57 };
58 static uint8_t dec_seed[32] = {
59 0x4e, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x72, 0x6f, 0x69,
60 0x64, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x27, 0x72, 0x65, 0x20, 0x6c, 0x6f,
61 0x6f, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f
62 };
63 static uint8_t expected_rho[3][32] = {
64 {
65 0x7e, 0xfb, 0x9e, 0x40, 0xc3, 0xbf, 0x0f, 0xf0, 0x43, 0x29, 0x86, 0xae,
66 0x4b, 0xc1, 0xa2, 0x42, 0xce, 0x99, 0x21, 0xaa, 0x9e, 0x22, 0x44, 0x88,
67 0x19, 0x58, 0x5d, 0xea, 0x30, 0x8e, 0xb0, 0x39
68 },
69 {
70 0x16, 0x2e, 0xc0, 0x98, 0xa9, 0x00, 0xb1, 0x2d, 0xd8, 0xfa, 0xbb, 0xfb,
71 0x3f, 0xe8, 0xcb, 0x1d, 0xc4, 0xe8, 0x31, 0x5f, 0x2a, 0xf0, 0xd3, 0x2f,
72 0x00, 0x17, 0xae, 0x13, 0x6e, 0x19, 0xf0, 0x28
73 },
74 {
75 0x29, 0xb4, 0xf9, 0xf8, 0xcf, 0xba, 0xdf, 0x2e, 0x41, 0x86, 0x9a, 0xbf,
76 0xba, 0xd1, 0x07, 0x38, 0xad, 0x04, 0xcc, 0x75, 0x2b, 0xc2, 0x0c, 0x39,
77 0x47, 0x46, 0x85, 0x0e, 0x0c, 0x48, 0x47, 0xdb
78 }
79 };
80 static uint8_t expected_ctext_sha256[3][32] = {
81 {
82 0xbc, 0x29, 0xd7, 0xdf, 0x8b, 0xc5, 0x46, 0x5d, 0x98, 0x06, 0x01, 0xd8,
83 0x00, 0x25, 0x97, 0x93, 0xe2, 0x60, 0x38, 0x25, 0xa5, 0x72, 0xda, 0x6c,
84 0xd1, 0x98, 0xa5, 0x12, 0xcc, 0x6d, 0x1a, 0x34
85 },
86 {
87 0x36, 0x82, 0x9a, 0x2f, 0x35, 0xcb, 0xf4, 0xde, 0xb6, 0x2c, 0x0a, 0x12,
88 0xa1, 0x5c, 0x22, 0xda, 0xe9, 0xf8, 0xd2, 0xc2, 0x52, 0x56, 0x6f, 0xc2,
89 0x4f, 0x88, 0xab, 0xe8, 0x05, 0xcb, 0x57, 0x5e
90 },
91 {
92 0x50, 0x81, 0x36, 0xa1, 0x3f, 0x8a, 0x79, 0x20, 0xe3, 0x43, 0x44, 0x98,
93 0xc6, 0x97, 0x5c, 0xbb, 0xab, 0x45, 0x7d, 0x80, 0x93, 0x09, 0xeb, 0x2f,
94 0x92, 0x45, 0x3e, 0x74, 0x09, 0x73, 0x82, 0x10
95 }
96 };
97 static uint8_t expected_shared_secret[3][32] = {
98 {
99 0x31, 0x98, 0x39, 0xe8, 0x2a, 0xb6, 0xb2, 0x22, 0xde, 0x7b, 0x61, 0x9e,
100 0x80, 0xda, 0x83, 0x91, 0x52, 0x2b, 0xbb, 0x37, 0x67, 0x70, 0x18, 0x49,
101 0x4a, 0x47, 0x42, 0xc5, 0x3f, 0x9a, 0xbf, 0xdf
102 },
103 {
104 0xe7, 0x18, 0x4a, 0x09, 0x75, 0xee, 0x34, 0x70, 0x87, 0x8d, 0x2d, 0x15,
105 0x9e, 0xc8, 0x31, 0x29, 0xc8, 0xae, 0xc2, 0x53, 0xd4, 0xee, 0x17, 0xb4,
106 0x81, 0x03, 0x11, 0xd1, 0x98, 0xcd, 0x03, 0x68
107 },
108 {
109 0x48, 0x9d, 0xd1, 0xe9, 0xc2, 0xbe, 0x4a, 0xf3, 0x48, 0x2b, 0xdb, 0x35,
110 0xbb, 0x26, 0xce, 0x76, 0x0e, 0x6e, 0x41, 0x4d, 0xa6, 0xec, 0xbe, 0x48,
111 0x99, 0x85, 0x74, 0x8a, 0x82, 0x5f, 0x1c, 0xd6
112 },
113 };
114
test_ml_kem(void)115 static int test_ml_kem(void)
116 {
117 EVP_PKEY *akey, *bkey = NULL;
118 int res = 0;
119 size_t publen;
120 unsigned char *rawpub = NULL;
121 EVP_PKEY_CTX *ctx = NULL;
122 unsigned char *wrpkey = NULL, *agenkey = NULL, *bgenkey = NULL;
123 size_t wrpkeylen, agenkeylen, bgenkeylen, i;
124
125 /* Generate Alice's key */
126 akey = EVP_PKEY_Q_keygen(testctx, NULL, "ML-KEM-768");
127 if (!TEST_ptr(akey))
128 goto err;
129
130 /* Get the raw public key */
131 publen = EVP_PKEY_get1_encoded_public_key(akey, &rawpub);
132 if (!TEST_size_t_gt(publen, 0))
133 goto err;
134
135 /* Create Bob's key and populate it with Alice's public key data */
136 bkey = EVP_PKEY_new();
137 if (!TEST_ptr(bkey))
138 goto err;
139
140 if (!TEST_int_gt(EVP_PKEY_copy_parameters(bkey, akey), 0))
141 goto err;
142
143 /* Bob's empty key is not equal to Alice's */
144 if (!TEST_false(EVP_PKEY_eq(akey, bkey))
145 || !TEST_false(EVP_PKEY_eq(bkey, akey)))
146 goto err;
147
148 if (!TEST_true(EVP_PKEY_set1_encoded_public_key(bkey, rawpub, publen)))
149 goto err;
150
151 /* Bob's copy of Alice's public key makes the two equal */
152 if (!TEST_true(EVP_PKEY_eq(akey, bkey))
153 || !TEST_true(EVP_PKEY_eq(bkey, akey)))
154 goto err;
155
156 /* Encapsulate Bob's key */
157 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, bkey, NULL);
158 if (!TEST_ptr(ctx))
159 goto err;
160
161 if (!TEST_int_gt(EVP_PKEY_encapsulate_init(ctx, NULL), 0))
162 goto err;
163
164 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, NULL, &wrpkeylen, NULL,
165 &bgenkeylen), 0))
166 goto err;
167
168 if (!TEST_size_t_gt(wrpkeylen, 0) || !TEST_size_t_gt(bgenkeylen, 0))
169 goto err;
170
171 wrpkey = OPENSSL_zalloc(wrpkeylen);
172 bgenkey = OPENSSL_zalloc(bgenkeylen);
173 if (!TEST_ptr(wrpkey) || !TEST_ptr(bgenkey))
174 goto err;
175
176 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, wrpkey, &wrpkeylen, bgenkey,
177 &bgenkeylen), 0))
178 goto err;
179
180 EVP_PKEY_CTX_free(ctx);
181
182 /* Alice now decapsulates Bob's key */
183 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, akey, NULL);
184 if (!TEST_ptr(ctx))
185 goto err;
186
187 if (!TEST_int_gt(EVP_PKEY_decapsulate_init(ctx, NULL), 0))
188 goto err;
189
190 if (!TEST_int_gt(EVP_PKEY_decapsulate(ctx, NULL, &agenkeylen, wrpkey,
191 wrpkeylen), 0))
192 goto err;
193
194 if (!TEST_size_t_gt(agenkeylen, 0))
195 goto err;
196
197 agenkey = OPENSSL_zalloc(agenkeylen);
198 if (!TEST_ptr(agenkey))
199 goto err;
200
201 if (!TEST_int_gt(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
202 wrpkeylen), 0))
203 goto err;
204
205 /* Hopefully we ended up with a shared key */
206 if (!TEST_mem_eq(agenkey, agenkeylen, bgenkey, bgenkeylen))
207 goto err;
208
209 /* Verify we generated a non-zero shared key */
210 for (i = 0; i < agenkeylen; i++)
211 if (agenkey[i] != 0)
212 break;
213 if (!TEST_size_t_ne(i, agenkeylen))
214 goto err;
215
216 res = 1;
217 err:
218 EVP_PKEY_CTX_free(ctx);
219 EVP_PKEY_free(akey);
220 EVP_PKEY_free(bkey);
221 OPENSSL_free(rawpub);
222 OPENSSL_free(wrpkey);
223 OPENSSL_free(agenkey);
224 OPENSSL_free(bgenkey);
225 return res;
226 }
227
test_non_derandomised_ml_kem(void)228 static int test_non_derandomised_ml_kem(void)
229 {
230 static const int alg[3] = {
231 EVP_PKEY_ML_KEM_512,
232 EVP_PKEY_ML_KEM_768,
233 EVP_PKEY_ML_KEM_1024
234 };
235 EVP_RAND_CTX *privctx;
236 EVP_RAND_CTX *pubctx;
237 EVP_MD *sha256;
238 int i, ret = 0;
239
240 if (!TEST_ptr(privctx = RAND_get0_private(NULL))
241 || !TEST_ptr(pubctx = RAND_get0_public(NULL)))
242 return 0;
243
244 if (!TEST_ptr(sha256 = EVP_MD_fetch(NULL, "sha256", NULL)))
245 return 0;
246
247 for (i = 0; i < (int) OSSL_NELEM(alg); ++i) {
248 const ML_KEM_VINFO *v;
249 OSSL_PARAM params[3], *p;
250 uint8_t hash[32];
251 EVP_PKEY *akey = NULL, *bkey = NULL;
252 size_t publen;
253 unsigned char *rawpub = NULL;
254 EVP_PKEY_CTX *ctx = NULL;
255 unsigned char *wrpkey = NULL, *agenkey = NULL, *bgenkey = NULL;
256 size_t wrpkeylen, agenkeylen, bgenkeylen;
257 unsigned int strength = 256;
258 unsigned char c;
259 int res = -1;
260
261 if ((v = ossl_ml_kem_get_vinfo(alg[i])) == NULL)
262 goto done;
263
264 /* Configure the private RNG to output just the keygen seed */
265 p = params;
266 *p++ = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
267 gen_seed, sizeof(gen_seed));
268 *p++ = OSSL_PARAM_construct_uint(OSSL_RAND_PARAM_STRENGTH, &strength);
269 *p = OSSL_PARAM_construct_end();
270 if (!TEST_true(EVP_RAND_CTX_set_params(privctx, params)))
271 goto done;
272
273 res = -2;
274 /* Generate Alice's key */
275 akey = EVP_PKEY_Q_keygen(testctx, NULL, v->algorithm_name);
276 if (!TEST_ptr(akey))
277 goto done;
278
279 /* Check that no more entropy is available! */
280 if (!TEST_int_le(RAND_priv_bytes(&c, 1), 0))
281 goto done;
282
283 /* Get the raw public key */
284 publen = EVP_PKEY_get1_encoded_public_key(akey, &rawpub);
285 if (!TEST_size_t_eq(publen, v->pubkey_bytes))
286 goto done;
287
288 res = -3;
289 /* Check that we got the expected 'rho' value in the ciphertext */
290 if (!TEST_mem_eq(rawpub + v->vector_bytes, ML_KEM_RANDOM_BYTES,
291 expected_rho[i], ML_KEM_RANDOM_BYTES))
292 goto done;
293
294 res = -4;
295 /* Create Bob's key and populate it with Alice's public key data */
296 bkey = EVP_PKEY_new();
297 if (!TEST_ptr(bkey))
298 goto done;
299 if (!TEST_int_gt(EVP_PKEY_copy_parameters(bkey, akey), 0))
300 goto done;
301 if (!TEST_true(EVP_PKEY_set1_encoded_public_key(bkey, rawpub, publen)))
302 goto done;
303
304 /* Configure the public RNG to output just the encap seed */
305 p = params;
306 *p = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
307 enc_seed, sizeof(enc_seed));
308 if (!TEST_true(EVP_RAND_CTX_set_params(pubctx, params)))
309 goto done;
310
311 /* Encapsulate Bob's key */
312 res = -5;
313 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, bkey, NULL);
314 if (!TEST_ptr(ctx))
315 goto done;
316 if (!TEST_int_gt(EVP_PKEY_encapsulate_init(ctx, NULL), 0))
317 goto done;
318 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, NULL, &wrpkeylen, NULL,
319 &bgenkeylen), 0))
320 goto done;
321 if (!TEST_size_t_eq(wrpkeylen, v->ctext_bytes)
322 || !TEST_size_t_eq(bgenkeylen, ML_KEM_SHARED_SECRET_BYTES))
323 goto done;
324 wrpkey = OPENSSL_zalloc(wrpkeylen);
325 bgenkey = OPENSSL_zalloc(bgenkeylen);
326 if (!TEST_ptr(wrpkey) || !TEST_ptr(bgenkey))
327 goto done;
328 if (!TEST_true(EVP_PKEY_encapsulate(ctx, wrpkey, &wrpkeylen, bgenkey,
329 &bgenkeylen)))
330 goto done;
331 EVP_PKEY_CTX_free(ctx);
332 ctx = NULL;
333 /* Check that no more public entropy is available! */
334 if (!TEST_int_le(RAND_bytes(&c, 1), 0))
335 goto done;
336
337 res = -6;
338 /* Check the ciphertext hash */
339 if (!TEST_true(EVP_Digest(wrpkey, v->ctext_bytes,
340 hash, NULL, sha256, NULL))
341 || !TEST_mem_eq(hash, sizeof(hash),
342 expected_ctext_sha256[i],
343 sizeof(expected_ctext_sha256[i])))
344 goto done;
345 /* Check for the expected shared secret */
346 if (!TEST_mem_eq(bgenkey, bgenkeylen,
347 expected_shared_secret[i], ML_KEM_SHARED_SECRET_BYTES))
348 goto done;
349
350 /*
351 * Alice now decapsulates Bob's key. Decap should not need a seed if
352 * the ciphertext length is good.
353 */
354 res = -7;
355 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, akey, NULL);
356 if (!TEST_ptr(ctx))
357 goto done;
358 if (!TEST_int_gt(EVP_PKEY_decapsulate_init(ctx, NULL), 0))
359 goto done;
360 if (!TEST_true(EVP_PKEY_decapsulate(ctx, NULL, &agenkeylen, wrpkey,
361 wrpkeylen)))
362 goto done;
363 if (!TEST_size_t_eq(agenkeylen, ML_KEM_SHARED_SECRET_BYTES))
364 goto done;
365 agenkey = OPENSSL_zalloc(agenkeylen);
366 if (!TEST_ptr(agenkey))
367 goto done;
368 if (!TEST_true(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
369 wrpkeylen)))
370 goto done;
371 /* Hopefully we ended up with a shared key */
372 if (!TEST_mem_eq(agenkey, agenkeylen, bgenkey, bgenkeylen))
373 goto done;
374
375 res = -8;
376 /* Now a quick negative test by zeroing the ciphertext */
377 memset(wrpkey, 0, v->ctext_bytes);
378 if (!TEST_true(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
379 wrpkeylen)))
380 goto done;
381 if (!TEST_mem_ne(agenkey, agenkeylen, bgenkey, bgenkeylen))
382 goto done;
383
384 res = -9;
385 /* Configure decap entropy for a bad ciphertext length */
386 p = params;
387 *p = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
388 dec_seed, sizeof(dec_seed));
389 if (!TEST_true(EVP_RAND_CTX_set_params(pubctx, params)))
390 goto done;
391
392 /* This time decap should fail, and return the decap entropy */
393 if (!TEST_false(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
394 wrpkeylen - 1)))
395 goto done;
396 if (!TEST_mem_eq(agenkey, agenkeylen, dec_seed, sizeof(dec_seed)))
397 goto done;
398
399 res = 0;
400
401 done:
402 EVP_PKEY_CTX_free(ctx);
403 EVP_PKEY_free(akey);
404 EVP_PKEY_free(bkey);
405 OPENSSL_free(rawpub);
406 OPENSSL_free(wrpkey);
407 OPENSSL_free(agenkey);
408 OPENSSL_free(bgenkey);
409 if (res != 0)
410 ret = res;
411 }
412
413 EVP_MD_free(sha256);
414 return ret == 0;
415 }
416
setup_tests(void)417 int setup_tests(void)
418 {
419 int test_rand = 0;
420 OPTION_CHOICE o;
421
422 while ((o = opt_next()) != OPT_EOF) {
423 switch (o) {
424 case OPT_TEST_RAND:
425 test_rand = 1;
426 break;
427 case OPT_TEST_CASES:
428 break;
429 default:
430 return 0;
431 }
432 }
433
434 if (test_rand != 0) {
435 /* Cargo-culted from test/rand_test.c, this may need changes */
436 if (!TEST_true(RAND_set_DRBG_type(NULL, "TEST-RAND", "fips=no",
437 NULL, NULL)))
438 return 0;
439 ADD_TEST(test_non_derandomised_ml_kem);
440 return 1;
441 }
442
443 ADD_TEST(test_ml_kem);
444 return 1;
445 }
446