1 /* 2 * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. 3 * 4 * Licensed under the Apache License 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * https://www.openssl.org/source/license.html 8 * or in the file LICENSE in the source distribution. 9 */ 10 11 /* 12 * Test ml-kem operation. 13 */ 14 #include <string.h> 15 #include <openssl/evp.h> 16 #include <openssl/err.h> 17 #include <openssl/rand.h> 18 #include <openssl/byteorder.h> 19 #include <openssl/ml_kem.h> 20 #include "internal/nelem.h" 21 #include "fuzzer.h" 22 23 /** 24 * @brief Consumes an 8-bit unsigned integer from a buffer. 25 * 26 * This function extracts an 8-bit unsigned integer from the provided buffer, 27 * updates the buffer pointer, and adjusts the remaining length. 28 * 29 * @param buf Pointer to the input buffer. 30 * @param len Pointer to the size of the remaining buffer; updated after consumption. 31 * @param val Pointer to store the extracted 8-bit value. 32 * 33 * @return Pointer to the updated buffer position after reading the value, 34 * or NULL if the buffer does not contain enough data. 35 */ 36 static uint8_t *consume_uint8t(const uint8_t *buf, size_t *len, uint8_t *val) 37 { 38 if (*len < sizeof(uint8_t)) 39 return NULL; 40 *val = *buf; 41 *len -= sizeof(uint8_t); 42 return (uint8_t *)buf + 1; 43 } 44 45 /** 46 * @brief Selects a key type and size from a buffer. 47 * 48 * This function reads a key size value from the buffer, determines the 49 * corresponding key type and length, and updates the buffer pointer 50 * accordingly. If `only_valid` is set, it restricts selection to valid 51 * key sizes; otherwise, it includes some invalid sizes for testing. 52 * 53 * @param buf Pointer to the buffer pointer; updated after reading. 54 * @param len Pointer to the remaining buffer size; updated accordingly. 55 * @param keytype Pointer to store the selected key type string. 56 * @param keylen Pointer to store the selected key length. 57 * @param only_valid Flag to restrict selection to valid key sizes. 58 * 59 * @return 1 if a key type is successfully selected, 0 on failure. 60 */ 61 static int select_keytype_and_size(uint8_t **buf, size_t *len, 62 char **keytype, size_t *keylen, 63 int only_valid) 64 { 65 uint16_t keysize; 66 uint16_t modulus = 6; 67 68 /* 69 * Note: We don't really care about endianess here, we just 70 * want a random 16 bit value 71 */ 72 *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); 73 *len -= sizeof(uint16_t); 74 75 if (*buf == NULL) 76 return 0; 77 78 /* 79 * select from sizes 80 * ML-KEM-512, ML-KEM-768, and ML-KEM-1024 81 * also select some invalid sizes to trigger 82 * error paths 83 */ 84 if (only_valid) 85 modulus = 3; 86 87 /* 88 * Note, keylens for valid values (cases 0-2) 89 * are taken based on input values from our unit tests 90 */ 91 switch (keysize % modulus) { 92 case 0: 93 *keytype = "ML-KEM-512"; 94 *keylen = OSSL_ML_KEM_512_PUBLIC_KEY_BYTES; 95 break; 96 case 1: 97 *keytype = "ML-KEM-768"; 98 *keylen = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES; 99 break; 100 case 2: 101 *keytype = "ML-KEM-1024"; 102 *keylen = OSSL_ML_KEM_1024_PUBLIC_KEY_BYTES; 103 break; 104 case 3: 105 /* select invalid alg */ 106 *keytype = "ML-KEM-13"; 107 *keylen = 13; 108 break; 109 case 4: 110 /* Select valid alg, but bogus size */ 111 *keytype = "ML-KEM-1024"; 112 *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); 113 *len -= sizeof(uint16_t); 114 *keylen = (size_t)keysize; 115 *keylen %= 1024; /* size to our key buffer */ 116 break; 117 default: 118 *keytype = NULL; 119 *keylen = 0; 120 break; 121 } 122 return 1; 123 } 124 125 /** 126 * @brief Creates an ML-KEM raw key from a buffer. 127 * 128 * This function selects a key type and size from the buffer, generates 129 * a random key of the appropriate length, and creates either a public 130 * or private ML-KEM key using OpenSSL's EVP_PKEY interface. 131 * 132 * @param buf Pointer to the buffer pointer; updated after reading. 133 * @param len Pointer to the remaining buffer size; updated accordingly. 134 * @param key1 Pointer to store the generated EVP_PKEY key (public or private). 135 * @param key2 Unused parameter (reserved for future use). 136 * 137 * @note The generated key is allocated using OpenSSL's EVP_PKEY functions 138 * and should be freed appropriately using `EVP_PKEY_free()`. 139 */ 140 static void create_mlkem_raw_key(uint8_t **buf, size_t *len, 141 void **key1, void **key2) 142 { 143 EVP_PKEY *pubkey; 144 char *keytype = NULL; 145 size_t keylen = 0; 146 uint8_t key[4096]; 147 int pub = 0; 148 149 if (!select_keytype_and_size(buf, len, &keytype, &keylen, 0)) 150 return; 151 152 /* 153 * Select public or private key creation based on the low order 154 * bit of the next buffer value 155 * Note that keylen as returned from select_keytype_and_size is 156 * a public key length, private keys for ML-KEM are always double 157 * the size plus 32, so make that adjustment here 158 */ 159 if ((*buf)[0] & 0x1) 160 pub = 1; 161 else 162 keylen = (keylen * 2) + 32; 163 164 /* 165 * libfuzzer provides by default up to 4096 bit input 166 * buffers, but its typically much less (between 1 and 100 bytes) 167 * so use RAND_bytes here instead 168 */ 169 if (!RAND_bytes(key, keylen)) 170 return; 171 172 /* 173 * Try to generate either a raw public or private key using random data 174 * Because the input is completely random, its effectively certain this 175 * operation will fail, but it will still exercise the code paths below, 176 * which is what we want the fuzzer to do 177 */ 178 if (pub == 1) 179 pubkey = EVP_PKEY_new_raw_public_key_ex(NULL, keytype, NULL, key, keylen); 180 else 181 pubkey = EVP_PKEY_new_raw_private_key_ex(NULL, keytype, NULL, key, keylen); 182 183 *key1 = pubkey; 184 return; 185 } 186 187 /** 188 * @brief Generates a valid ML-KEM key using OpenSSL. 189 * 190 * This function selects a valid ML-KEM key type and size from the buffer, 191 * initializes an OpenSSL EVP_PKEY context, and generates a cryptographic 192 * key accordingly. 193 * 194 * @param buf Pointer to the buffer pointer; updated after reading. 195 * @param len Pointer to the remaining buffer size; updated accordingly. 196 * @param key1 Pointer to store the generated EVP_PKEY key. 197 * @param unused Unused parameter (reserved for future use). 198 * 199 * @note The generated key is allocated using OpenSSL's EVP_PKEY functions 200 * and should be freed using `EVP_PKEY_free()`. 201 */ 202 static void keygen_mlkem_real_key(uint8_t **buf, size_t *len, 203 void **key1, void **key2) 204 { 205 char *keytype = NULL; 206 size_t keylen = 0; 207 EVP_PKEY_CTX *ctx = NULL; 208 EVP_PKEY **key; 209 210 *key1 = *key2 = NULL; 211 212 key = (EVP_PKEY **)key1; 213 214 again: 215 /* 216 * Only generate valid key types and lengths 217 * Note, no adjustment is made to keylen here, as 218 * the provider is responsible for selecting the keys and sizes 219 * for us during the EVP_PKEY_keygen call 220 */ 221 if (!select_keytype_and_size(buf, len, &keytype, &keylen, 1)) 222 return; 223 224 ctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, NULL); 225 if (!ctx) { 226 fprintf(stderr, "Failed to generate ctx\n"); 227 return; 228 } 229 230 if (!EVP_PKEY_keygen_init(ctx)) { 231 fprintf(stderr, "Failed to init keygen ctx\n"); 232 goto err; 233 } 234 235 *key = EVP_PKEY_new(); 236 if (*key == NULL) 237 goto err; 238 239 if (!EVP_PKEY_generate(ctx, key)) { 240 fprintf(stderr, "Failed to generate new real key\n"); 241 goto err; 242 } 243 244 if (key == (EVP_PKEY **)key1) { 245 EVP_PKEY_CTX_free(ctx); 246 key = (EVP_PKEY **)key2; 247 goto again; 248 } 249 250 err: 251 EVP_PKEY_CTX_free(ctx); 252 return; 253 } 254 255 /** 256 * @brief Performs key encapsulation and decapsulation using an EVP_PKEY. 257 * 258 * This function generates a random key, encapsulates it using the provided 259 * public key, then decapsulates it to retrieve the original key. It makes 260 * use of OpenSSL's EVP_PKEY API for encryption and decryption. 261 * 262 * @param[out] buf Unused output buffer (reserved for future use). 263 * @param[out] len Unused length parameter (reserved for future use). 264 * @param[in] key1 Pointer to an EVP_PKEY structure used for key operations. 265 * @param[in] in2 Unused input parameter (reserved for future use). 266 * @param[out] out1 Unused output parameter (reserved for future use). 267 * @param[out] out2 Unused output parameter (reserved for future use). 268 */ 269 static void mlkem_encap_decap(uint8_t **buf, size_t *len, void *key1, void *in2, 270 void **out1, void **out2) 271 { 272 EVP_PKEY *key = (EVP_PKEY *)key1; 273 EVP_PKEY_CTX *ctx; 274 unsigned char genkey[32]; 275 size_t genkey_len = 32; 276 unsigned char unwrappedkey[32]; 277 size_t unwrappedkey_len = 32; 278 unsigned char wrapkey[1568]; 279 size_t wrapkey_len = 1568; 280 281 ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL); 282 if (ctx == NULL) { 283 fprintf(stderr, "Failed to allocate ctx\n"); 284 goto err; 285 } 286 287 if (!EVP_PKEY_encapsulate_init(ctx, NULL)) { 288 fprintf(stderr, "Failed to init encap context\n"); 289 goto err; 290 } 291 292 if (!RAND_bytes(genkey, genkey_len)) 293 goto err; 294 295 if (EVP_PKEY_encapsulate(ctx, wrapkey, &wrapkey_len, genkey, &genkey_len) <= 0) { 296 fprintf(stderr, "Failed to encapsulate key\n"); 297 goto err; 298 } 299 300 EVP_PKEY_CTX_free(ctx); 301 ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL); 302 if (ctx == NULL) { 303 fprintf(stderr, "Failed to create context\n"); 304 goto err; 305 } 306 307 if (!EVP_PKEY_decapsulate_init(ctx, NULL)) { 308 fprintf(stderr, "Failed to init decap\n"); 309 goto err; 310 } 311 312 if (EVP_PKEY_decapsulate(ctx, unwrappedkey, &unwrappedkey_len, 313 wrapkey, wrapkey_len) <= 0) { 314 fprintf(stderr, "Failed to decap key\n"); 315 goto err; 316 } 317 318 if (memcmp(unwrappedkey, genkey, genkey_len)) 319 fprintf(stderr, "mismatch on secret comparison\n"); 320 err: 321 EVP_PKEY_CTX_free(ctx); 322 return; 323 } 324 325 /** 326 * @brief Derives a shared secret using the provided key and peer key. 327 * 328 * This function performs a key derivation operation using the given 329 * private key and peer public key. The resulting shared secret is 330 * allocated dynamically and must be freed by the caller. 331 * 332 * @param[in] key The private key used for derivation. 333 * @param[in] peer The peer's public key. 334 * @param[out] shared Pointer to the derived shared secret (allocated). 335 * @param[out] shared_len Length of the derived shared secret. 336 * 337 * @note The caller is responsible for freeing the memory allocated 338 * for `shared` using `OPENSSL_free()`. 339 */ 340 static void do_derive(EVP_PKEY *key, EVP_PKEY *peer, uint8_t **shared, size_t *shared_len) 341 { 342 EVP_PKEY_CTX *ctx = NULL; 343 344 *shared = NULL; 345 *shared_len = 0; 346 347 ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL); 348 if (ctx == NULL) { 349 fprintf(stderr, "failed to create keygen context\n"); 350 goto err; 351 } 352 353 if (!EVP_PKEY_derive_init(ctx)) { 354 fprintf(stderr, "failed to init derive context\n"); 355 goto err; 356 } 357 358 if (!EVP_PKEY_derive_set_peer(ctx, peer)) { 359 fprintf(stderr, "failed to set peer\n"); 360 goto err; 361 } 362 363 if (!EVP_PKEY_derive(ctx, NULL, shared_len)) { 364 fprintf(stderr, "Derive failed 1\n"); 365 goto err; 366 } 367 368 if (*shared_len == 0) 369 goto err; 370 371 *shared = OPENSSL_zalloc(*shared_len); 372 if (*shared == NULL) { 373 fprintf(stderr, "Failed to alloc\n"); 374 goto err; 375 } 376 if (!EVP_PKEY_derive(ctx, *shared, shared_len)) { 377 fprintf(stderr, "Derive failed 2\n"); 378 OPENSSL_free(*shared); 379 *shared = NULL; 380 *shared_len = 0; 381 goto err; 382 } 383 err: 384 EVP_PKEY_CTX_free(ctx); 385 } 386 387 /** 388 * @brief Performs a key exchange using ML-KEM. 389 * 390 * This function derives shared secrets using the provided key pairs. 391 * It calls `do_derive()` to compute shared secrets for both participants 392 * and frees the allocated memory for the shared secrets. 393 * 394 * @param[out] buf Unused output buffer (reserved for future use). 395 * @param[out] len Unused output length (reserved for future use). 396 * @param[in] key1 First key (typically Alice's key). 397 * @param[in] key2 Second key (typically Bob's key). 398 * @param[out] out1 Unused output parameter (reserved for future use). 399 * @param[out] out2 Unused output parameter (reserved for future use). 400 * 401 * @note Currently, this function does not validate whether the derived 402 * shared secrets match. A check should be added when ML-KEM 403 * supports this. 404 */ 405 static void mlkem_kex(uint8_t **buf, size_t *len, void *key1, void *key2, 406 void **out1, void **out2) 407 { 408 EVP_PKEY *alice = (EVP_PKEY *)key1; 409 EVP_PKEY *bob = (EVP_PKEY *)key2; 410 size_t boblen, alicelen; 411 uint8_t *bobshare = NULL; 412 uint8_t *aliceshare = NULL; 413 414 do_derive(alice, bob, &aliceshare, &alicelen); 415 do_derive(bob, alice, &bobshare, &boblen); 416 417 /* 418 * TODO add check of shared secrets here when ML-KEM supports this 419 */ 420 OPENSSL_free(bobshare); 421 OPENSSL_free(aliceshare); 422 } 423 424 /** 425 * @brief Exports and imports an ML-KEM key. 426 * 427 * This function extracts key material from the given key (`key1`), 428 * exports it as parameters, and then attempts to reconstruct a new 429 * key from those parameters. It uses OpenSSL's `EVP_PKEY_todata()` 430 * and `EVP_PKEY_fromdata()` functions for this process. 431 * 432 * @param[out] buf Unused output buffer (reserved for future use). 433 * @param[out] len Unused output length (reserved for future use). 434 * @param[in] key1 The key to be exported and imported. 435 * @param[in] key2 Unused input key (reserved for future use). 436 * @param[out] out1 Unused output parameter (reserved for future use). 437 * @param[out] out2 Unused output parameter (reserved for future use). 438 * 439 * @note If any step in the export-import process fails, the function 440 * logs an error and cleans up allocated resources. 441 */ 442 static void mlkem_export_import(uint8_t **buf, size_t *len, void *key1, 443 void *key2, void **out1, void **out2) 444 { 445 EVP_PKEY *alice = (EVP_PKEY *)key1; 446 EVP_PKEY *new = NULL; 447 EVP_PKEY_CTX *ctx = NULL; 448 OSSL_PARAM *params = NULL; 449 450 if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, ¶ms)) { 451 fprintf(stderr, "Failed todata\n"); 452 goto err; 453 } 454 455 ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL); 456 if (ctx == NULL) { 457 fprintf(stderr, "Failed new ctx\n"); 458 goto err; 459 } 460 461 if (!EVP_PKEY_fromdata(ctx, &new, EVP_PKEY_KEYPAIR, params)) { 462 fprintf(stderr, "Failed fromdata\n"); 463 goto err; 464 } 465 466 err: 467 EVP_PKEY_CTX_free(ctx); 468 EVP_PKEY_free(new); 469 OSSL_PARAM_free(params); 470 } 471 472 /** 473 * @brief Compares two cryptographic keys and performs equality checks. 474 * 475 * This function takes in two cryptographic keys, casts them to `EVP_PKEY` 476 * structures, and checks their equality using `EVP_PKEY_eq()`. The purpose 477 * of `buf`, `len`, `out1`, and `out2` parameters is not clear from the 478 * function's current implementation. 479 * 480 * @param buf Unused parameter (purpose unclear). 481 * @param len Unused parameter (purpose unclear). 482 * @param key1 First key, expected to be an `EVP_PKEY *`. 483 * @param key2 Second key, expected to be an `EVP_PKEY *`. 484 * @param out1 Unused parameter (purpose unclear). 485 * @param out2 Unused parameter (purpose unclear). 486 */ 487 static void mlkem_compare(uint8_t **buf, size_t *len, void *key1, 488 void *key2, void **out1, void **out2) 489 { 490 EVP_PKEY *alice = (EVP_PKEY *)key1; 491 EVP_PKEY *bob = (EVP_PKEY *)key2; 492 493 EVP_PKEY_eq(alice, alice); 494 EVP_PKEY_eq(alice, bob); 495 } 496 497 /** 498 * @brief Frees allocated ML-KEM keys. 499 * 500 * This function releases memory associated with up to four EVP_PKEY 501 * objects by calling `EVP_PKEY_free()` on each provided key. 502 * 503 * @param key1 Pointer to the first key to be freed. 504 * @param key2 Pointer to the second key to be freed. 505 * @param key3 Pointer to the third key to be freed. 506 * @param key4 Pointer to the fourth key to be freed. 507 * 508 * @note This function assumes that each key is either a valid EVP_PKEY 509 * object or NULL. Passing NULL is safe and has no effect. 510 */ 511 static void cleanup_mlkem_keys(void *key1, void *key2, 512 void *key3, void *key4) 513 { 514 EVP_PKEY_free((EVP_PKEY *)key1); 515 EVP_PKEY_free((EVP_PKEY *)key2); 516 EVP_PKEY_free((EVP_PKEY *)key3); 517 EVP_PKEY_free((EVP_PKEY *)key4); 518 return; 519 } 520 521 /** 522 * @brief Represents an operation table entry for cryptographic operations. 523 * 524 * This structure defines a table entry containing function pointers for 525 * setting up, executing, and cleaning up cryptographic operations, along 526 * with associated metadata such as a name and description. 527 * 528 * @struct op_table_entry 529 */ 530 struct op_table_entry { 531 /** Name of the operation. */ 532 char *name; 533 534 /** Description of the operation. */ 535 char *desc; 536 537 /** 538 * @brief Function pointer for setting up the operation. 539 * 540 * @param buf Pointer to the buffer pointer; may be updated. 541 * @param len Pointer to the remaining buffer size; may be updated. 542 * @param out1 Pointer to store the first output of the setup function. 543 * @param out2 Pointer to store the second output of the setup function. 544 */ 545 void (*setup)(uint8_t **buf, size_t *len, void **out1, void **out2); 546 547 /** 548 * @brief Function pointer for executing the operation. 549 * 550 * @param buf Pointer to the buffer pointer; may be updated. 551 * @param len Pointer to the remaining buffer size; may be updated. 552 * @param in1 First input parameter for the operation. 553 * @param in2 Second input parameter for the operation. 554 * @param out1 Pointer to store the first output of the operation. 555 * @param out2 Pointer to store the second output of the operation. 556 */ 557 void (*doit)(uint8_t **buf, size_t *len, void *in1, void *in2, 558 void **out1, void **out2); 559 560 /** 561 * @brief Function pointer for cleaning up after the operation. 562 * 563 * @param in1 First input parameter to be cleaned up. 564 * @param in2 Second input parameter to be cleaned up. 565 * @param out1 First output parameter to be cleaned up. 566 * @param out2 Second output parameter to be cleaned up. 567 */ 568 void (*cleanup)(void *in1, void *in2, void *out1, void *out2); 569 }; 570 571 static struct op_table_entry ops[] = { 572 { 573 "Generate ML-KEM raw key", 574 "Try generate a raw keypair using random data. Usually fails", 575 create_mlkem_raw_key, 576 NULL, 577 cleanup_mlkem_keys 578 }, { 579 "Generate ML-KEM keypair, using EVP_PKEY_keygen", 580 "Generates a real ML-KEM keypair, should always work", 581 keygen_mlkem_real_key, 582 NULL, 583 cleanup_mlkem_keys 584 }, { 585 "Do a key encap/decap operation on a key", 586 "Generate key, encap it, decap it and compare, should work", 587 keygen_mlkem_real_key, 588 mlkem_encap_decap, 589 cleanup_mlkem_keys 590 }, { 591 "Do a key exchange operation on two keys", 592 "Gen keys, do a key exchange both ways and compare", 593 keygen_mlkem_real_key, 594 mlkem_kex, 595 cleanup_mlkem_keys 596 }, { 597 "Do an export/import of key data", 598 "Exercise EVP_PKEY_todata/fromdata", 599 keygen_mlkem_real_key, 600 mlkem_export_import, 601 cleanup_mlkem_keys 602 }, { 603 "Compare keys for equality", 604 "Compare key1/key1 and key1/key2 for equality", 605 keygen_mlkem_real_key, 606 mlkem_compare, 607 cleanup_mlkem_keys 608 } 609 }; 610 611 int FuzzerInitialize(int *argc, char ***argv) 612 { 613 return 0; 614 } 615 616 /** 617 * @brief Processes a fuzzing input by selecting and executing an operation. 618 * 619 * This function interprets the first byte of the input buffer to determine 620 * an operation to execute. It then follows a setup, execution, and cleanup 621 * sequence based on the selected operation. 622 * 623 * @param buf Pointer to the input buffer. 624 * @param len Length of the input buffer. 625 * 626 * @return 0 on successful execution, -1 if the input is too short. 627 * 628 * @note The function requires at least 32 bytes in the buffer to proceed. 629 * It utilizes the `ops` operation table to dynamically determine and 630 * execute the selected operation. 631 */ 632 int FuzzerTestOneInput(const uint8_t *buf, size_t len) 633 { 634 uint8_t operation; 635 uint8_t *buffer_cursor; 636 void *in1 = NULL, *in2 = NULL; 637 void *out1 = NULL, *out2 = NULL; 638 639 if (len < 32) 640 return -1; 641 /* 642 * Get the first byte of the buffer to tell us what operation 643 * to preform 644 */ 645 buffer_cursor = consume_uint8t(buf, &len, &operation); 646 if (buffer_cursor == NULL) 647 return -1; 648 649 /* 650 * Adjust for operational array size 651 */ 652 operation %= OSSL_NELEM(ops); 653 654 /* 655 * And run our setup/doit/cleanup sequence 656 */ 657 if (ops[operation].setup != NULL) 658 ops[operation].setup(&buffer_cursor, &len, &in1, &in2); 659 if (ops[operation].doit != NULL) 660 ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2); 661 if (ops[operation].cleanup != NULL) 662 ops[operation].cleanup(in1, in2, out1, out2); 663 664 return 0; 665 } 666 667 void FuzzerCleanup(void) 668 { 669 OPENSSL_cleanup(); 670 } 671