/*1* Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License");4* you may not use this file except in compliance with the License.5* You may obtain a copy of the License at6* https://www.openssl.org/source/license.html7* or in the file LICENSE in the source distribution.8*/910/* Test ML-DSA operation. */11#include <string.h>12#include <openssl/evp.h>13#include <openssl/err.h>14#include <openssl/rand.h>15#include <openssl/byteorder.h>16#include "internal/nelem.h"17#include "fuzzer.h"18#include "crypto/ml_dsa.h"1920/**21* @brief Consumes an 8-bit unsigned integer from a buffer.22*23* This function extracts an 8-bit unsigned integer from the provided buffer,24* updates the buffer pointer, and adjusts the remaining length.25*26* @param buf Pointer to the input buffer.27* @param len Pointer to the size of the remaining buffer; updated after consumption.28* @param val Pointer to store the extracted 8-bit value.29*30* @return Pointer to the updated buffer position after reading the value,31* or NULL if the buffer does not contain enough data.32*/33static uint8_t *consume_uint8_t(const uint8_t *buf, size_t *len, uint8_t *val)34{35if (*len < sizeof(uint8_t))36return NULL;37*val = *buf;38*len -= sizeof(uint8_t);39return (uint8_t *)buf + 1;40}4142/**43* @brief Consumes a size_t from a buffer.44*45* This function extracts a size_t from the provided buffer, updates the buffer46* pointer, and adjusts the remaining length.47*48* @param buf Pointer to the input buffer.49* @param len Pointer to the size of the remaining buffer; updated after consumption.50* @param val Pointer to store the extracted size_t value.51*52* @return Pointer to the updated buffer position after reading the value,53* or NULL if the buffer does not contain enough data.54*/55static uint8_t *consume_size_t(const uint8_t *buf, size_t *len, size_t *val)56{57if (*len < sizeof(size_t))58return NULL;59*val = *buf;60*len -= sizeof(size_t);61return (uint8_t *)buf + sizeof(size_t);62}6364/**65* @brief Selects a key type and size from a buffer.66*67* This function reads a key size value from the buffer, determines the68* corresponding key type and length, and updates the buffer pointer69* accordingly. If `only_valid` is set, it restricts selection to valid key70* sizes; otherwise, it includes some invalid sizes for testing.71*72* @param buf Pointer to the buffer pointer; updated after reading.73* @param len Pointer to the remaining buffer size; updated accordingly.74* @param keytype Pointer to store the selected key type string.75* @param keylen Pointer to store the selected key length.76* @param only_valid Flag to restrict selection to valid key sizes.77*78* @return 1 if a key type is successfully selected, 0 on failure.79*/80static int select_keytype_and_size(uint8_t **buf, size_t *len,81char **keytype, size_t *keylen,82int only_valid)83{84uint16_t keysize;85uint16_t modulus = 6;8687/*88* Note: We don't really care about endianness here, we just want a random89* 16 bit value90*/91*buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf);92*len -= sizeof(uint16_t);9394if (*buf == NULL)95return 0;9697/*98* If `only_valid` is set, select only ML-DSA-44, ML-DSA-65, and ML-DSA-87.99* Otherwise, include some invalid sizes to trigger error paths.100*/101102if (only_valid)103modulus = 3;104105/*106* Note, keylens for valid values (cases 0-2) are taken based on input107* values from our unit tests108*/109switch (keysize % modulus) {110case 0:111*keytype = "ML-DSA-44";112*keylen = ML_DSA_44_PUB_LEN;113break;114case 1:115*keytype = "ML-DSA-65";116*keylen = ML_DSA_65_PUB_LEN;117break;118case 2:119*keytype = "ML-DSA-87";120*keylen = ML_DSA_87_PUB_LEN;121break;122case 3:123/* select invalid alg */124*keytype = "ML-DSA-33";125*keylen = 33;126break;127case 4:128/* Select valid alg, but bogus size */129*keytype = "ML-DSA-87";130*buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf);131*len -= sizeof(uint16_t);132*keylen = (size_t)keysize;133*keylen %= ML_DSA_87_PUB_LEN; /* size to our key buffer */134break;135default:136*keytype = NULL;137*keylen = 0;138break;139}140return 1;141}142143/**144* @brief Creates an ML-DSA raw key from a buffer.145*146* This function selects a key type and size from the buffer, generates a random147* key of the appropriate length, and creates either a public or private ML-DSA148* key using OpenSSL's EVP_PKEY interface.149*150* @param buf Pointer to the buffer pointer; updated after reading.151* @param len Pointer to the remaining buffer size; updated accordingly.152* @param key1 Pointer to store the generated EVP_PKEY key (public or private).153* @param key2 Unused parameter (reserved for future use).154*155* @note The generated key is allocated using OpenSSL's EVP_PKEY functions156* and should be freed appropriately using `EVP_PKEY_free()`.157*/158static void create_ml_dsa_raw_key(uint8_t **buf, size_t *len,159void **key1, void **key2)160{161EVP_PKEY *pubkey;162char *keytype = NULL;163size_t keylen = 0;164/* MAX_ML_DSA_PRIV_LEN is longer of that and ML_DSA_87_PUB_LEN */165uint8_t key[MAX_ML_DSA_PRIV_LEN];166int pub = 0;167168if (!select_keytype_and_size(buf, len, &keytype, &keylen, 0))169return;170171/*172* Select public or private key creation based on the low order bit of the173* next buffer value.174* Note that keylen as returned from select_keytype_and_size is a public key175* length, so make the adjustment to private key lengths here.176*/177if ((*buf)[0] & 0x1) {178pub = 1;179} else {180switch (keylen) {181case (ML_DSA_44_PUB_LEN):182keylen = ML_DSA_44_PRIV_LEN;183break;184case (ML_DSA_65_PUB_LEN):185keylen = ML_DSA_65_PRIV_LEN;186break;187case (ML_DSA_87_PUB_LEN):188keylen = ML_DSA_87_PRIV_LEN;189break;190default:191return;192}193}194195/*196* libfuzzer provides by default up to 4096 bit input buffers, but it's197* typically much less (between 1 and 100 bytes) so use RAND_bytes here198* instead199*/200if (!RAND_bytes(key, keylen))201return;202203/*204* Try to generate either a raw public or private key using random data205* Because the input is completely random, it's effectively certain this206* operation will fail, but it will still exercise the code paths below,207* which is what we want the fuzzer to do208*/209if (pub == 1)210pubkey = EVP_PKEY_new_raw_public_key_ex(NULL, keytype, NULL, key, keylen);211else212pubkey = EVP_PKEY_new_raw_private_key_ex(NULL, keytype, NULL, key, keylen);213214*key1 = pubkey;215return;216}217218static int keygen_ml_dsa_real_key_helper(uint8_t **buf, size_t *len,219EVP_PKEY **key)220{221char *keytype = NULL;222size_t keylen = 0;223EVP_PKEY_CTX *ctx = NULL;224int ret = 0;225226/*227* Only generate valid key types and lengths. Note, no adjustment is made to228* keylen here, as the provider is responsible for selecting the keys and229* sizes for us during the EVP_PKEY_keygen call230*/231if (!select_keytype_and_size(buf, len, &keytype, &keylen, 1))232goto err;233234ctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, NULL);235if (!ctx) {236fprintf(stderr, "Failed to generate ctx\n");237goto err;238}239240if (!EVP_PKEY_keygen_init(ctx)) {241fprintf(stderr, "Failed to init keygen ctx\n");242goto err;243}244245*key = EVP_PKEY_new();246if (*key == NULL)247goto err;248249if (!EVP_PKEY_generate(ctx, key)) {250fprintf(stderr, "Failed to generate new real key\n");251goto err;252}253254ret = 1;255err:256EVP_PKEY_CTX_free(ctx);257return ret;258}259260/**261* @brief Generates a valid ML-DSA key using OpenSSL.262*263* This function selects a valid ML-DSA key type and size from the buffer,264* initializes an OpenSSL EVP_PKEY context, and generates a cryptographic key265* accordingly.266*267* @param buf Pointer to the buffer pointer; updated after reading.268* @param len Pointer to the remaining buffer size; updated accordingly.269* @param key1 Pointer to store the first generated EVP_PKEY key.270* @param key2 Pointer to store the second generated EVP_PKEY key.271*272* @note The generated key is allocated using OpenSSL's EVP_PKEY functions273* and should be freed using `EVP_PKEY_free()`.274*/275static void keygen_ml_dsa_real_key(uint8_t **buf, size_t *len,276void **key1, void **key2)277{278if (!keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key1)279|| !keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key2))280fprintf(stderr, "Unable to generate valid keys");281}282283/**284* @brief Performs key sign and verify using an EVP_PKEY.285*286* This function generates a random key, signs random data using the provided287* public key, then verifies it. It makes use of OpenSSL's EVP_PKEY API for288* encryption and decryption.289*290* @param[out] buf Unused output buffer (reserved for future use).291* @param[out] len Unused length parameter (reserved for future use).292* @param[in] key1 Pointer to an EVP_PKEY structure used for key operations.293* @param[in] in2 Unused input parameter (reserved for future use).294* @param[out] out1 Unused output parameter (reserved for future use).295* @param[out] out2 Unused output parameter (reserved for future use).296*/297static void ml_dsa_sign_verify(uint8_t **buf, size_t *len, void *key1,298void *in2, void **out1, void **out2)299{300EVP_PKEY *key = (EVP_PKEY *)key1;301EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL);302EVP_SIGNATURE *sig_alg = NULL;303unsigned char *sig = NULL;304size_t sig_len = 0, tbslen;305unsigned char *tbs = NULL;306/* Ownership of alg is retained by the pkey object */307const char *alg = EVP_PKEY_get0_type_name(key);308const OSSL_PARAM params[] = {309OSSL_PARAM_octet_string("context-string",310(unsigned char *)"A context string", 16),311OSSL_PARAM_END312};313314if (!consume_size_t(*buf, len, &tbslen)) {315fprintf(stderr, "Failed to set tbslen");316goto err;317}318/* Keep tbslen within a reasonable value we can malloc */319tbslen = (tbslen % 2048) + 1;320321if ((tbs = OPENSSL_malloc(tbslen)) == NULL322|| ctx == NULL || alg == NULL323|| !RAND_bytes_ex(NULL, tbs, tbslen, 0)) {324fprintf(stderr, "Failed basic initialization\n");325goto err;326}327328/*329* Because ML-DSA is fundamentally a one-shot algorithm like "pure" Ed25519330* and Ed448, we don't have any immediate plans to implement intermediate331* sign/verify functions. Therefore, we only test the one-shot functions.332*/333334if ((sig_alg = EVP_SIGNATURE_fetch(NULL, alg, NULL)) == NULL335|| EVP_PKEY_sign_message_init(ctx, sig_alg, params) <= 0336|| EVP_PKEY_sign(ctx, NULL, &sig_len, tbs, tbslen) <= 0337|| (sig = OPENSSL_zalloc(sig_len)) == NULL338|| EVP_PKEY_sign(ctx, sig, &sig_len, tbs, tbslen) <= 0) {339fprintf(stderr, "Failed to sign message\n");340goto err;341}342343/* Verify signature */344EVP_PKEY_CTX_free(ctx);345ctx = NULL;346347if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL)) == NULL348|| EVP_PKEY_verify_message_init(ctx, sig_alg, params) <= 0349|| EVP_PKEY_verify(ctx, sig, sig_len, tbs, tbslen) <= 0) {350fprintf(stderr, "Failed to verify message\n");351goto err;352}353354err:355OPENSSL_free(tbs);356EVP_PKEY_CTX_free(ctx);357EVP_SIGNATURE_free(sig_alg);358OPENSSL_free(sig);359return;360}361362/**363* @brief Performs key sign and verify using an EVP_PKEY.364*365* This function generates a random key, signs random data using the provided366* public key, then verifies it. It makes use of OpenSSL's EVP_PKEY API for367* encryption and decryption.368*369* @param[out] buf Unused output buffer (reserved for future use).370* @param[out] len Unused length parameter (reserved for future use).371* @param[in] key1 Pointer to an EVP_PKEY structure used for key operations.372* @param[in] in2 Unused input parameter (reserved for future use).373* @param[out] out1 Unused output parameter (reserved for future use).374* @param[out] out2 Unused output parameter (reserved for future use).375*/376static void ml_dsa_digest_sign_verify(uint8_t **buf, size_t *len, void *key1,377void *in2, void **out1, void **out2)378{379EVP_PKEY *key = (EVP_PKEY *)key1;380EVP_MD_CTX *ctx = EVP_MD_CTX_new();381EVP_SIGNATURE *sig_alg = NULL;382unsigned char *sig = NULL;383size_t sig_len, tbslen;384unsigned char *tbs = NULL;385const OSSL_PARAM params[] = {386OSSL_PARAM_octet_string("context-string",387(unsigned char *)"A context string", 16),388OSSL_PARAM_END389};390391if (!consume_size_t(*buf, len, &tbslen)) {392fprintf(stderr, "Failed to set tbslen");393goto err;394}395/* Keep tbslen within a reasonable value we can malloc */396tbslen = (tbslen % 2048) + 1;397398if ((tbs = OPENSSL_malloc(tbslen)) == NULL399|| ctx == NULL400|| !RAND_bytes_ex(NULL, tbs, tbslen, 0)) {401fprintf(stderr, "Failed basic initialization\n");402goto err;403}404405/*406* Because ML-DSA is fundamentally a one-shot algorithm like "pure" Ed25519407* and Ed448, we don't have any immediate plans to implement intermediate408* sign/verify functions. Therefore, we only test the one-shot functions.409*/410411if (!EVP_DigestSignInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, params)412|| EVP_DigestSign(ctx, NULL, &sig_len, tbs, tbslen) <= 0413|| (sig = OPENSSL_malloc(sig_len)) == NULL414|| EVP_DigestSign(ctx, sig, &sig_len, tbs, tbslen) <= 0) {415fprintf(stderr, "Failed to sign digest with EVP_DigestSign\n");416goto err;417}418419/* Verify signature */420EVP_MD_CTX_free(ctx);421ctx = NULL;422423if ((ctx = EVP_MD_CTX_new()) == NULL424|| EVP_DigestVerifyInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key,425params) <= 0426|| EVP_DigestVerify(ctx, sig, sig_len, tbs, tbslen) <= 0) {427fprintf(stderr, "Failed to verify digest with EVP_DigestVerify\n");428goto err;429}430431err:432OPENSSL_free(tbs);433EVP_MD_CTX_free(ctx);434EVP_SIGNATURE_free(sig_alg);435OPENSSL_free(sig);436return;437}438439/**440* @brief Exports and imports an ML-DSA key.441*442* This function extracts key material from the given key (`key1`), exports it443* as parameters, and then attempts to reconstruct a new key from those444* parameters. It uses OpenSSL's `EVP_PKEY_todata()` and `EVP_PKEY_fromdata()`445* functions for this process.446*447* @param[out] buf Unused output buffer (reserved for future use).448* @param[out] len Unused output length (reserved for future use).449* @param[in] key1 The key to be exported and imported.450* @param[in] key2 Unused input key (reserved for future use).451* @param[out] out1 Unused output parameter (reserved for future use).452* @param[out] out2 Unused output parameter (reserved for future use).453*454* @note If any step in the export-import process fails, the function455* logs an error and cleans up allocated resources.456*/457static void ml_dsa_export_import(uint8_t **buf, size_t *len, void *key1,458void *key2, void **out1, void **out2)459{460EVP_PKEY *alice = (EVP_PKEY *)key1;461EVP_PKEY *new_key = NULL;462EVP_PKEY_CTX *ctx = NULL;463OSSL_PARAM *params = NULL;464465if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, ¶ms)) {466fprintf(stderr, "Failed todata\n");467goto err;468}469470ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL);471if (ctx == NULL) {472fprintf(stderr, "Failed new ctx\n");473goto err;474}475476if (!EVP_PKEY_fromdata(ctx, &new_key, EVP_PKEY_KEYPAIR, params)) {477fprintf(stderr, "Failed fromdata\n");478goto err;479}480481err:482EVP_PKEY_CTX_free(ctx);483EVP_PKEY_free(new_key);484OSSL_PARAM_free(params);485}486487/**488* @brief Compares two cryptographic keys and performs equality checks.489*490* This function takes in two cryptographic keys, casts them to `EVP_PKEY`491* structures, and checks their equality using `EVP_PKEY_eq()`. The purpose of492* `buf`, `len`, `out1`, and `out2` parameters is not clear from the function's493* current implementation.494*495* @param buf Unused parameter (purpose unclear).496* @param len Unused parameter (purpose unclear).497* @param key1 First key, expected to be an `EVP_PKEY *`.498* @param key2 Second key, expected to be an `EVP_PKEY *`.499* @param out1 Unused parameter (purpose unclear).500* @param out2 Unused parameter (purpose unclear).501*/502static void ml_dsa_compare(uint8_t **buf, size_t *len, void *key1,503void *key2, void **out1, void **out2)504{505EVP_PKEY *alice = (EVP_PKEY *)key1;506EVP_PKEY *bob = (EVP_PKEY *)key2;507508EVP_PKEY_eq(alice, alice);509EVP_PKEY_eq(alice, bob);510}511512/**513* @brief Frees allocated ML-DSA keys.514*515* This function releases memory associated with up to four EVP_PKEY objects by516* calling `EVP_PKEY_free()` on each provided key.517*518* @param key1 Pointer to the first key to be freed.519* @param key2 Pointer to the second key to be freed.520* @param key3 Pointer to the third key to be freed.521* @param key4 Pointer to the fourth key to be freed.522*523* @note This function assumes that each key is either a valid EVP_PKEY524* object or NULL. Passing NULL is safe and has no effect.525*/526static void cleanup_ml_dsa_keys(void *key1, void *key2,527void *key3, void *key4)528{529EVP_PKEY_free((EVP_PKEY *)key1);530EVP_PKEY_free((EVP_PKEY *)key2);531EVP_PKEY_free((EVP_PKEY *)key3);532EVP_PKEY_free((EVP_PKEY *)key4);533}534535/**536* @brief Represents an operation table entry for cryptographic operations.537*538* This structure defines a table entry containing function pointers for setting539* up, executing, and cleaning up cryptographic operations, along with540* associated metadata such as a name and description.541*542* @struct op_table_entry543*/544struct op_table_entry {545/** Name of the operation. */546char *name;547548/** Description of the operation. */549char *desc;550551/**552* @brief Function pointer for setting up the operation.553*554* @param buf Pointer to the buffer pointer; may be updated.555* @param len Pointer to the remaining buffer size; may be updated.556* @param out1 Pointer to store the first output of the setup function.557* @param out2 Pointer to store the second output of the setup function.558*/559void (*setup)(uint8_t **buf, size_t *len, void **out1, void **out2);560561/**562* @brief Function pointer for executing the operation.563*564* @param buf Pointer to the buffer pointer; may be updated.565* @param len Pointer to the remaining buffer size; may be updated.566* @param in1 First input parameter for the operation.567* @param in2 Second input parameter for the operation.568* @param out1 Pointer to store the first output of the operation.569* @param out2 Pointer to store the second output of the operation.570*/571void (*doit)(uint8_t **buf, size_t *len, void *in1, void *in2,572void **out1, void **out2);573574/**575* @brief Function pointer for cleaning up after the operation.576*577* @param in1 First input parameter to be cleaned up.578* @param in2 Second input parameter to be cleaned up.579* @param out1 First output parameter to be cleaned up.580* @param out2 Second output parameter to be cleaned up.581*/582void (*cleanup)(void *in1, void *in2, void *out1, void *out2);583};584585static struct op_table_entry ops[] = {586{587"Generate ML-DSA raw key",588"Try generate a raw keypair using random data. Usually fails",589create_ml_dsa_raw_key,590NULL,591cleanup_ml_dsa_keys592}, {593"Generate ML-DSA keypair, using EVP_PKEY_keygen",594"Generates a real ML-DSA keypair, should always work",595keygen_ml_dsa_real_key,596NULL,597cleanup_ml_dsa_keys598}, {599"Do a sign/verify operation on a key",600"Generate key, sign random data, verify it, should work",601keygen_ml_dsa_real_key,602ml_dsa_sign_verify,603cleanup_ml_dsa_keys604}, {605"Do a digest sign/verify operation on a key",606"Generate key, digest sign random data, verify it, should work",607keygen_ml_dsa_real_key,608ml_dsa_digest_sign_verify,609cleanup_ml_dsa_keys610}, {611"Do an export/import of key data",612"Exercise EVP_PKEY_todata/fromdata",613keygen_ml_dsa_real_key,614ml_dsa_export_import,615cleanup_ml_dsa_keys616}, {617"Compare keys for equality",618"Compare key1/key1 and key1/key2 for equality",619keygen_ml_dsa_real_key,620ml_dsa_compare,621cleanup_ml_dsa_keys622}623};624625int FuzzerInitialize(int *argc, char ***argv)626{627return 0;628}629630/**631* @brief Processes a fuzzing input by selecting and executing an operation.632*633* This function interprets the first byte of the input buffer to determine an634* operation to execute. It then follows a setup, execution, and cleanup635* sequence based on the selected operation.636*637* @param buf Pointer to the input buffer.638* @param len Length of the input buffer.639*640* @return 0 on successful execution, -1 if the input is too short.641*642* @note The function requires at least 32 bytes in the buffer to proceed.643* It utilizes the `ops` operation table to dynamically determine and644* execute the selected operation.645*/646int FuzzerTestOneInput(const uint8_t *buf, size_t len)647{648uint8_t operation;649uint8_t *buffer_cursor;650void *in1 = NULL, *in2 = NULL;651void *out1 = NULL, *out2 = NULL;652653if (len < 32)654return -1;655656/* Get the first byte of the buffer to tell us what operation to perform */657buffer_cursor = consume_uint8_t(buf, &len, &operation);658if (buffer_cursor == NULL)659return -1;660661/* Adjust for operational array size */662operation %= OSSL_NELEM(ops);663664/* And run our setup/doit/cleanup sequence */665if (ops[operation].setup != NULL)666ops[operation].setup(&buffer_cursor, &len, &in1, &in2);667if (ops[operation].doit != NULL)668ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2);669if (ops[operation].cleanup != NULL)670ops[operation].cleanup(in1, in2, out1, out2);671672return 0;673}674675void FuzzerCleanup(void)676{677OPENSSL_cleanup();678}679680681