/*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)426<= 0427|| EVP_DigestVerify(ctx, sig, sig_len, tbs, tbslen) <= 0) {428fprintf(stderr, "Failed to verify digest with EVP_DigestVerify\n");429goto err;430}431432err:433OPENSSL_free(tbs);434EVP_MD_CTX_free(ctx);435EVP_SIGNATURE_free(sig_alg);436OPENSSL_free(sig);437return;438}439440/**441* @brief Exports and imports an ML-DSA key.442*443* This function extracts key material from the given key (`key1`), exports it444* as parameters, and then attempts to reconstruct a new key from those445* parameters. It uses OpenSSL's `EVP_PKEY_todata()` and `EVP_PKEY_fromdata()`446* functions for this process.447*448* @param[out] buf Unused output buffer (reserved for future use).449* @param[out] len Unused output length (reserved for future use).450* @param[in] key1 The key to be exported and imported.451* @param[in] key2 Unused input key (reserved for future use).452* @param[out] out1 Unused output parameter (reserved for future use).453* @param[out] out2 Unused output parameter (reserved for future use).454*455* @note If any step in the export-import process fails, the function456* logs an error and cleans up allocated resources.457*/458static void ml_dsa_export_import(uint8_t **buf, size_t *len, void *key1,459void *key2, void **out1, void **out2)460{461EVP_PKEY *alice = (EVP_PKEY *)key1;462EVP_PKEY *new_key = NULL;463EVP_PKEY_CTX *ctx = NULL;464OSSL_PARAM *params = NULL;465466if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, ¶ms)) {467fprintf(stderr, "Failed todata\n");468goto err;469}470471ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL);472if (ctx == NULL) {473fprintf(stderr, "Failed new ctx\n");474goto err;475}476477if (!EVP_PKEY_fromdata(ctx, &new_key, EVP_PKEY_KEYPAIR, params)) {478fprintf(stderr, "Failed fromdata\n");479goto err;480}481482err:483EVP_PKEY_CTX_free(ctx);484EVP_PKEY_free(new_key);485OSSL_PARAM_free(params);486}487488/**489* @brief Compares two cryptographic keys and performs equality checks.490*491* This function takes in two cryptographic keys, casts them to `EVP_PKEY`492* structures, and checks their equality using `EVP_PKEY_eq()`. The purpose of493* `buf`, `len`, `out1`, and `out2` parameters is not clear from the function's494* current implementation.495*496* @param buf Unused parameter (purpose unclear).497* @param len Unused parameter (purpose unclear).498* @param key1 First key, expected to be an `EVP_PKEY *`.499* @param key2 Second key, expected to be an `EVP_PKEY *`.500* @param out1 Unused parameter (purpose unclear).501* @param out2 Unused parameter (purpose unclear).502*/503static void ml_dsa_compare(uint8_t **buf, size_t *len, void *key1,504void *key2, void **out1, void **out2)505{506EVP_PKEY *alice = (EVP_PKEY *)key1;507EVP_PKEY *bob = (EVP_PKEY *)key2;508509EVP_PKEY_eq(alice, alice);510EVP_PKEY_eq(alice, bob);511}512513/**514* @brief Frees allocated ML-DSA keys.515*516* This function releases memory associated with up to four EVP_PKEY objects by517* calling `EVP_PKEY_free()` on each provided key.518*519* @param key1 Pointer to the first key to be freed.520* @param key2 Pointer to the second key to be freed.521* @param key3 Pointer to the third key to be freed.522* @param key4 Pointer to the fourth key to be freed.523*524* @note This function assumes that each key is either a valid EVP_PKEY525* object or NULL. Passing NULL is safe and has no effect.526*/527static void cleanup_ml_dsa_keys(void *key1, void *key2,528void *key3, void *key4)529{530EVP_PKEY_free((EVP_PKEY *)key1);531EVP_PKEY_free((EVP_PKEY *)key2);532EVP_PKEY_free((EVP_PKEY *)key3);533EVP_PKEY_free((EVP_PKEY *)key4);534}535536/**537* @brief Represents an operation table entry for cryptographic operations.538*539* This structure defines a table entry containing function pointers for setting540* up, executing, and cleaning up cryptographic operations, along with541* associated metadata such as a name and description.542*543* @struct op_table_entry544*/545struct op_table_entry {546/** Name of the operation. */547char *name;548549/** Description of the operation. */550char *desc;551552/**553* @brief Function pointer for setting up the operation.554*555* @param buf Pointer to the buffer pointer; may be updated.556* @param len Pointer to the remaining buffer size; may be updated.557* @param out1 Pointer to store the first output of the setup function.558* @param out2 Pointer to store the second output of the setup function.559*/560void (*setup)(uint8_t **buf, size_t *len, void **out1, void **out2);561562/**563* @brief Function pointer for executing the operation.564*565* @param buf Pointer to the buffer pointer; may be updated.566* @param len Pointer to the remaining buffer size; may be updated.567* @param in1 First input parameter for the operation.568* @param in2 Second input parameter for the operation.569* @param out1 Pointer to store the first output of the operation.570* @param out2 Pointer to store the second output of the operation.571*/572void (*doit)(uint8_t **buf, size_t *len, void *in1, void *in2,573void **out1, void **out2);574575/**576* @brief Function pointer for cleaning up after the operation.577*578* @param in1 First input parameter to be cleaned up.579* @param in2 Second input parameter to be cleaned up.580* @param out1 First output parameter to be cleaned up.581* @param out2 Second output parameter to be cleaned up.582*/583void (*cleanup)(void *in1, void *in2, void *out1, void *out2);584};585586static struct op_table_entry ops[] = {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_keys },592{ "Generate ML-DSA keypair, using EVP_PKEY_keygen",593"Generates a real ML-DSA keypair, should always work",594keygen_ml_dsa_real_key,595NULL,596cleanup_ml_dsa_keys },597{ "Do a sign/verify operation on a key",598"Generate key, sign random data, verify it, should work",599keygen_ml_dsa_real_key,600ml_dsa_sign_verify,601cleanup_ml_dsa_keys },602{ "Do a digest sign/verify operation on a key",603"Generate key, digest sign random data, verify it, should work",604keygen_ml_dsa_real_key,605ml_dsa_digest_sign_verify,606cleanup_ml_dsa_keys },607{ "Do an export/import of key data",608"Exercise EVP_PKEY_todata/fromdata",609keygen_ml_dsa_real_key,610ml_dsa_export_import,611cleanup_ml_dsa_keys },612{ "Compare keys for equality",613"Compare key1/key1 and key1/key2 for equality",614keygen_ml_dsa_real_key,615ml_dsa_compare,616cleanup_ml_dsa_keys }617};618619int FuzzerInitialize(int *argc, char ***argv)620{621return 0;622}623624/**625* @brief Processes a fuzzing input by selecting and executing an operation.626*627* This function interprets the first byte of the input buffer to determine an628* operation to execute. It then follows a setup, execution, and cleanup629* sequence based on the selected operation.630*631* @param buf Pointer to the input buffer.632* @param len Length of the input buffer.633*634* @return 0 on successful execution, -1 if the input is too short.635*636* @note The function requires at least 32 bytes in the buffer to proceed.637* It utilizes the `ops` operation table to dynamically determine and638* execute the selected operation.639*/640int FuzzerTestOneInput(const uint8_t *buf, size_t len)641{642uint8_t operation;643uint8_t *buffer_cursor;644void *in1 = NULL, *in2 = NULL;645void *out1 = NULL, *out2 = NULL;646647if (len < 32)648return -1;649650/* Get the first byte of the buffer to tell us what operation to perform */651buffer_cursor = consume_uint8_t(buf, &len, &operation);652if (buffer_cursor == NULL)653return -1;654655/* Adjust for operational array size */656operation %= OSSL_NELEM(ops);657658/* And run our setup/doit/cleanup sequence */659if (ops[operation].setup != NULL)660ops[operation].setup(&buffer_cursor, &len, &in1, &in2);661if (ops[operation].doit != NULL)662ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2);663if (ops[operation].cleanup != NULL)664ops[operation].cleanup(in1, in2, out1, out2);665666return 0;667}668669void FuzzerCleanup(void)670{671OPENSSL_cleanup();672}673674675