From: Andrew Dinh Date: Fri, 14 Feb 2025 12:15:50 +0000 (+0700) Subject: Add an initial ML-DSA fuzzer X-Git-Tag: openssl-3.5.0-alpha1~490 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c0cf783178f88601a40e86b8db4d44708ad3e131;p=thirdparty%2Fopenssl.git Add an initial ML-DSA fuzzer Add an initial version of an ML-DSA fuzzer. Exercises various ML-DSA appropriate APIs. Currently it is able to randomly: 1. Attempt to create raw public private keys of various valid and invalid sizes 2. Generate legitimate keys of various sizes using the keygen api 3. Perform sign/verify operations using real generated keys 4. Perform digest sign/verify operations using real generated keys 5. Do an export and import of a key using todata/fromdata 6. Do a comparison of two equal and unequal keys Reviewed-by: Neil Horman Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/26685) --- diff --git a/doc/man7/EVP_SIGNATURE-ML-DSA.pod b/doc/man7/EVP_SIGNATURE-ML-DSA.pod index ad7569eff78..3e7cc41b242 100644 --- a/doc/man7/EVP_SIGNATURE-ML-DSA.pod +++ b/doc/man7/EVP_SIGNATURE-ML-DSA.pod @@ -100,7 +100,7 @@ To sign a message using an ML-DSA EVP_PKEY structure: size_t sig_len; unsigned char *sig = NULL; const OSSL_PARAM params[] = { - OSSL_PARAM_octet_string("context-string", (unsigned char *)"A context string", 33), + OSSL_PARAM_octet_string("context-string", (unsigned char *)"A context string", 16), OSSL_PARAM_END }; EVP_PKEY_CTX *sctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); diff --git a/fuzz/build.info b/fuzz/build.info index 21f647471fb..af0a62a3678 100644 --- a/fuzz/build.info +++ b/fuzz/build.info @@ -13,10 +13,15 @@ IF[{- !$disabled{"fuzz-afl"} || !$disabled{"fuzz-libfuzzer"} -}] PROGRAMS{noinst}=punycode pem decoder hashtable acert PROGRAMS{noinst}=v3name PROGRAMS{noinst}=provider + IF[{- !$disabled{"ml-kem"} -}] PROGRAMS{noinst}=ml-kem ENDIF + IF[{- !$disabled{"ml-dsa"} -}] + PROGRAMS{noinst}=ml-dsa + ENDIF + IF[{- !$disabled{"cmp"} -}] PROGRAMS{noinst}=cmp ENDIF @@ -148,6 +153,10 @@ IF[{- !$disabled{"fuzz-afl"} || !$disabled{"fuzz-libfuzzer"} -}] SOURCE[ml-kem]=ml-kem.c driver.c INCLUDE[ml-kem]=../include {- $ex_inc -} DEPEND[ml-kem]=../libcrypto {- $ex_lib -} + + SOURCE[ml-dsa]=ml-dsa.c driver.c + INCLUDE[ml-dsa]=../include {- $ex_inc -} + DEPEND[ml-dsa]=../libcrypto {- $ex_lib -} ENDIF IF[{- !$disabled{tests} -}] @@ -160,6 +169,10 @@ IF[{- !$disabled{tests} -}] PROGRAMS{noinst}=ml-kem-test ENDIF + IF[{- !$disabled{"ml-dsa"} -}] + PROGRAMS{noinst}=ml-dsa-test + ENDIF + IF[{- !$disabled{"cmp"} -}] PROGRAMS{noinst}=cmp-test ENDIF @@ -213,6 +226,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[ml-kem-test]=../include DEPEND[ml-kem-test]=../libcrypto.a + SOURCE[ml-dsa-test]=ml-dsa.c test-corpus.c fuzz_rand.c + INCLUDE[ml-dsa-test]=../include + DEPEND[ml-dsa-test]=../libcrypto.a + # referring to static lib allows using non-exported functions SOURCE[cms-test]=cms.c test-corpus.c diff --git a/fuzz/corpora b/fuzz/corpora index 93072ea488b..ce771805c09 160000 --- a/fuzz/corpora +++ b/fuzz/corpora @@ -1 +1 @@ -Subproject commit 93072ea488b1d9035e0ae29c7d6c5fab8f3471af +Subproject commit ce771805c094d098c25a218bc8e9f7344eccbc5a diff --git a/fuzz/ml-dsa.c b/fuzz/ml-dsa.c new file mode 100644 index 00000000000..1088f9d054f --- /dev/null +++ b/fuzz/ml-dsa.c @@ -0,0 +1,679 @@ +/* + * Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.openssl.org/source/license.html + * or in the file LICENSE in the source distribution. + */ + +/* Test ML-DSA operation. */ +#include +#include +#include +#include +#include +#include "internal/nelem.h" +#include "fuzzer.h" +#include "crypto/ml_dsa.h" + +/** + * @brief Consumes an 8-bit unsigned integer from a buffer. + * + * This function extracts an 8-bit unsigned integer from the provided buffer, + * updates the buffer pointer, and adjusts the remaining length. + * + * @param buf Pointer to the input buffer. + * @param len Pointer to the size of the remaining buffer; updated after consumption. + * @param val Pointer to store the extracted 8-bit value. + * + * @return Pointer to the updated buffer position after reading the value, + * or NULL if the buffer does not contain enough data. + */ +static uint8_t *consume_uint8_t(const uint8_t *buf, size_t *len, uint8_t *val) +{ + if (*len < sizeof(uint8_t)) + return NULL; + *val = *buf; + *len -= sizeof(uint8_t); + return (uint8_t *)buf + 1; +} + +/** + * @brief Consumes a size_t from a buffer. + * + * This function extracts a size_t from the provided buffer, updates the buffer + * pointer, and adjusts the remaining length. + * + * @param buf Pointer to the input buffer. + * @param len Pointer to the size of the remaining buffer; updated after consumption. + * @param val Pointer to store the extracted size_t value. + * + * @return Pointer to the updated buffer position after reading the value, + * or NULL if the buffer does not contain enough data. + */ +static uint8_t *consume_size_t(const uint8_t *buf, size_t *len, size_t *val) +{ + if (*len < sizeof(size_t)) + return NULL; + *val = *buf; + *len -= sizeof(size_t); + return (uint8_t *)buf + sizeof(size_t); +} + +/** + * @brief Selects a key type and size from a buffer. + * + * This function reads a key size value from the buffer, determines the + * corresponding key type and length, and updates the buffer pointer + * accordingly. If `only_valid` is set, it restricts selection to valid key + * sizes; otherwise, it includes some invalid sizes for testing. + * + * @param buf Pointer to the buffer pointer; updated after reading. + * @param len Pointer to the remaining buffer size; updated accordingly. + * @param keytype Pointer to store the selected key type string. + * @param keylen Pointer to store the selected key length. + * @param only_valid Flag to restrict selection to valid key sizes. + * + * @return 1 if a key type is successfully selected, 0 on failure. + */ +static int select_keytype_and_size(uint8_t **buf, size_t *len, + char **keytype, size_t *keylen, + int only_valid) +{ + uint16_t keysize; + uint16_t modulus = 6; + + /* + * Note: We don't really care about endianness here, we just want a random + * 16 bit value + */ + *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); + *len -= sizeof(uint16_t); + + if (*buf == NULL) + return 0; + + /* + * If `only_valid` is set, select only ML-DSA-44, ML-DSA-65, and ML-DSA-87. + * Otherwise, include some invalid sizes to trigger error paths. + */ + + if (only_valid) + modulus = 3; + + /* + * Note, keylens for valid values (cases 0-2) are taken based on input + * values from our unit tests + */ + switch (keysize % modulus) { + case 0: + *keytype = "ML-DSA-44"; + *keylen = ML_DSA_44_PUB_LEN; + break; + case 1: + *keytype = "ML-DSA-65"; + *keylen = ML_DSA_65_PUB_LEN; + break; + case 2: + *keytype = "ML-DSA-87"; + *keylen = ML_DSA_87_PUB_LEN; + break; + case 3: + /* select invalid alg */ + *keytype = "ML-DSA-33"; + *keylen = 33; + break; + case 4: + /* Select valid alg, but bogus size */ + *keytype = "ML-DSA-87"; + *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); + *len -= sizeof(uint16_t); + *keylen = (size_t)keysize; + *keylen %= ML_DSA_87_PUB_LEN; /* size to our key buffer */ + break; + default: + *keytype = NULL; + *keylen = 0; + break; + } + return 1; +} + +/** + * @brief Creates an ML-DSA raw key from a buffer. + * + * This function selects a key type and size from the buffer, generates a random + * key of the appropriate length, and creates either a public or private ML-DSA + * key using OpenSSL's EVP_PKEY interface. + * + * @param buf Pointer to the buffer pointer; updated after reading. + * @param len Pointer to the remaining buffer size; updated accordingly. + * @param key1 Pointer to store the generated EVP_PKEY key (public or private). + * @param key2 Unused parameter (reserved for future use). + * + * @note The generated key is allocated using OpenSSL's EVP_PKEY functions + * and should be freed appropriately using `EVP_PKEY_free()`. + */ +static void create_ml_dsa_raw_key(uint8_t **buf, size_t *len, + void **key1, void **key2) +{ + EVP_PKEY *pubkey; + char *keytype = NULL; + size_t keylen = 0; + /* MAX_ML_DSA_PRIV_LEN is longer of that and ML_DSA_87_PUB_LEN */ + uint8_t key[MAX_ML_DSA_PRIV_LEN]; + int pub = 0; + + if (!select_keytype_and_size(buf, len, &keytype, &keylen, 0)) + return; + + /* + * Select public or private key creation based on the low order bit of the + * next buffer value. + * Note that keylen as returned from select_keytype_and_size is a public key + * length, so make the adjustment to private key lengths here. + */ + if ((*buf)[0] & 0x1) { + pub = 1; + } else { + switch (keylen) { + case (ML_DSA_44_PUB_LEN): + keylen = ML_DSA_44_PRIV_LEN; + break; + case (ML_DSA_65_PUB_LEN): + keylen = ML_DSA_65_PRIV_LEN; + break; + case (ML_DSA_87_PUB_LEN): + keylen = ML_DSA_87_PRIV_LEN; + break; + default: + return; + } + } + + /* + * libfuzzer provides by default up to 4096 bit input buffers, but it's + * typically much less (between 1 and 100 bytes) so use RAND_bytes here + * instead + */ + if (!RAND_bytes(key, keylen)) + return; + + /* + * Try to generate either a raw public or private key using random data + * Because the input is completely random, it's effectively certain this + * operation will fail, but it will still exercise the code paths below, + * which is what we want the fuzzer to do + */ + if (pub == 1) + pubkey = EVP_PKEY_new_raw_public_key_ex(NULL, keytype, NULL, key, keylen); + else + pubkey = EVP_PKEY_new_raw_private_key_ex(NULL, keytype, NULL, key, keylen); + + *key1 = pubkey; + return; +} + +static int keygen_ml_dsa_real_key_helper(uint8_t **buf, size_t *len, + EVP_PKEY **key) +{ + char *keytype = NULL; + size_t keylen = 0; + EVP_PKEY_CTX *ctx = NULL; + int ret = 0; + + /* + * Only generate valid key types and lengths. Note, no adjustment is made to + * keylen here, as the provider is responsible for selecting the keys and + * sizes for us during the EVP_PKEY_keygen call + */ + if (!select_keytype_and_size(buf, len, &keytype, &keylen, 1)) + goto err; + + ctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, NULL); + if (!ctx) { + fprintf(stderr, "Failed to generate ctx\n"); + goto err; + } + + if (!EVP_PKEY_keygen_init(ctx)) { + fprintf(stderr, "Failed to init keygen ctx\n"); + goto err; + } + + *key = EVP_PKEY_new(); + if (*key == NULL) + goto err; + + if (!EVP_PKEY_generate(ctx, key)) { + fprintf(stderr, "Failed to generate new real key\n"); + goto err; + } + + ret = 1; +err: + EVP_PKEY_CTX_free(ctx); + return ret; +} + +/** + * @brief Generates a valid ML-DSA key using OpenSSL. + * + * This function selects a valid ML-DSA key type and size from the buffer, + * initializes an OpenSSL EVP_PKEY context, and generates a cryptographic key + * accordingly. + * + * @param buf Pointer to the buffer pointer; updated after reading. + * @param len Pointer to the remaining buffer size; updated accordingly. + * @param key1 Pointer to store the first generated EVP_PKEY key. + * @param key2 Pointer to store the second generated EVP_PKEY key. + * + * @note The generated key is allocated using OpenSSL's EVP_PKEY functions + * and should be freed using `EVP_PKEY_free()`. + */ +static void keygen_ml_dsa_real_key(uint8_t **buf, size_t *len, + void **key1, void **key2) +{ + if (!keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key1) + || !keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key2)) + fprintf(stderr, "Unable to generate valid keys"); +} + +/** + * @brief Performs key sign and verify using an EVP_PKEY. + * + * This function generates a random key, signs random data using the provided + * public key, then verifies it. It makes use of OpenSSL's EVP_PKEY API for + * encryption and decryption. + * + * @param[out] buf Unused output buffer (reserved for future use). + * @param[out] len Unused length parameter (reserved for future use). + * @param[in] key1 Pointer to an EVP_PKEY structure used for key operations. + * @param[in] in2 Unused input parameter (reserved for future use). + * @param[out] out1 Unused output parameter (reserved for future use). + * @param[out] out2 Unused output parameter (reserved for future use). + */ +static void ml_dsa_sign_verify(uint8_t **buf, size_t *len, void *key1, + void *in2, void **out1, void **out2) +{ + EVP_PKEY *key = (EVP_PKEY *)key1; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL); + EVP_SIGNATURE *sig_alg = NULL; + unsigned char *sig = NULL; + size_t sig_len = 0, tbslen; + unsigned char *tbs = NULL; + /* Ownership of alg is retained by the pkey object */ + const char *alg = EVP_PKEY_get0_type_name(key); + const OSSL_PARAM params[] = { + OSSL_PARAM_octet_string("context-string", + (unsigned char *)"A context string", 16), + OSSL_PARAM_END + }; + + if (!consume_size_t(*buf, len, &tbslen)) { + fprintf(stderr, "Failed to set tbslen"); + goto err; + } + /* Keep tbslen within a reasonable value we can malloc */ + tbslen = (tbslen % 2048) + 1; + + if ((tbs = OPENSSL_malloc(tbslen)) == NULL + || ctx == NULL || alg == NULL + || !RAND_bytes_ex(NULL, tbs, tbslen, 0)) { + fprintf(stderr, "Failed basic initialization\n"); + goto err; + } + + /* + * Because ML-DSA is fundamentally a one-shot algorithm like "pure" Ed25519 + * and Ed448, we don't have any immediate plans to implement intermediate + * sign/verify functions. Therefore, we only test the one-shot functions. + */ + + if ((sig_alg = EVP_SIGNATURE_fetch(NULL, alg, NULL)) == NULL + || EVP_PKEY_sign_message_init(ctx, sig_alg, params) <= 0 + || EVP_PKEY_sign(ctx, NULL, &sig_len, tbs, tbslen) <= 0 + || (sig = OPENSSL_zalloc(sig_len)) == NULL + || EVP_PKEY_sign(ctx, sig, &sig_len, tbs, tbslen) <= 0) { + fprintf(stderr, "Failed to sign message\n"); + goto err; + } + + /* Verify signature */ + EVP_PKEY_CTX_free(ctx); + ctx = NULL; + + if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL)) == NULL + || EVP_PKEY_verify_message_init(ctx, sig_alg, params) <= 0 + || EVP_PKEY_verify(ctx, sig, sig_len, tbs, tbslen) <= 0) { + fprintf(stderr, "Failed to verify message\n"); + goto err; + } + +err: + OPENSSL_free(tbs); + EVP_PKEY_CTX_free(ctx); + EVP_SIGNATURE_free(sig_alg); + OPENSSL_free(sig); + return; +} + +/** + * @brief Performs key sign and verify using an EVP_PKEY. + * + * This function generates a random key, signs random data using the provided + * public key, then verifies it. It makes use of OpenSSL's EVP_PKEY API for + * encryption and decryption. + * + * @param[out] buf Unused output buffer (reserved for future use). + * @param[out] len Unused length parameter (reserved for future use). + * @param[in] key1 Pointer to an EVP_PKEY structure used for key operations. + * @param[in] in2 Unused input parameter (reserved for future use). + * @param[out] out1 Unused output parameter (reserved for future use). + * @param[out] out2 Unused output parameter (reserved for future use). + */ +static void ml_dsa_digest_sign_verify(uint8_t **buf, size_t *len, void *key1, + void *in2, void **out1, void **out2) +{ + EVP_PKEY *key = (EVP_PKEY *)key1; + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + EVP_SIGNATURE *sig_alg = NULL; + unsigned char *sig = NULL; + size_t sig_len, tbslen; + unsigned char *tbs = NULL; + const OSSL_PARAM params[] = { + OSSL_PARAM_octet_string("context-string", + (unsigned char *)"A context string", 16), + OSSL_PARAM_END + }; + + if (!consume_size_t(*buf, len, &tbslen)) { + fprintf(stderr, "Failed to set tbslen"); + goto err; + } + /* Keep tbslen within a reasonable value we can malloc */ + tbslen = (tbslen % 2048) + 1; + + if ((tbs = OPENSSL_malloc(tbslen)) == NULL + || ctx == NULL + || !RAND_bytes_ex(NULL, tbs, tbslen, 0)) { + fprintf(stderr, "Failed basic initialization\n"); + goto err; + } + + /* + * Because ML-DSA is fundamentally a one-shot algorithm like "pure" Ed25519 + * and Ed448, we don't have any immediate plans to implement intermediate + * sign/verify functions. Therefore, we only test the one-shot functions. + */ + + if (!EVP_DigestSignInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, params) + || EVP_DigestSign(ctx, NULL, &sig_len, tbs, tbslen) <= 0 + || (sig = OPENSSL_malloc(sig_len)) == NULL + || EVP_DigestSign(ctx, sig, &sig_len, tbs, tbslen) <= 0) { + fprintf(stderr, "Failed to sign digest with EVP_DigestSign\n"); + goto err; + } + + /* Verify signature */ + EVP_MD_CTX_free(ctx); + ctx = NULL; + + if ((ctx = EVP_MD_CTX_new()) == NULL + || EVP_DigestVerifyInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, + params) <= 0 + || EVP_DigestVerify(ctx, sig, sig_len, tbs, tbslen) <= 0) { + fprintf(stderr, "Failed to verify digest with EVP_DigestVerify\n"); + goto err; + } + +err: + OPENSSL_free(tbs); + EVP_MD_CTX_free(ctx); + EVP_SIGNATURE_free(sig_alg); + OPENSSL_free(sig); + return; +} + +/** + * @brief Exports and imports an ML-DSA key. + * + * This function extracts key material from the given key (`key1`), exports it + * as parameters, and then attempts to reconstruct a new key from those + * parameters. It uses OpenSSL's `EVP_PKEY_todata()` and `EVP_PKEY_fromdata()` + * functions for this process. + * + * @param[out] buf Unused output buffer (reserved for future use). + * @param[out] len Unused output length (reserved for future use). + * @param[in] key1 The key to be exported and imported. + * @param[in] key2 Unused input key (reserved for future use). + * @param[out] out1 Unused output parameter (reserved for future use). + * @param[out] out2 Unused output parameter (reserved for future use). + * + * @note If any step in the export-import process fails, the function + * logs an error and cleans up allocated resources. + */ +static void ml_dsa_export_import(uint8_t **buf, size_t *len, void *key1, + void *key2, void **out1, void **out2) +{ + EVP_PKEY *alice = (EVP_PKEY *)key1; + EVP_PKEY *new_key = NULL; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + + if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, ¶ms)) { + fprintf(stderr, "Failed todata\n"); + goto err; + } + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL); + if (ctx == NULL) { + fprintf(stderr, "Failed new ctx\n"); + goto err; + } + + if (!EVP_PKEY_fromdata(ctx, &new_key, EVP_PKEY_KEYPAIR, params)) { + fprintf(stderr, "Failed fromdata\n"); + goto err; + } + +err: + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(new_key); + OSSL_PARAM_free(params); +} + +/** + * @brief Compares two cryptographic keys and performs equality checks. + * + * This function takes in two cryptographic keys, casts them to `EVP_PKEY` + * structures, and checks their equality using `EVP_PKEY_eq()`. The purpose of + * `buf`, `len`, `out1`, and `out2` parameters is not clear from the function's + * current implementation. + * + * @param buf Unused parameter (purpose unclear). + * @param len Unused parameter (purpose unclear). + * @param key1 First key, expected to be an `EVP_PKEY *`. + * @param key2 Second key, expected to be an `EVP_PKEY *`. + * @param out1 Unused parameter (purpose unclear). + * @param out2 Unused parameter (purpose unclear). + */ +static void ml_dsa_compare(uint8_t **buf, size_t *len, void *key1, + void *key2, void **out1, void **out2) +{ + EVP_PKEY *alice = (EVP_PKEY *)key1; + EVP_PKEY *bob = (EVP_PKEY *)key2; + + EVP_PKEY_eq(alice, alice); + EVP_PKEY_eq(alice, bob); +} + +/** + * @brief Frees allocated ML-DSA keys. + * + * This function releases memory associated with up to four EVP_PKEY objects by + * calling `EVP_PKEY_free()` on each provided key. + * + * @param key1 Pointer to the first key to be freed. + * @param key2 Pointer to the second key to be freed. + * @param key3 Pointer to the third key to be freed. + * @param key4 Pointer to the fourth key to be freed. + * + * @note This function assumes that each key is either a valid EVP_PKEY + * object or NULL. Passing NULL is safe and has no effect. + */ +static void cleanup_ml_dsa_keys(void *key1, void *key2, + void *key3, void *key4) +{ + EVP_PKEY_free((EVP_PKEY *)key1); + EVP_PKEY_free((EVP_PKEY *)key2); + EVP_PKEY_free((EVP_PKEY *)key3); + EVP_PKEY_free((EVP_PKEY *)key4); +} + +/** + * @brief Represents an operation table entry for cryptographic operations. + * + * This structure defines a table entry containing function pointers for setting + * up, executing, and cleaning up cryptographic operations, along with + * associated metadata such as a name and description. + * + * @struct op_table_entry + */ +struct op_table_entry { + /** Name of the operation. */ + char *name; + + /** Description of the operation. */ + char *desc; + + /** + * @brief Function pointer for setting up the operation. + * + * @param buf Pointer to the buffer pointer; may be updated. + * @param len Pointer to the remaining buffer size; may be updated. + * @param out1 Pointer to store the first output of the setup function. + * @param out2 Pointer to store the second output of the setup function. + */ + void (*setup)(uint8_t **buf, size_t *len, void **out1, void **out2); + + /** + * @brief Function pointer for executing the operation. + * + * @param buf Pointer to the buffer pointer; may be updated. + * @param len Pointer to the remaining buffer size; may be updated. + * @param in1 First input parameter for the operation. + * @param in2 Second input parameter for the operation. + * @param out1 Pointer to store the first output of the operation. + * @param out2 Pointer to store the second output of the operation. + */ + void (*doit)(uint8_t **buf, size_t *len, void *in1, void *in2, + void **out1, void **out2); + + /** + * @brief Function pointer for cleaning up after the operation. + * + * @param in1 First input parameter to be cleaned up. + * @param in2 Second input parameter to be cleaned up. + * @param out1 First output parameter to be cleaned up. + * @param out2 Second output parameter to be cleaned up. + */ + void (*cleanup)(void *in1, void *in2, void *out1, void *out2); +}; + +static struct op_table_entry ops[] = { + { + "Generate ML-DSA raw key", + "Try generate a raw keypair using random data. Usually fails", + create_ml_dsa_raw_key, + NULL, + cleanup_ml_dsa_keys + }, { + "Generate ML-DSA keypair, using EVP_PKEY_keygen", + "Generates a real ML-DSA keypair, should always work", + keygen_ml_dsa_real_key, + NULL, + cleanup_ml_dsa_keys + }, { + "Do a sign/verify operation on a key", + "Generate key, sign random data, verify it, should work", + keygen_ml_dsa_real_key, + ml_dsa_sign_verify, + cleanup_ml_dsa_keys + }, { + "Do a digest sign/verify operation on a key", + "Generate key, digest sign random data, verify it, should work", + keygen_ml_dsa_real_key, + ml_dsa_digest_sign_verify, + cleanup_ml_dsa_keys + }, { + "Do an export/import of key data", + "Exercise EVP_PKEY_todata/fromdata", + keygen_ml_dsa_real_key, + ml_dsa_export_import, + cleanup_ml_dsa_keys + }, { + "Compare keys for equality", + "Compare key1/key1 and key1/key2 for equality", + keygen_ml_dsa_real_key, + ml_dsa_compare, + cleanup_ml_dsa_keys + } +}; + +int FuzzerInitialize(int *argc, char ***argv) +{ + return 0; +} + +/** + * @brief Processes a fuzzing input by selecting and executing an operation. + * + * This function interprets the first byte of the input buffer to determine an + * operation to execute. It then follows a setup, execution, and cleanup + * sequence based on the selected operation. + * + * @param buf Pointer to the input buffer. + * @param len Length of the input buffer. + * + * @return 0 on successful execution, -1 if the input is too short. + * + * @note The function requires at least 32 bytes in the buffer to proceed. + * It utilizes the `ops` operation table to dynamically determine and + * execute the selected operation. + */ +int FuzzerTestOneInput(const uint8_t *buf, size_t len) +{ + uint8_t operation; + uint8_t *buffer_cursor; + void *in1 = NULL, *in2 = NULL; + void *out1 = NULL, *out2 = NULL; + + if (len < 32) + return -1; + + /* Get the first byte of the buffer to tell us what operation to perform */ + buffer_cursor = consume_uint8_t(buf, &len, &operation); + if (buffer_cursor == NULL) + return -1; + + /* Adjust for operational array size */ + operation %= OSSL_NELEM(ops); + + /* And run our setup/doit/cleanup sequence */ + if (ops[operation].setup != NULL) + ops[operation].setup(&buffer_cursor, &len, &in1, &in2); + if (ops[operation].doit != NULL) + ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2); + if (ops[operation].cleanup != NULL) + ops[operation].cleanup(in1, in2, out1, out2); + + return 0; +} + +void FuzzerCleanup(void) +{ + OPENSSL_cleanup(); +} diff --git a/test/recipes/99-test_fuzz_ml-dsa.t b/test/recipes/99-test_fuzz_ml-dsa.t new file mode 100644 index 00000000000..d17595f97ad --- /dev/null +++ b/test/recipes/99-test_fuzz_ml-dsa.t @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved. +# +# Licensed under the Apache License 2.0 (the "License"). You may not use +# this file except in compliance with the License. You can obtain a copy +# in the file LICENSE in the source distribution or at +# https://www.openssl.org/source/license.html + +use strict; +use warnings; + +use OpenSSL::Test qw/:DEFAULT srctop_file/; +use OpenSSL::Test::Utils; + +my $fuzzer = "ml-dsa"; +setup("test_fuzz_${fuzzer}"); + +plan skip_all => "This test requires ML-DSA support" + if disabled("ml-dsa"); + +plan tests => 2; # one more due to below require_ok(...) + +require_ok(srctop_file('test','recipes','fuzz.pl')); + +fuzz_ok($fuzzer);