Coverage Report

Created: 2025-12-31 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl36/fuzz/ml-dsa.c
Line
Count
Source
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
894
{
36
894
    if (*len < sizeof(uint8_t))
37
0
        return NULL;
38
894
    *val = *buf;
39
894
    *len -= sizeof(uint8_t);
40
894
    return (uint8_t *)buf + 1;
41
894
}
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
594
{
58
594
    if (*len < sizeof(size_t))
59
0
        return NULL;
60
594
    *val = *buf;
61
594
    *len -= sizeof(size_t);
62
594
    return (uint8_t *)buf + sizeof(size_t);
63
594
}
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
1.56k
{
85
1.56k
    uint16_t keysize;
86
1.56k
    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
1.56k
    *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf);
93
1.56k
    *len -= sizeof(uint16_t);
94
95
1.56k
    if (*buf == NULL)
96
0
        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
1.56k
    if (only_valid)
104
1.33k
        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
1.56k
    switch (keysize % modulus) {
111
551
    case 0:
112
551
        *keytype = "ML-DSA-44";
113
551
        *keylen = ML_DSA_44_PUB_LEN;
114
551
        break;
115
465
    case 1:
116
465
        *keytype = "ML-DSA-65";
117
465
        *keylen = ML_DSA_65_PUB_LEN;
118
465
        break;
119
388
    case 2:
120
388
        *keytype = "ML-DSA-87";
121
388
        *keylen = ML_DSA_87_PUB_LEN;
122
388
        break;
123
3
    case 3:
124
        /* select invalid alg */
125
3
        *keytype = "ML-DSA-33";
126
3
        *keylen = 33;
127
3
        break;
128
151
    case 4:
129
        /* Select valid alg, but bogus size */
130
151
        *keytype = "ML-DSA-87";
131
151
        *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf);
132
151
        *len -= sizeof(uint16_t);
133
151
        *keylen = (size_t)keysize;
134
151
        *keylen %= ML_DSA_87_PUB_LEN; /* size to our key buffer */
135
151
        break;
136
3
    default:
137
3
        *keytype = NULL;
138
3
        *keylen = 0;
139
3
        break;
140
1.56k
    }
141
1.56k
    return 1;
142
1.56k
}
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
227
{
162
227
    EVP_PKEY *pubkey;
163
227
    char *keytype = NULL;
164
227
    size_t keylen = 0;
165
    /* MAX_ML_DSA_PRIV_LEN is longer of that and ML_DSA_87_PUB_LEN */
166
227
    uint8_t key[MAX_ML_DSA_PRIV_LEN];
167
227
    int pub = 0;
168
169
227
    if (!select_keytype_and_size(buf, len, &keytype, &keylen, 0))
170
0
        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
227
    if ((*buf)[0] & 0x1) {
179
53
        pub = 1;
180
174
    } else {
181
174
        switch (keylen) {
182
23
        case (ML_DSA_44_PUB_LEN):
183
23
            keylen = ML_DSA_44_PRIV_LEN;
184
23
            break;
185
34
        case (ML_DSA_65_PUB_LEN):
186
34
            keylen = ML_DSA_65_PRIV_LEN;
187
34
            break;
188
6
        case (ML_DSA_87_PUB_LEN):
189
6
            keylen = ML_DSA_87_PRIV_LEN;
190
6
            break;
191
111
        default:
192
111
            return;
193
174
        }
194
174
    }
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
116
    if (!RAND_bytes(key, (int)keylen))
202
0
        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
116
    if (pub == 1)
211
53
        pubkey = EVP_PKEY_new_raw_public_key_ex(NULL, keytype, NULL, key, keylen);
212
63
    else
213
63
        pubkey = EVP_PKEY_new_raw_private_key_ex(NULL, keytype, NULL, key, keylen);
214
215
116
    *key1 = pubkey;
216
116
    return;
217
116
}
218
219
static int keygen_ml_dsa_real_key_helper(uint8_t **buf, size_t *len,
220
    EVP_PKEY **key)
221
1.33k
{
222
1.33k
    char *keytype = NULL;
223
1.33k
    size_t keylen = 0;
224
1.33k
    EVP_PKEY_CTX *ctx = NULL;
225
1.33k
    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
1.33k
    if (!select_keytype_and_size(buf, len, &keytype, &keylen, 1))
233
0
        goto err;
234
235
1.33k
    ctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, NULL);
236
1.33k
    if (!ctx) {
237
0
        fprintf(stderr, "Failed to generate ctx\n");
238
0
        goto err;
239
0
    }
