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 if (!TEST_true(EVP_PKEY_set1_encoded_public_key(bkey, rawpub, publen)))
144 goto err;
145
146 /* Encapsulate Bob's key */
147 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, bkey, NULL);
148 if (!TEST_ptr(ctx))
149 goto err;
150
151 if (!TEST_int_gt(EVP_PKEY_encapsulate_init(ctx, NULL), 0))
152 goto err;
153
154 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, NULL, &wrpkeylen, NULL,
155 &bgenkeylen), 0))
156 goto err;
157
158 if (!TEST_size_t_gt(wrpkeylen, 0) || !TEST_size_t_gt(bgenkeylen, 0))
159 goto err;
160
161 wrpkey = OPENSSL_zalloc(wrpkeylen);
162 bgenkey = OPENSSL_zalloc(bgenkeylen);
163 if (!TEST_ptr(wrpkey) || !TEST_ptr(bgenkey))
164 goto err;
165
166 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, wrpkey, &wrpkeylen, bgenkey,
167 &bgenkeylen), 0))
168 goto err;
169
170 EVP_PKEY_CTX_free(ctx);
171
172 /* Alice now decapsulates Bob's key */
173 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, akey, NULL);
174 if (!TEST_ptr(ctx))
175 goto err;
176
177 if (!TEST_int_gt(EVP_PKEY_decapsulate_init(ctx, NULL), 0))
178 goto err;
179
180 if (!TEST_int_gt(EVP_PKEY_decapsulate(ctx, NULL, &agenkeylen, wrpkey,
181 wrpkeylen), 0))
182 goto err;
183
184 if (!TEST_size_t_gt(agenkeylen, 0))
185 goto err;
186
187 agenkey = OPENSSL_zalloc(agenkeylen);
188 if (!TEST_ptr(agenkey))
189 goto err;
190
191 if (!TEST_int_gt(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
192 wrpkeylen), 0))
193 goto err;
194
195 /* Hopefully we ended up with a shared key */
196 if (!TEST_mem_eq(agenkey, agenkeylen, bgenkey, bgenkeylen))
197 goto err;
198
199 /* Verify we generated a non-zero shared key */
200 for (i = 0; i < agenkeylen; i++)
201 if (agenkey[i] != 0)
202 break;
203 if (!TEST_size_t_ne(i, agenkeylen))
204 goto err;
205
206 res = 1;
207 err:
208 EVP_PKEY_CTX_free(ctx);
209 EVP_PKEY_free(akey);
210 EVP_PKEY_free(bkey);
211 OPENSSL_free(rawpub);
212 OPENSSL_free(wrpkey);
213 OPENSSL_free(agenkey);
214 OPENSSL_free(bgenkey);
215 return res;
216 }
217
test_non_derandomised_ml_kem(void)218 static int test_non_derandomised_ml_kem(void)
219 {
220 static const int alg[3] = {
221 EVP_PKEY_ML_KEM_512,
222 EVP_PKEY_ML_KEM_768,
223 EVP_PKEY_ML_KEM_1024
224 };
225 EVP_RAND_CTX *privctx;
226 EVP_RAND_CTX *pubctx;
227 EVP_MD *sha256;
228 int i, ret = 0;
229
230 if (!TEST_ptr(privctx = RAND_get0_private(NULL))
231 || !TEST_ptr(pubctx = RAND_get0_public(NULL)))
232 return 0;
233
234 if (!TEST_ptr(sha256 = EVP_MD_fetch(NULL, "sha256", NULL)))
235 return 0;
236
237 for (i = 0; i < (int) OSSL_NELEM(alg); ++i) {
238 const ML_KEM_VINFO *v;
239 OSSL_PARAM params[3], *p;
240 uint8_t hash[32];
241 EVP_PKEY *akey = NULL, *bkey = NULL;
242 size_t publen;
243 unsigned char *rawpub = NULL;
244 EVP_PKEY_CTX *ctx = NULL;
245 unsigned char *wrpkey = NULL, *agenkey = NULL, *bgenkey = NULL;
246 size_t wrpkeylen, agenkeylen, bgenkeylen;
247 unsigned int strength = 256;
248 unsigned char c;
249 int res = -1;
250
251 if ((v = ossl_ml_kem_get_vinfo(alg[i])) == NULL)
252 goto done;
253
254 /* Configure the private RNG to output just the keygen seed */
255 p = params;
256 *p++ = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
257 gen_seed, sizeof(gen_seed));
258 *p++ = OSSL_PARAM_construct_uint(OSSL_RAND_PARAM_STRENGTH, &strength);
259 *p = OSSL_PARAM_construct_end();
260 if (!TEST_true(EVP_RAND_CTX_set_params(privctx, params)))
261 goto done;
262
263 res = -2;
264 /* Generate Alice's key */
265 akey = EVP_PKEY_Q_keygen(testctx, NULL, v->algorithm_name);
266 if (!TEST_ptr(akey))
267 goto done;
268
269 /* Check that no more entropy is available! */
270 if (!TEST_int_le(RAND_priv_bytes(&c, 1), 0))
271 goto done;
272
273 /* Get the raw public key */
274 publen = EVP_PKEY_get1_encoded_public_key(akey, &rawpub);
275 if (!TEST_size_t_eq(publen, v->pubkey_bytes))
276 goto done;
277
278 res = -3;
279 /* Check that we got the expected 'rho' value in the ciphertext */
280 if (!TEST_mem_eq(rawpub + v->vector_bytes, ML_KEM_RANDOM_BYTES,
281 expected_rho[i], ML_KEM_RANDOM_BYTES))
282 goto done;
283
284 res = -4;
285 /* Create Bob's key and populate it with Alice's public key data */
286 bkey = EVP_PKEY_new();
287 if (!TEST_ptr(bkey))
288 goto done;
289 if (!TEST_int_gt(EVP_PKEY_copy_parameters(bkey, akey), 0))
290 goto done;
291 if (!TEST_true(EVP_PKEY_set1_encoded_public_key(bkey, rawpub, publen)))
292 goto done;
293
294 /* Configure the public RNG to output just the encap seed */
295 p = params;
296 *p = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
297 enc_seed, sizeof(enc_seed));
298 if (!TEST_true(EVP_RAND_CTX_set_params(pubctx, params)))
299 goto done;
300
301 /* Encapsulate Bob's key */
302 res = -5;
303 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, bkey, NULL);
304 if (!TEST_ptr(ctx))
305 goto done;
306 if (!TEST_int_gt(EVP_PKEY_encapsulate_init(ctx, NULL), 0))
307 goto done;
308 if (!TEST_int_gt(EVP_PKEY_encapsulate(ctx, NULL, &wrpkeylen, NULL,
309 &bgenkeylen), 0))
310 goto done;
311 if (!TEST_size_t_eq(wrpkeylen, v->ctext_bytes)
312 || !TEST_size_t_eq(bgenkeylen, ML_KEM_SHARED_SECRET_BYTES))
313 goto done;
314 wrpkey = OPENSSL_zalloc(wrpkeylen);
315 bgenkey = OPENSSL_zalloc(bgenkeylen);
316 if (!TEST_ptr(wrpkey) || !TEST_ptr(bgenkey))
317 goto done;
318 if (!TEST_true(EVP_PKEY_encapsulate(ctx, wrpkey, &wrpkeylen, bgenkey,
319 &bgenkeylen)))
320 goto done;
321 EVP_PKEY_CTX_free(ctx);
322 ctx = NULL;
323 /* Check that no more public entropy is available! */
324 if (!TEST_int_le(RAND_bytes(&c, 1), 0))
325 goto done;
326
327 res = -6;
328 /* Check the ciphertext hash */
329 if (!TEST_true(EVP_Digest(wrpkey, v->ctext_bytes,
330 hash, NULL, sha256, NULL))
331 || !TEST_mem_eq(hash, sizeof(hash),
332 expected_ctext_sha256[i],
333 sizeof(expected_ctext_sha256[i])))
334 goto done;
335 /* Check for the expected shared secret */
336 if (!TEST_mem_eq(bgenkey, bgenkeylen,
337 expected_shared_secret[i], ML_KEM_SHARED_SECRET_BYTES))
338 goto done;
339
340 /*
341 * Alice now decapsulates Bob's key. Decap should not need a seed if
342 * the ciphertext length is good.
343 */
344 res = -7;
345 ctx = EVP_PKEY_CTX_new_from_pkey(testctx, akey, NULL);
346 if (!TEST_ptr(ctx))
347 goto done;
348 if (!TEST_int_gt(EVP_PKEY_decapsulate_init(ctx, NULL), 0))
349 goto done;
350 if (!TEST_true(EVP_PKEY_decapsulate(ctx, NULL, &agenkeylen, wrpkey,
351 wrpkeylen)))
352 goto done;
353 if (!TEST_size_t_eq(agenkeylen, ML_KEM_SHARED_SECRET_BYTES))
354 goto done;
355 agenkey = OPENSSL_zalloc(agenkeylen);
356 if (!TEST_ptr(agenkey))
357 goto done;
358 if (!TEST_true(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
359 wrpkeylen)))
360 goto done;
361 /* Hopefully we ended up with a shared key */
362 if (!TEST_mem_eq(agenkey, agenkeylen, bgenkey, bgenkeylen))
363 goto done;
364
365 res = -8;
366 /* Now a quick negative test by zeroing the ciphertext */
367 memset(wrpkey, 0, v->ctext_bytes);
368 if (!TEST_true(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
369 wrpkeylen)))
370 goto done;
371 if (!TEST_mem_ne(agenkey, agenkeylen, bgenkey, bgenkeylen))
372 goto done;
373
374 res = -9;
375 /* Configure decap entropy for a bad ciphertext length */
376 p = params;
377 *p = OSSL_PARAM_construct_octet_string(OSSL_RAND_PARAM_TEST_ENTROPY,
378 dec_seed, sizeof(dec_seed));
379 if (!TEST_true(EVP_RAND_CTX_set_params(pubctx, params)))
380 goto done;
381
382 /* This time decap should fail, and return the decap entropy */
383 if (!TEST_false(EVP_PKEY_decapsulate(ctx, agenkey, &agenkeylen, wrpkey,
384 wrpkeylen - 1)))
385 goto done;
386 if (!TEST_mem_eq(agenkey, agenkeylen, dec_seed, sizeof(dec_seed)))
387 goto done;
388
389 res = 0;
390
391 done:
392 EVP_PKEY_CTX_free(ctx);
393 EVP_PKEY_free(akey);
394 EVP_PKEY_free(bkey);
395 OPENSSL_free(rawpub);
396 OPENSSL_free(wrpkey);
397 OPENSSL_free(agenkey);
398 OPENSSL_free(bgenkey);
399 if (res != 0)
400 ret = res;
401 }
402
403 EVP_MD_free(sha256);
404 return ret == 0;
405 }
406
setup_tests(void)407 int setup_tests(void)
408 {
409 int test_rand = 0;
410 OPTION_CHOICE o;
411
412 while ((o = opt_next()) != OPT_EOF) {
413 switch (o) {
414 case OPT_TEST_RAND:
415 test_rand = 1;
416 break;
417 case OPT_TEST_CASES:
418 break;
419 default:
420 return 0;
421 }
422 }
423
424 if (test_rand != 0) {
425 /* Cargo-culted from test/rand_test.c, this may need changes */
426 if (!TEST_true(RAND_set_DRBG_type(NULL, "TEST-RAND", "fips=no",
427 NULL, NULL)))
428 return 0;
429 ADD_TEST(test_non_derandomised_ml_kem);
430 return 1;
431 }
432
433 ADD_TEST(test_ml_kem);
434 return 1;
435 }
436