/src/openssl35/fuzz/ml-dsa.c
Line | Count | Source (jump to first uncovered line) |
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 | 586 | { |
36 | 586 | if (*len < sizeof(uint8_t)) |
37 | 0 | return NULL; |
38 | 586 | *val = *buf; |
39 | 586 | *len -= sizeof(uint8_t); |
40 | 586 | return (uint8_t *)buf + 1; |
41 | 586 | } |
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 | 399 | { |
58 | 399 | if (*len < sizeof(size_t)) |
59 | 0 | return NULL; |
60 | 399 | *val = *buf; |
61 | 399 | *len -= sizeof(size_t); |
62 | 399 | return (uint8_t *)buf + sizeof(size_t); |
63 | 399 | } |
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.03k | { |
85 | 1.03k | uint16_t keysize; |
86 | 1.03k | 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.03k | *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); |
93 | 1.03k | *len -= sizeof(uint16_t); |
94 | | |
95 | 1.03k | 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.03k | if (only_valid) |
104 | 898 | 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.03k | switch (keysize % modulus) { |
111 | 338 | case 0: |
112 | 338 | *keytype = "ML-DSA-44"; |
113 | 338 | *keylen = ML_DSA_44_PUB_LEN; |
114 | 338 | break; |
115 | 314 | case 1: |
116 | 314 | *keytype = "ML-DSA-65"; |
117 | 314 | *keylen = ML_DSA_65_PUB_LEN; |
118 | 314 | break; |
119 | 288 | case 2: |
120 | 288 | *keytype = "ML-DSA-87"; |
121 | 288 | *keylen = ML_DSA_87_PUB_LEN; |
122 | 288 | break; |
123 | 2 | case 3: |
124 | | /* select invalid alg */ |
125 | 2 | *keytype = "ML-DSA-33"; |
126 | 2 | *keylen = 33; |
127 | 2 | break; |
128 | 91 | case 4: |
129 | | /* Select valid alg, but bogus size */ |
130 | 91 | *keytype = "ML-DSA-87"; |
131 | 91 | *buf = (uint8_t *)OPENSSL_load_u16_le(&keysize, *buf); |
132 | 91 | *len -= sizeof(uint16_t); |
133 | 91 | *keylen = (size_t)keysize; |
134 | 91 | *keylen %= ML_DSA_87_PUB_LEN; /* size to our key buffer */ |
135 | 91 | break; |
136 | 2 | default: |
137 | 2 | *keytype = NULL; |
138 | 2 | *keylen = 0; |
139 | 2 | break; |
140 | 1.03k | } |
141 | 1.03k | return 1; |
142 | 1.03k | } |
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 | 137 | { |
162 | 137 | EVP_PKEY *pubkey; |
163 | 137 | char *keytype = NULL; |
164 | 137 | size_t keylen = 0; |
165 | | /* MAX_ML_DSA_PRIV_LEN is longer of that and ML_DSA_87_PUB_LEN */ |
166 | 137 | uint8_t key[MAX_ML_DSA_PRIV_LEN]; |
167 | 137 | int pub = 0; |
168 | | |
169 | 137 | 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 | 137 | if ((*buf)[0] & 0x1) { |
179 | 41 | pub = 1; |
180 | 96 | } else { |
181 | 96 | switch (keylen) { |
182 | 10 | case (ML_DSA_44_PUB_LEN): |
183 | 10 | keylen = ML_DSA_44_PRIV_LEN; |
184 | 10 | break; |
185 | 23 | case (ML_DSA_65_PUB_LEN): |
186 | 23 | keylen = ML_DSA_65_PRIV_LEN; |
187 | 23 | break; |
188 | 3 | case (ML_DSA_87_PUB_LEN): |
189 | 3 | keylen = ML_DSA_87_PRIV_LEN; |
190 | 3 | break; |
191 | 60 | default: |
192 | 60 | return; |
193 | 96 | } |
194 | 96 | } |
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 | 77 | if (!RAND_bytes(key, 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 | 77 | if (pub == 1) |
211 | 41 | pubkey = EVP_PKEY_new_raw_public_key_ex(NULL, keytype, NULL, key, keylen); |
212 | 36 | else |
213 | 36 | pubkey = EVP_PKEY_new_raw_private_key_ex(NULL, keytype, NULL, key, keylen); |
214 | | |
215 | 77 | *key1 = pubkey; |
216 | 77 | return; |
217 | 77 | } |
218 | | |
219 | | static int keygen_ml_dsa_real_key_helper(uint8_t **buf, size_t *len, |
220 | | EVP_PKEY **key) |
221 | 898 | { |
222 | 898 | char *keytype = NULL; |
223 | 898 | size_t keylen = 0; |
224 | 898 | EVP_PKEY_CTX *ctx = NULL; |
225 | 898 | 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 | 898 | if (!select_keytype_and_size(buf, len, &keytype, &keylen, 1)) |
233 | 0 | goto err; |
234 | | |
235 | 898 | ctx = EVP_PKEY_CTX_new_from_name(NULL, keytype, NULL); |
236 | 898 | if (!ctx) { |
237 | 0 | fprintf(stderr, "Failed to generate ctx\n"); |
238 | 0 | goto err; |
239 | 0 | } |
240 | | |
241 | 898 | if (!EVP_PKEY_keygen_init(ctx)) { |
242 | 0 | fprintf(stderr, "Failed to init keygen ctx\n"); |
243 | 0 | goto err; |
244 | 0 | } |
245 | | |
246 | 898 | *key = EVP_PKEY_new(); |
247 | 898 | if (*key == NULL) |
248 | 0 | goto err; |
249 | | |
250 | 898 | 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 | 898 | ret = 1; |
256 | 898 | err: |
257 | 898 | EVP_PKEY_CTX_free(ctx); |
258 | 898 | return ret; |
259 | 898 | } |
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 | 449 | { |
279 | 449 | if (!keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key1) |
280 | 449 | || !keygen_ml_dsa_real_key_helper(buf, len, (EVP_PKEY **)key2)) |
281 | 0 | fprintf(stderr, "Unable to generate valid keys"); |
282 | 449 | } |
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 | 259 | { |
301 | 259 | EVP_PKEY *key = (EVP_PKEY *)key1; |
302 | 259 | EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL); |
303 | 259 | EVP_SIGNATURE *sig_alg = NULL; |
304 | 259 | unsigned char *sig = NULL; |
305 | 259 | size_t sig_len = 0, tbslen; |
306 | 259 | unsigned char *tbs = NULL; |
307 | | /* Ownership of alg is retained by the pkey object */ |
308 | 259 | const char *alg = EVP_PKEY_get0_type_name(key); |
309 | 259 | const OSSL_PARAM params[] = { |
310 | 259 | OSSL_PARAM_octet_string("context-string", |
311 | 259 | (unsigned char *)"A context string", 16), |
312 | 259 | OSSL_PARAM_END |
313 | 259 | }; |
314 | | |
315 | 259 | 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 | 259 | tbslen = (tbslen % 2048) + 1; |
321 | | |
322 | 259 | if ((tbs = OPENSSL_malloc(tbslen)) == NULL |
323 | 259 | || ctx == NULL || alg == NULL |
324 | 259 | || !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 | 259 | if ((sig_alg = EVP_SIGNATURE_fetch(NULL, alg, NULL)) == NULL |
336 | 259 | || EVP_PKEY_sign_message_init(ctx, sig_alg, params) <= 0 |
337 | 259 | || EVP_PKEY_sign(ctx, NULL, &sig_len, tbs, tbslen) <= 0 |
338 | 259 | || (sig = OPENSSL_zalloc(sig_len)) == NULL |
339 | 259 | || 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 | 259 | EVP_PKEY_CTX_free(ctx); |
346 | 259 | ctx = NULL; |
347 | | |
348 | 259 | if ((ctx = EVP_PKEY_CTX_new_from_pkey(NULL, key, NULL)) == NULL |
349 | 259 | || EVP_PKEY_verify_message_init(ctx, sig_alg, params) <= 0 |
350 | 259 | || 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 | 259 | err: |
356 | 259 | OPENSSL_free(tbs); |
357 | 259 | EVP_PKEY_CTX_free(ctx); |
358 | 259 | EVP_SIGNATURE_free(sig_alg); |
359 | 259 | OPENSSL_free(sig); |
360 | 259 | return; |
361 | 259 | } |
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 | 140 | { |
380 | 140 | EVP_PKEY *key = (EVP_PKEY *)key1; |
381 | 140 | EVP_MD_CTX *ctx = EVP_MD_CTX_new(); |
382 | 140 | EVP_SIGNATURE *sig_alg = NULL; |
383 | 140 | unsigned char *sig = NULL; |
384 | 140 | size_t sig_len, tbslen; |
385 | 140 | unsigned char *tbs = NULL; |
386 | 140 | const OSSL_PARAM params[] = { |
387 | 140 | OSSL_PARAM_octet_string("context-string", |
388 | 140 | (unsigned char *)"A context string", 16), |
389 | 140 | OSSL_PARAM_END |
390 | 140 | }; |
391 | | |
392 | 140 | 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 | 140 | tbslen = (tbslen % 2048) + 1; |
398 | | |
399 | 140 | if ((tbs = OPENSSL_malloc(tbslen)) == NULL |
400 | 140 | || ctx == NULL |
401 | 140 | || !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 | 140 | if (!EVP_DigestSignInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, params) |
413 | 140 | || EVP_DigestSign(ctx, NULL, &sig_len, tbs, tbslen) <= 0 |
414 | 140 | || (sig = OPENSSL_malloc(sig_len)) == NULL |
415 | 140 | || 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 | 140 | EVP_MD_CTX_free(ctx); |
422 | 140 | ctx = NULL; |
423 | | |
424 | 140 | if ((ctx = EVP_MD_CTX_new()) == NULL |
425 | 140 | || EVP_DigestVerifyInit_ex(ctx, NULL, NULL, NULL, "?fips=true", key, |
426 | 140 | params) <= 0 |
427 | 140 | || EVP_DigestVerify(ctx, sig, sig_len, tbs, tbslen) <= 0) { |
428 | 0 | fprintf(stderr, "Failed to verify digest with EVP_DigestVerify\n"); |
429 | 0 | goto err; |
430 | 0 | } |
431 | | |
432 | 140 | err: |
433 | 140 | OPENSSL_free(tbs); |
434 | 140 | EVP_MD_CTX_free(ctx); |
435 | 140 | EVP_SIGNATURE_free(sig_alg); |
436 | 140 | OPENSSL_free(sig); |
437 | 140 | return; |
438 | 140 | } |
439 | | |
440 | | /** |
441 | | * @brief Exports and imports an ML-DSA key. |
442 | | * |
443 | | * This function extracts key material from the given key (`key1`), exports it |
444 | | * as parameters, and then attempts to reconstruct a new key from those |
445 | | * 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 function |
456 | | * logs an error and cleans up allocated resources. |
457 | | */ |
458 | | static void ml_dsa_export_import(uint8_t **buf, size_t *len, void *key1, |
459 | | void *key2, void **out1, void **out2) |
460 | 6 | { |
461 | 6 | EVP_PKEY *alice = (EVP_PKEY *)key1; |
462 | 6 | EVP_PKEY *new_key = NULL; |
463 | 6 | EVP_PKEY_CTX *ctx = NULL; |
464 | 6 | OSSL_PARAM *params = NULL; |
465 | | |
466 | 6 | if (!EVP_PKEY_todata(alice, EVP_PKEY_KEYPAIR, ¶ms)) { |
467 | 0 | fprintf(stderr, "Failed todata\n"); |
468 | 0 | goto err; |
469 | 0 | } |
470 | | |
471 | 6 | ctx = EVP_PKEY_CTX_new_from_pkey(NULL, alice, NULL); |
472 | 6 | if (ctx == NULL) { |
473 | 0 | fprintf(stderr, "Failed new ctx\n"); |
474 | 0 | goto err; |
475 | 0 | } |
476 | | |
477 | 6 | if (!EVP_PKEY_fromdata(ctx, &new_key, EVP_PKEY_KEYPAIR, params)) { |
478 | 0 | fprintf(stderr, "Failed fromdata\n"); |
479 | 0 | goto err; |
480 | 0 | } |
481 | | |
482 | 6 | err: |
483 | 6 | EVP_PKEY_CTX_free(ctx); |
484 | 6 | EVP_PKEY_free(new_key); |
485 | 6 | OSSL_PARAM_free(params); |
486 | 6 | } |
487 | | |
488 | | /** |
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 of |
493 | | * `buf`, `len`, `out1`, and `out2` parameters is not clear from the function's |
494 | | * 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 | | */ |
503 | | static void ml_dsa_compare(uint8_t **buf, size_t *len, void *key1, |
504 | | void *key2, void **out1, void **out2) |
505 | 35 | { |
506 | 35 | EVP_PKEY *alice = (EVP_PKEY *)key1; |
507 | 35 | EVP_PKEY *bob = (EVP_PKEY *)key2; |
508 | | |
509 | 35 | EVP_PKEY_eq(alice, alice); |
510 | 35 | EVP_PKEY_eq(alice, bob); |
511 | 35 | } |
512 | | |
513 | | /** |
514 | | * @brief Frees allocated ML-DSA keys. |
515 | | * |
516 | | * This function releases memory associated with up to four EVP_PKEY objects by |
517 | | * 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_PKEY |
525 | | * object or NULL. Passing NULL is safe and has no effect. |
526 | | */ |
527 | | static void cleanup_ml_dsa_keys(void *key1, void *key2, |
528 | | void *key3, void *key4) |
529 | 586 | { |
530 | 586 | EVP_PKEY_free((EVP_PKEY *)key1); |
531 | 586 | EVP_PKEY_free((EVP_PKEY *)key2); |
532 | 586 | EVP_PKEY_free((EVP_PKEY *)key3); |
533 | 586 | EVP_PKEY_free((EVP_PKEY *)key4); |
534 | 586 | } |
535 | | |
536 | | /** |
537 | | * @brief Represents an operation table entry for cryptographic operations. |
538 | | * |
539 | | * This structure defines a table entry containing function pointers for setting |
540 | | * up, executing, and cleaning up cryptographic operations, along with |
541 | | * associated metadata such as a name and description. |
542 | | * |
543 | | * @struct op_table_entry |
544 | | */ |
545 | | struct op_table_entry { |
546 | | /** Name of the operation. */ |
547 | | char *name; |
548 | | |
549 | | /** Description of the operation. */ |
550 | | char *desc; |
551 | | |
552 | | /** |
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 | | */ |
560 | | void (*setup)(uint8_t **buf, size_t *len, void **out1, void **out2); |
561 | | |
562 | | /** |
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 | | */ |
572 | | void (*doit)(uint8_t **buf, size_t *len, void *in1, void *in2, |
573 | | void **out1, void **out2); |
574 | | |
575 | | /** |
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 | | */ |
583 | | void (*cleanup)(void *in1, void *in2, void *out1, void *out2); |
584 | | }; |
585 | | |
586 | | static struct op_table_entry ops[] = { |
587 | | { |
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 | | }, { |
594 | | "Generate ML-DSA keypair, using EVP_PKEY_keygen", |
595 | | "Generates a real ML-DSA keypair, should always work", |
596 | | keygen_ml_dsa_real_key, |
597 | | NULL, |
598 | | cleanup_ml_dsa_keys |
599 | | }, { |
600 | | "Do a sign/verify operation on a key", |
601 | | "Generate key, sign random data, verify it, should work", |
602 | | keygen_ml_dsa_real_key, |
603 | | ml_dsa_sign_verify, |
604 | | cleanup_ml_dsa_keys |
605 | | }, { |
606 | | "Do a digest sign/verify operation on a key", |
607 | | "Generate key, digest sign random data, verify it, should work", |
608 | | keygen_ml_dsa_real_key, |
609 | | ml_dsa_digest_sign_verify, |
610 | | cleanup_ml_dsa_keys |
611 | | }, { |
612 | | "Do an export/import of key data", |
613 | | "Exercise EVP_PKEY_todata/fromdata", |
614 | | keygen_ml_dsa_real_key, |
615 | | ml_dsa_export_import, |
616 | | cleanup_ml_dsa_keys |
617 | | }, { |
618 | | "Compare keys for equality", |
619 | | "Compare key1/key1 and key1/key2 for equality", |
620 | | keygen_ml_dsa_real_key, |
621 | | ml_dsa_compare, |
622 | | cleanup_ml_dsa_keys |
623 | | } |
624 | | }; |
625 | | |
626 | | int FuzzerInitialize(int *argc, char ***argv) |
627 | 200 | { |
628 | 200 | return 0; |
629 | 200 | } |
630 | | |
631 | | /** |
632 | | * @brief Processes a fuzzing input by selecting and executing an operation. |
633 | | * |
634 | | * This function interprets the first byte of the input buffer to determine an |
635 | | * operation to execute. It then follows a setup, execution, and cleanup |
636 | | * sequence based on the selected operation. |
637 | | * |
638 | | * @param buf Pointer to the input buffer. |
639 | | * @param len Length of the input buffer. |
640 | | * |
641 | | * @return 0 on successful execution, -1 if the input is too short. |
642 | | * |
643 | | * @note The function requires at least 32 bytes in the buffer to proceed. |
644 | | * It utilizes the `ops` operation table to dynamically determine and |
645 | | * execute the selected operation. |
646 | | */ |
647 | | int FuzzerTestOneInput(const uint8_t *buf, size_t len) |
648 | 1.77k | { |
649 | 1.77k | uint8_t operation; |
650 | 1.77k | uint8_t *buffer_cursor; |
651 | 1.77k | void *in1 = NULL, *in2 = NULL; |
652 | 1.77k | void *out1 = NULL, *out2 = NULL; |
653 | | |
654 | 1.77k | if (len < 32) |
655 | 48 | return -1; |
656 | | |
657 | | /* Get the first byte of the buffer to tell us what operation to perform */ |
658 | 1.72k | buffer_cursor = consume_uint8_t(buf, &len, &operation); |
659 | 1.72k | if (buffer_cursor == NULL) |
660 | 0 | return -1; |
661 | | |
662 | | /* Adjust for operational array size */ |
663 | 1.72k | operation %= OSSL_NELEM(ops); |
664 | | |
665 | | /* And run our setup/doit/cleanup sequence */ |
666 | 1.72k | if (ops[operation].setup != NULL) |
667 | 1.09k | ops[operation].setup(&buffer_cursor, &len, &in1, &in2); |
668 | 1.72k | if (ops[operation].doit != NULL) |
669 | 1.31k | ops[operation].doit(&buffer_cursor, &len, in1, in2, &out1, &out2); |
670 | 1.72k | if (ops[operation].cleanup != NULL) |
671 | 1.72k | ops[operation].cleanup(in1, in2, out1, out2); |
672 | | |
673 | 1.72k | return 0; |
674 | 1.72k | } |
675 | | |
676 | | void FuzzerCleanup(void) |
677 | 0 | { |
678 | 0 | OPENSSL_cleanup(); |
679 | 0 | } |