240
241
1.33k
    if (!EVP_PKEY_keygen_init(ctx)) {
242
0
        fprintf(stderr, "Failed to init keygen ctx\n");
243
0
        goto err;
244
0
    }
245
246
1.33k
    *key = EVP_PKEY_new();
247
1.33k
    if (*key == NULL)
248
0
        goto err;
249
250
1.33k
    if (!EVP_PKEY_generate(ctx, key)) {
251
0
        fprintf(stderr, "Failed to generate new real key\n");
252
0
        goto err;
253
0
    }
254
255
1.33k
    ret = 1;
256
1.33k
err:
257
1.33k
    EVP_PKEY_CTX_free(ctx);
258
1.33k
    return ret;
259
1.33k
}
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
667
{
279
667
    if (!keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key1)
280
667
        || !keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key2))
281
0
        fprintf(stderr, "Unable to generate valid keys");
282
667
}
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
352
{
301
352
    EVP_PKEY *key = (EVP_PKEY *)key1;
302
352
    EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL);
303
352
    EVP_SIGNATURE *sig_alg = NULL;
304
352
    unsigned char *sig = NULL;
305
352
    size_t sig_len = 0, tbslen;
306
352
    unsigned char *tbs = NULL;
307
    /* Ownership of alg is retained by the pkey object */
308
352
    const char *alg = EVP_PKEY_get0_type_name(key);
309
352
    const OSSL_PARAM params[] = {
310
352
        OSSL_PARAM_octet_string("context-string",
311
352
            (unsigned char *)"A context string", 16),
312
352
        OSSL_PARAM_END
313
352
    };
314
315
352
    if (!consume_size_t(*buf, len, &tbslen)) {
316
0
        fprintf(stderr, "Failed to set tbslen");
317
0
        goto err;
318
0
    }
319
    /* Keep tbslen within a reasonable value we can malloc */
320
352
    tbslen = (tbslen % 2048) + 1;
321
322
352
    if ((tbs = OPENSSL_malloc(tbslen)) == NULL
323
352
        || ctx == NULL || alg == NULL
324
352
        || !RAND_bytes_ex(NULL, tbs, tbslen, 0)) {
325
0
        fprintf(stderr, "Failed basic initialization\n");
326
0
        goto err;
327
0
    }
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
352
    if ((sig_alg = EVP_SIGNATURE_fetch(NULL, alg, NULL)) == NULL
336
352
        || EVP_PKEY_sign_message_init(ctx, sig_alg, params) <= 0
337
352
        || EVP_PKEY_sign(ctx, NULL, &sig_len, tbs, tbslen) <= 0
338
352
        || (sig = OPENSSL_zalloc(sig_len)) == NULL
339
352
        || EVP_PKEY_sign(ctx, sig, &sig_len, tbs, tbslen) <= 0) {
340
0
        fprintf(stderr, "Failed to sign message\n");
341
0
        goto err;
342
0
    }
343
344
    /* Verify signature */
345
352
    EVP_PKEY_CTX_free(ctx);
346
352
    ctx = NULL;
347
348
352
    if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL)) == NULL
349
352
        || EVP_PKEY_verify_message_init(ctx, sig_alg, params) <= 0
350
352
        || EVP_PKEY_verify(ctx, sig, sig_len, tbs, tbslen) <= 0) {
351
0
        fprintf(stderr, "Failed to verify message\n");
352
0
        goto err;
353
0
    }
354
355
352
err:
356
352
    OPENSSL_free(tbs);
357
352
    EVP_PKEY_CTX_free(ctx);
358
352
    EVP_SIGNATURE_free(sig_alg);
359
352
    OPENSSL_free(sig);
360
352
    return;
361
352
}
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
242
{
380
242
    EVP_PKEY *key = (EVP_PKEY *)key1;
381
242
    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
382
242
    EVP_SIGNATURE *sig_alg = NULL;
383
242
    unsigned char *sig = NULL;
384
242
    size_t sig_len, tbslen;
385
242
    unsigned char *tbs = NULL;
386
242
    const OSSL_PARAM params[] = {
387
242
        OSSL_PARAM_octet_string("context-string",
388
242
            (unsigned char *)"A context string", 16),
389
242
        OSSL_PARAM_END
390
242
    };
391
392
242
    if (!consume_size_t(*buf, len, &tbslen)) {
393
0
        fprintf(stderr, "Failed to set tbslen");
394
0
        goto err;
395
0
    }
396
    /* Keep tbslen within a reasonable value we can malloc */
397
242
    tbslen = (tbslen % 2048) + 1;
398
399
242
    if ((tbs = OPENSSL_malloc(tbslen)) == NULL
400
242
        || ctx == NULL
401
242
        || !RAND_bytes_ex(NULL, tbs, tbslen, 0)) {
402
0
        fprintf(stderr, "Failed basic initialization\n");
403
0
        goto err;
404
0
    }
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
242
    if (!EVP_DigestSignInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, params)
413
242
        || EVP_DigestSign(ctx, NULL, &sig_len, tbs, tbslen) <= 0
414
242
        || (sig = OPENSSL_malloc(sig_len)) == NULL
415
242
        || EVP_DigestSign(ctx, sig, &sig_len, tbs, tbslen) <= 0) {
416
0
        fprintf(stderr, "Failed to sign digest with EVP_DigestSign\n");
417
0
        goto err;
418
0
    }
