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