xref: /freebsd/crypto/openssh/regress/unittests/crypto/test_mldsa_eddsa.c (revision bb5c77e9d281d6def6835d48249898764bc6a5fe)
1 /* 	$OpenBSD: test_mldsa_eddsa.c,v 1.1 2026/06/14 04:08:06 djm Exp $ */
2 /*
3  * Regress test for MLDSA44-Ed25519 composite signature
4  *
5  * Placed in the public domain
6  */
7 
8 #include "includes.h"
9 
10 #ifdef USE_MLDSA
11 
12 #include <sys/types.h>
13 #include <stdio.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <fcntl.h>
19 #include <unistd.h>
20 
21 #include "../test_helper/test_helper.h"
22 #include "crypto_api.h"
23 #include "ssherr.h"
24 #include "sshkey.h"
25 #include "sshbuf.h"
26 #include "log.h"
27 
28 /* in tests.c */
29 struct sshbuf *load_text_file(const char *name);
30 char *get_json_string(struct sshbuf *content, const char *key, int consume);
31 
32 void mldsa_eddsa_tests(void);
33 
34 /*
35  * Simple JSON-ish parser for tvec.json.
36  * Extracts the base64 value for a given key and decodes it into a new sshbuf.
37  * Errors cause ASSERT_* failures.
38  */
39 static struct sshbuf *
get_json_b64(struct sshbuf * content,const char * key)40 get_json_b64(struct sshbuf *content, const char *key)
41 {
42 	char *b64;
43 	struct sshbuf *ret;
44 
45 	b64 = get_json_string(content, key, 0);
46 	ASSERT_PTR_NE(ret = sshbuf_new(), NULL);
47 	ASSERT_INT_EQ(sshbuf_b64tod(ret, b64), 0);
48 	free(b64);
49 	return ret;
50 }
51 
52 static void
onerror(void * fuzz)53 onerror(void *fuzz)
54 {
55 	fprintf(stderr, "Failed during fuzz:\n");
56 	fuzz_dump((struct fuzz *)fuzz);
57 }
58 
59 static void
sig_fuzz(const uint8_t * m,size_t m_len,const uint8_t * ctx,size_t ctx_len,const uint8_t * pk,const uint8_t * s)60 sig_fuzz(const uint8_t *m, size_t m_len, const uint8_t *ctx, size_t ctx_len,
61     const uint8_t *pk, const uint8_t *s)
62 {
63 	struct fuzz *fuzz;
64 	u_int fuzzers = FUZZ_1_BIT_FLIP | FUZZ_1_BYTE_FLIP;
65 
66 	if (test_is_fast())
67 		fuzzers &= ~FUZZ_1_BIT_FLIP;
68 	if (test_is_slow())
69 		fuzzers |= FUZZ_2_BYTE_FLIP; /* FUZZ_2_BIT_FLIP much too slow */
70 
71 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(s, m, m_len,
72 	    ctx, ctx_len, pk), 0);
73 	fuzz = fuzz_begin(fuzzers, s, MLDSA44_ED25519_SIG_SZ);
74 	TEST_ONERROR(onerror, fuzz);
75 	for(; !fuzz_done(fuzz); fuzz_next(fuzz)) {
76 		/* Ensure 1-bit difference at least */
77 		if (fuzz_matches_original(fuzz))
78 			continue;
79 		ASSERT_INT_NE(crypto_sign_mldsa44_ed25519_verify(fuzz_ptr(fuzz),
80 		    m, m_len, ctx, ctx_len, pk), 0);
81 	}
82 	fuzz_cleanup(fuzz);
83 }
84 
85 
86 void
mldsa_eddsa_tests(void)87 mldsa_eddsa_tests(void)
88 {
89 	uint8_t pk[MLDSA44_ED25519_PK_SZ];
90 	uint8_t sk[MLDSA44_ED25519_SK_SZ];
91 	uint8_t sig[MLDSA44_ED25519_SIG_SZ];
92 	struct sshbuf *b_m = NULL, *b_ctx = NULL, *b_pk = NULL;
93 	struct sshbuf *b_sk = NULL, *b_s = NULL, *b_sWithContext = NULL;
94 	struct sshbuf *json_buf = NULL;
95 	const uint8_t *tvec_m;
96 	size_t tvec_m_len;
97 	const uint8_t *tvec_ctx;
98 	size_t tvec_ctx_len;
99 	const uint8_t *tvec_pk;
100 	const uint8_t *tvec_sk;
101 	const uint8_t *tvec_s;
102 	const uint8_t *tvec_sWithContext;
103 
104 	TEST_START("MLDSA44-Ed25519-SHA512 load test vectors");
105 	json_buf = load_text_file("draft-ietf-lamps-pq-composite-sigs.json");
106 	b_m = get_json_b64(json_buf, "m");
107 	b_ctx = get_json_b64(json_buf, "ctx");
108 	b_pk = get_json_b64(json_buf, "pk");
109 	b_sk = get_json_b64(json_buf, "sk");
110 	b_s = get_json_b64(json_buf, "s");
111 	b_sWithContext = get_json_b64(json_buf, "sWithContext");
112 	ASSERT_INT_EQ(sshbuf_len(b_pk), MLDSA44_ED25519_PK_SZ);
113 	ASSERT_INT_EQ(sshbuf_len(b_sk), MLDSA44_ED25519_SK_SZ);
114 	ASSERT_INT_EQ(sshbuf_len(b_s), MLDSA44_ED25519_SIG_SZ);
115 	ASSERT_INT_EQ(sshbuf_len(b_sWithContext), MLDSA44_ED25519_SIG_SZ);
116 	TEST_DONE();
117 
118 	tvec_m = sshbuf_ptr(b_m);
119 	tvec_m_len = sshbuf_len(b_m);
120 	tvec_ctx = sshbuf_ptr(b_ctx);
121 	tvec_ctx_len = sshbuf_len(b_ctx);
122 	tvec_pk = sshbuf_ptr(b_pk);
123 	tvec_sk = sshbuf_ptr(b_sk);
124 	tvec_s = sshbuf_ptr(b_s);
125 	tvec_sWithContext = sshbuf_ptr(b_sWithContext);
126 
127 	TEST_START("MLDSA44-Ed25519-SHA512 raw self-consistency");
128 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_keygen(pk, sk), 0);
129 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig,
130 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, sk), 0);
131 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
132 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, pk), 0);
133 	TEST_DONE();
134 
135 	TEST_START("MLDSA44-Ed25519-SHA512 raw KAT key expansion");
136 	uint8_t pk_expanded[MLDSA44_ED25519_PK_SZ];
137 	uint8_t sk_expanded[MLDSA44_ED25519_SK_SZ];
138 
139 	/* Expansion: mldsa_seed (sk[0:32]) and ed25519_seed (sk[32:64]) */
140 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_keygen_seeded(pk_expanded,
141 	    sk_expanded, tvec_sk, tvec_sk + 32), 0);
142 	ASSERT_MEM_EQ(pk_expanded, tvec_pk, MLDSA44_ED25519_PK_SZ);
143 	/* sk_expanded should also match tvec_sk */
144 	ASSERT_MEM_EQ(sk_expanded, tvec_sk, MLDSA44_ED25519_SK_SZ);
145 	TEST_DONE();
146 
147 	TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (no context)");
148 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_s, tvec_m,
149 	    tvec_m_len, NULL, 0, tvec_pk), 0);
150 	TEST_DONE();
151 
152 	TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (with context)");
153 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
154 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
155 	TEST_DONE();
156 
157 	TEST_START("MLDSA44-Ed25519-SHA512 raw round-trip (no context)");
158 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig, tvec_m, tvec_m_len,
159 	    NULL, 0, tvec_sk), 0);
160 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
161 	    tvec_m, tvec_m_len, NULL, 0, tvec_pk), 0);
162 	TEST_DONE();
163 
164 	TEST_START("MLDSA44-Ed25519-SHA512 raw round-trip (with context)");
165 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_sign(sig,
166 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_sk), 0);
167 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(sig,
168 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
169 	TEST_DONE();
170 
171 	TEST_START("MLDSA44-Ed25519-SHA512 raw KAT verify (no context)");
172 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_s, tvec_m,
173 	    tvec_m_len, NULL, 0, tvec_pk), 0);
174 	TEST_DONE();
175 
176 	TEST_START("MLDSA44-Ed25519-SHA512 fuzz raw verify (with context)");
177 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
178 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
179 	sig_fuzz(tvec_m, tvec_m_len, NULL, 0, tvec_pk, tvec_s);
180 	TEST_DONE();
181 
182 	TEST_START("MLDSA44-Ed25519-SHA512 fuzz raw verify (with context)");
183 	ASSERT_INT_EQ(crypto_sign_mldsa44_ed25519_verify(tvec_sWithContext,
184 	    tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len, tvec_pk), 0);
185 	sig_fuzz(tvec_m, tvec_m_len, tvec_ctx, tvec_ctx_len,
186 	    tvec_pk, tvec_sWithContext);
187 	TEST_DONE();
188 
189 	sshbuf_free(json_buf);
190 	sshbuf_free(b_m);
191 	sshbuf_free(b_ctx);
192 	sshbuf_free(b_pk);
193 	sshbuf_free(b_sk);
194 	sshbuf_free(b_s);
195 	sshbuf_free(b_sWithContext);
196 }
197 #endif /* USE_MLDSA */
198