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