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