xref: /freebsd/crypto/openssl/test/ml_kem_evp_extra_test.c (revision 046c625e9382e17da953767b881aaa782fa73af8)
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