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