419
420
    /* Verify signature */
421
242
    EVP_MD_CTX_free(ctx);
422
242
    ctx = NULL;
423
424
242
    if ((ctx = EVP_MD_CTX_new()) == NULL
425
242
        || EVP_DigestVerifyInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key,
426
242
               params)
427
242
            <= 0
428
242
        || EVP_DigestVerify(ctx, sig, sig_len, tbs, tbslen) <= 0) {
429
0
        fprintf(stderr, "Failed to verify digest with EVP_DigestVerify\n");
430
0
        goto err;
431
0
    }
432
433
242
err:
434
242
    OPENSSL_free(tbs);
435
242
    EVP_MD_CTX_free(ctx);
436
242
    EVP_SIGNATURE_free(sig_alg);
437
242
    OPENSSL_free(sig);
438
242
    return;
439
242
}
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
12
{
462
12
    EVP_PKEY *alice = (EVP_PKEY *)key1;
463
12
    EVP_PKEY *new_key = NULL;
464
12
    EVP_PKEY_CTX *ctx = NULL;
465
12
    OSSL_PARAM *params = NULL;
466
467
12
    if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, &params)) {
468
0
        fprintf(stderr, "Failed todata\n");
469
0
        goto err;
470
0
    }
471
472
12
    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL);
473
12
    if (ctx == NULL) {
474
0
        fprintf(stderr, "Failed new ctx\n");
475
0
        goto err;
476
0
    }
477
478
12
    if (!EVP_PKEY_fromdata(ctx, &new_key, EVP_PKEY_KEYPAIR, params)) {
479
0
        fprintf(stderr, "Failed fromdata\n");
480
0
        goto err;
481
0
    }
482
483
12
err:
484
12
    EVP_PKEY_CTX_free(ctx);
485
12
    EVP_PKEY_free(new_key);
486
12
    OSSL_PARAM_free(params);
487
12
}
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
51
{
507
51
    EVP_PKEY *alice = (EVP_PKEY *)key1;
508
51
    EVP_PKEY *bob = (EVP_PKEY *)key2;
509
510
51
    EVP_PKEY_eq(alice, alice);
511
51
    EVP_PKEY_eq(alice, bob);
512
51
}
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
894
{
531
894
    EVP_PKEY_free((EVP_PKEY *)key1);
532
894
    EVP_PKEY_free((EVP_PKEY *)key2);
533
894
    EVP_PKEY_free((EVP_PKEY *)key3);
534
894
    EVP_PKEY_free((EVP_PKEY *)key4);
535
894
}
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
214
{
622
214
    return 0;
623
214
}
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
2.87k
{
643
2.87k
    uint8_t operation;
644
2.87k
    uint8_t *buffer_cursor;
645
2.87k
    void *in1 = NULL, *in2 = NULL;
646
2.87k
    void *out1 = NULL, *out2 = NULL;
647
648
2.87k
    if (len < 32)
649
72
        return -1;
650
651
    /* Get the first byte of the buffer to tell us what operation to perform */
652
2.79k
    buffer_cursor = consume_uint8_t(buf, &len, &operation);
653
2.79k
    if (buffer_cursor == NULL)
654
0
        return -1;
655
656
    /* Adjust for operational array size */
657
2.79k
    operation %= OSSL_NELEM(ops);
658
659
    /* And run our setup/doit/cleanup sequence */
660
2.79k
    if (ops[operation].setup != NULL)
661
1.70k
        ops[operation].setup(&buffer_cursor, &len, &in1, &in2);
662
2.79k
    if (ops[operation].doit != NULL)
663
2.13k
        ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2);
664
2.79k
    if (ops[operation].cleanup != NULL)
665
2.79k
        ops[operation].cleanup(in1, in2, out1, out2);
666
667
2.79k
    return 0;
668
2.79k
}
669
670
void FuzzerCleanup(void)
671
0
{
672
0
    OPENSSL_cleanup();
673
0
}