Home | History | Annotate | Line # | Download | only in dist
      1 /*	$NetBSD: ssh-mldsa-eddsa.c,v 1.2 2026/09/21 23:01:54 christos Exp $	*/
      2 /* $OpenBSD: ssh-mldsa-eddsa.c,v 1.4 2026/07/30 07:40:48 brynet Exp $ */
      3 
      4 /*
      5  * Copyright (c) 2026 Damien Miller <djm (at) mindrot.org>
      6  *
      7  * Permission to use, copy, modify, and distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 
     20 /* draft-miller-sshm-mldsa44-ed25519-composite-sigs-00 */
     21 #include "includes.h"
     22 __RCSID("$NetBSD: ssh-mldsa-eddsa.c,v 1.2 2026/09/21 23:01:54 christos Exp $");
     23 
     24 #include <sys/types.h>
     25 #include <stdint.h>
     26 #include <string.h>
     27 #include <stdlib.h>
     28 
     29 #include "crypto_api.h"
     30 #include "sshbuf.h"
     31 #include "ssherr.h"
     32 #include "digest.h"
     33 #define SSHKEY_INTERNAL
     34 #include "sshkey.h"
     35 #include "log.h"
     36 
     37 #define COMPOSITE_PREFIX "CompositeAlgorithmSignatures2025"
     38 #define COMPOSITE_LABEL  "COMPSIG-MLDSA44-Ed25519-SHA512"
     39 #define SSH_MLDSA44_ED25519_ALG_NAME   "ssh-mldsa44-ed25519 (at) openssh.com"
     40 
     41 /*
     42  * raw_* functions implement the draft-ietf-lamps-pq-composite-sigs-18
     43  * composite signature scheme. These are exposed (i.e. not static) so
     44  * we can test them separately in unittests/crypto.
     45  */
     46 
     47 int
     48 crypto_sign_mldsa44_ed25519_keygen(uint8_t pk[MLDSA44_ED25519_PK_SZ],
     49     uint8_t sk[MLDSA44_ED25519_SK_SZ])
     50 {
     51 	uint8_t mldsa_seed[MLDSA44_SEEDBYTES];
     52 	uint8_t ed25519_seed[crypto_sign_ed25519_SEEDBYTES];
     53 	int r;
     54 
     55 	arc4random_buf(mldsa_seed, sizeof(mldsa_seed));
     56 	arc4random_buf(ed25519_seed, sizeof(ed25519_seed));
     57 
     58 	r = crypto_sign_mldsa44_ed25519_keygen_seeded(pk, sk, mldsa_seed,
     59 	    ed25519_seed);
     60 	explicit_bzero(mldsa_seed, sizeof(mldsa_seed));
     61 	explicit_bzero(ed25519_seed, sizeof(ed25519_seed));
     62 	return r;
     63 }
     64 
     65 int
     66 crypto_sign_mldsa44_ed25519_keygen_seeded(uint8_t pk[MLDSA44_ED25519_PK_SZ],
     67     uint8_t sk[MLDSA44_ED25519_SK_SZ],
     68     const uint8_t mldsa_seed[MLDSA44_SEEDBYTES],
     69     const uint8_t ed25519_seed[crypto_sign_ed25519_SEEDBYTES])
     70 {
     71 	uint8_t ed25519_pk[MLDSA44_SEEDBYTES];
     72 	uint8_t ed25519_sk[crypto_sign_ed25519_SECRETKEYBYTES];
     73 	uint8_t mldsa_sk[MLDSA44_SECRETKEYBYTES];
     74 	int ret = -1;
     75 
     76 	if (crypto_sign_mldsa44_keypair_seeded(pk, mldsa_sk, mldsa_seed) != 0)
     77 		goto out;
     78 	if (crypto_sign_ed25519_keypair_from_seed(ed25519_pk, ed25519_sk,
     79 	    ed25519_seed) != 0)
     80 		goto out;
     81 
     82 	/* Serialize PK: mldsaPK || ed25519PK */
     83 	memcpy(pk + MLDSA44_PUBLICKEYBYTES, ed25519_pk,
     84 	    crypto_sign_ed25519_SEEDBYTES);
     85 
     86 	/* Serialize SK: mldsaSeed || ed25519Seed */
     87 	memcpy(sk, mldsa_seed, MLDSA44_SEEDBYTES);
     88 	memcpy(sk + MLDSA44_SEEDBYTES, ed25519_seed,
     89 	    crypto_sign_ed25519_SEEDBYTES);
     90 
     91 	/* success */
     92 	ret = 0;
     93  out:
     94 	explicit_bzero(mldsa_sk, sizeof(mldsa_sk));
     95 	explicit_bzero(ed25519_sk, sizeof(ed25519_sk));
     96 	return ret;
     97 }
     98 
     99 static int
    100 construct_m_prime(uint8_t **m_primep, size_t *m_prime_lenp,
    101     const uint8_t *msg, size_t msglen,
    102     const uint8_t *ctx, size_t ctxlen)
    103 {
    104 	int r;
    105 	uint8_t hash[64];
    106 	struct sshbuf *m_prime;
    107 
    108 	*m_primep = NULL;
    109 	*m_prime_lenp = 0;
    110 
    111 	if (ctxlen > 255)
    112 		return SSH_ERR_INVALID_ARGUMENT;
    113 	if ((r = ssh_digest_memory(SSH_DIGEST_SHA512, msg, msglen,
    114 	    hash, sizeof(hash))) != 0)
    115 		return r;
    116 	if ((m_prime = sshbuf_new()) == NULL)
    117 		return SSH_ERR_ALLOC_FAIL;
    118 	if ((r = sshbuf_put(m_prime, COMPOSITE_PREFIX,
    119 	    sizeof(COMPOSITE_PREFIX) - 1)) != 0 ||
    120 	    (r = sshbuf_put(m_prime, COMPOSITE_LABEL,
    121 	    sizeof(COMPOSITE_LABEL) - 1)) != 0 ||
    122 	    (r = sshbuf_put_u8(m_prime, (uint8_t)ctxlen)) != 0 ||
    123 	    (r = sshbuf_put(m_prime, ctx, ctxlen)) != 0 ||
    124 	    (r = sshbuf_put(m_prime, hash, sizeof(hash))) != 0) {
    125 		sshbuf_free(m_prime);
    126 		return r;
    127 	}
    128 	if ((*m_primep = malloc(sshbuf_len(m_prime))) == NULL) {
    129 		sshbuf_free(m_prime);
    130 		return SSH_ERR_ALLOC_FAIL;
    131 	}
    132 	memcpy(*m_primep, sshbuf_ptr(m_prime), sshbuf_len(m_prime));
    133 	*m_prime_lenp = sshbuf_len(m_prime);
    134 	/* success */
    135 	sshbuf_free(m_prime);
    136 	return 0;
    137 }
    138 
    139 int
    140 crypto_sign_mldsa44_ed25519_sign(uint8_t sig[MLDSA44_ED25519_SIG_SZ],
    141     const uint8_t *msg, size_t msglen,
    142     const uint8_t *ctx, size_t ctxlen,
    143     const uint8_t sk[MLDSA44_ED25519_SK_SZ])
    144 {
    145 	uint8_t *m_prime = NULL;
    146 	size_t m_prime_len = 0;
    147 	uint8_t mldsa_sk[MLDSA44_SECRETKEYBYTES];
    148 	uint8_t mldsa_pk_dummy[MLDSA44_PUBLICKEYBYTES];
    149 	uint8_t ed25519_pk[crypto_sign_ed25519_PUBLICKEYBYTES];
    150 	uint8_t ed25519_sk[crypto_sign_ed25519_SECRETKEYBYTES];
    151 	uint8_t mldsa_rnd[MLDSA44_SEEDBYTES];
    152 	unsigned long long smlen;
    153 	int r = -1;
    154 
    155 	if (construct_m_prime(&m_prime, &m_prime_len, msg, msglen,
    156 	    ctx, ctxlen) != 0)
    157 		return -1;
    158 
    159 	/* Expand ML-DSA key from seed */
    160 	if (crypto_sign_mldsa44_keypair_seeded(mldsa_pk_dummy, mldsa_sk, sk) != 0)
    161 		goto out;
    162 
    163 	/* Sign with ML-DSA */
    164 	arc4random_buf(mldsa_rnd, sizeof(mldsa_rnd));
    165 	if (crypto_sign_mldsa44_seeded(sig, m_prime, m_prime_len,
    166 	    (const uint8_t *)COMPOSITE_LABEL, sizeof(COMPOSITE_LABEL) - 1,
    167 	    mldsa_sk, mldsa_rnd) != 0)
    168 		goto out;
    169 
    170 	/* Expand Ed25519 key from seed */
    171 	if (crypto_sign_ed25519_keypair_from_seed(ed25519_pk, ed25519_sk,
    172 	    sk + MLDSA44_SEEDBYTES) != 0)
    173 		goto out;
    174 
    175 	/* Sign with Ed25519 */
    176 	uint8_t *sm = malloc(m_prime_len + crypto_sign_ed25519_BYTES);
    177 	if (sm == NULL)
    178 		goto out;
    179 
    180 	if (crypto_sign_ed25519(sm, &smlen, m_prime, m_prime_len,
    181 	    ed25519_sk) != 0) {
    182 		free(sm);
    183 		goto out;
    184 	}
    185 	memcpy(sig + MLDSA44_SIGBYTES, sm, crypto_sign_ed25519_BYTES);
    186 	free(sm);
    187 
    188 	r = 0;
    189  out:
    190 	free(m_prime);
    191 	explicit_bzero(mldsa_rnd, sizeof(mldsa_rnd));
    192 	explicit_bzero(mldsa_sk, sizeof(mldsa_sk));
    193 	explicit_bzero(ed25519_sk, sizeof(ed25519_sk));
    194 	return r;
    195 }
    196 
    197 int
    198 crypto_sign_mldsa44_ed25519_verify(const uint8_t sig[MLDSA44_ED25519_SIG_SZ],
    199     const uint8_t *msg, size_t msglen,
    200     const uint8_t *ctx, size_t ctxlen,
    201     const uint8_t pk[MLDSA44_ED25519_PK_SZ])
    202 {
    203 	uint8_t *m_prime = NULL;
    204 	size_t m_prime_len = 0;
    205 	uint8_t *sm = NULL, *m = NULL;
    206 	unsigned long long smlen, mlen;
    207 	int r = -1;
    208 
    209 	if (construct_m_prime(&m_prime, &m_prime_len, msg, msglen,
    210 	    ctx, ctxlen) != 0)
    211 		return -1;
    212 
    213 	/* Verify ML-DSA */
    214 	if (crypto_sign_mldsa44_verify(sig, m_prime, m_prime_len,
    215 	    (const uint8_t *)COMPOSITE_LABEL, sizeof(COMPOSITE_LABEL) - 1,
    216 	    pk) != 0)
    217 		goto out;
    218 
    219 	/* Verify Ed25519 */
    220 	smlen = m_prime_len + crypto_sign_ed25519_BYTES;
    221 	mlen = smlen;
    222 	if ((sm = malloc(smlen)) == NULL || (m = malloc(mlen)) == NULL)
    223 		goto out;
    224 	memcpy(sm, sig + MLDSA44_SIGBYTES, crypto_sign_ed25519_BYTES);
    225 	memcpy(sm + crypto_sign_ed25519_BYTES, m_prime, m_prime_len);
    226 
    227 	if (crypto_sign_ed25519_open(m, &mlen, sm, smlen,
    228 	    pk + MLDSA44_PUBLICKEYBYTES) != 0)
    229 		goto out;
    230 	if (mlen != m_prime_len)
    231 		goto out;
    232 
    233 	r = 0;
    234  out:
    235 	free(m_prime);
    236 	free(sm);
    237 	free(m);
    238 	return r;
    239 }
    240 
    241 /* sshkey integration */
    242 
    243 static void
    244 ssh_mldsa44_ed25519_cleanup(struct sshkey *k)
    245 {
    246 	freezero(k->mldsa_ed25519_pk, MLDSA44_ED25519_PK_SZ);
    247 	freezero(k->mldsa_ed25519_sk, MLDSA44_ED25519_SK_SZ);
    248 	k->mldsa_ed25519_pk = NULL;
    249 	k->mldsa_ed25519_sk = NULL;
    250 }
    251 
    252 static int
    253 ssh_mldsa44_ed25519_equal(const struct sshkey *a, const struct sshkey *b)
    254 {
    255 	if (a->mldsa_ed25519_pk == NULL || b->mldsa_ed25519_pk == NULL)
    256 		return 0;
    257 	if (memcmp(a->mldsa_ed25519_pk, b->mldsa_ed25519_pk,
    258 	    MLDSA44_ED25519_PK_SZ) != 0)
    259 		return 0;
    260 	return 1;
    261 }
    262 
    263 static int
    264 ssh_mldsa44_ed25519_serialize_public(const struct sshkey *key, struct sshbuf *b,
    265     enum sshkey_serialize_rep opts)
    266 {
    267 	int r;
    268 
    269 	if (key->mldsa_ed25519_pk == NULL)
    270 		return SSH_ERR_INVALID_ARGUMENT;
    271 	if ((r = sshbuf_put_string(b, key->mldsa_ed25519_pk,
    272 	    MLDSA44_ED25519_PK_SZ)) != 0)
    273 		return r;
    274 
    275 	return 0;
    276 }
    277 
    278 static int
    279 ssh_mldsa44_ed25519_serialize_private(const struct sshkey *key, struct sshbuf *b,
    280     enum sshkey_serialize_rep opts)
    281 {
    282 	int r;
    283 
    284 	if (key->mldsa_ed25519_sk == NULL)
    285 		return SSH_ERR_INVALID_ARGUMENT;
    286 	if (!sshkey_is_cert(key)) {
    287 		if ((r = ssh_mldsa44_ed25519_serialize_public(key,
    288 		    b, opts)) != 0)
    289 			return r;
    290 	}
    291 	if ((r = sshbuf_put_string(b, key->mldsa_ed25519_sk,
    292 	    MLDSA44_ED25519_SK_SZ)) != 0)
    293 		return r;
    294 
    295 	return 0;
    296 }
    297 
    298 static int
    299 ssh_mldsa44_ed25519_deserialize_public(const char *ktype, struct sshbuf *b,
    300     struct sshkey *key)
    301 {
    302 	u_char *pk = NULL;
    303 	size_t len = 0;
    304 	int r;
    305 
    306 	if ((r = sshbuf_get_string(b, &pk, &len)) != 0)
    307 		return r;
    308 	if (len != MLDSA44_ED25519_PK_SZ) {
    309 		freezero(pk, len);
    310 		return SSH_ERR_INVALID_FORMAT;
    311 	}
    312 	key->mldsa_ed25519_pk = pk;
    313 	return 0;
    314 }
    315 
    316 static int
    317 ssh_mldsa44_ed25519_deserialize_private(const char *ktype, struct sshbuf *b,
    318     struct sshkey *key)
    319 {
    320 	int r;
    321 	size_t sklen = 0;
    322 	u_char *sk = NULL;
    323 
    324 	if (!sshkey_is_cert(key)) {
    325 		if ((r = ssh_mldsa44_ed25519_deserialize_public(ktype,
    326 		    b, key)) != 0)
    327 			return r;
    328 	}
    329 	if ((r = sshbuf_get_string(b, &sk, &sklen)) != 0)
    330 		goto out;
    331 	if (sklen != MLDSA44_ED25519_SK_SZ) {
    332 		r = SSH_ERR_INVALID_FORMAT;
    333 		goto out;
    334 	}
    335 	key->mldsa_ed25519_sk = sk;
    336 	sk = NULL; /* transferred */
    337 	r = 0;
    338  out:
    339 	freezero(sk, sklen);
    340 	return r;
    341 }
    342 
    343 static int
    344 ssh_mldsa44_ed25519_generate(struct sshkey *k, int bits)
    345 {
    346 	ssh_mldsa44_ed25519_cleanup(k);
    347 	if ((k->mldsa_ed25519_pk = malloc(MLDSA44_ED25519_PK_SZ)) == NULL ||
    348 	    (k->mldsa_ed25519_sk = malloc(MLDSA44_ED25519_SK_SZ)) == NULL) {
    349 		ssh_mldsa44_ed25519_cleanup(k);
    350 		return SSH_ERR_ALLOC_FAIL;
    351 	}
    352 	if (crypto_sign_mldsa44_ed25519_keygen(k->mldsa_ed25519_pk,
    353 	    k->mldsa_ed25519_sk) != 0) {
    354 		ssh_mldsa44_ed25519_cleanup(k);
    355 		return SSH_ERR_CRYPTO_ERROR;
    356 	}
    357 	return 0;
    358 }
    359 
    360 static int
    361 ssh_mldsa44_ed25519_copy_public(const struct sshkey *from, struct sshkey *to)
    362 {
    363 	if (from->mldsa_ed25519_pk == NULL)
    364 		return SSH_ERR_INVALID_ARGUMENT;
    365 	if ((to->mldsa_ed25519_pk = malloc(MLDSA44_ED25519_PK_SZ)) == NULL)
    366 		return SSH_ERR_ALLOC_FAIL;
    367 	memcpy(to->mldsa_ed25519_pk, from->mldsa_ed25519_pk,
    368 	    MLDSA44_ED25519_PK_SZ);
    369 	return 0;
    370 }
    371 
    372 static int
    373 ssh_mldsa44_ed25519_sign(struct sshkey *key,
    374     u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,
    375     const char *alg, const char *sk_provider, const char *sk_pin,
    376     u_int compat)
    377 {
    378 	u_char sig[MLDSA44_ED25519_SIG_SZ];
    379 	struct sshbuf *b = NULL;
    380 	int r = SSH_ERR_INTERNAL_ERROR;
    381 
    382 	if (lenp != NULL)
    383 		*lenp = 0;
    384 	if (sigp != NULL)
    385 		*sigp = NULL;
    386 
    387 	if (key == NULL ||
    388 	    sshkey_type_plain(key->type) != KEY_MLDSA44_ED25519 ||
    389 	    key->mldsa_ed25519_sk == NULL)
    390 		return SSH_ERR_INVALID_ARGUMENT;
    391 
    392 	if (crypto_sign_mldsa44_ed25519_sign(sig, data, datalen, NULL, 0,
    393 	    key->mldsa_ed25519_sk) != 0) {
    394 		r = SSH_ERR_CRYPTO_ERROR;
    395 		goto out;
    396 	}
    397 
    398 	if ((b = sshbuf_new()) == NULL) {
    399 		r = SSH_ERR_ALLOC_FAIL;
    400 		goto out;
    401 	}
    402 	if ((r = sshbuf_put_cstring(b, SSH_MLDSA44_ED25519_ALG_NAME)) != 0 ||
    403 	    (r = sshbuf_put_string(b, sig, sizeof(sig))) != 0)
    404 		goto out;
    405 
    406 	if (sigp != NULL) {
    407 		if ((*sigp = malloc(sshbuf_len(b))) == NULL) {
    408 			r = SSH_ERR_ALLOC_FAIL;
    409 			goto out;
    410 		}
    411 		memcpy(*sigp, sshbuf_ptr(b), sshbuf_len(b));
    412 	}
    413 	if (lenp != NULL)
    414 		*lenp = sshbuf_len(b);
    415 	r = 0;
    416  out:
    417 	sshbuf_free(b);
    418 	explicit_bzero(sig, sizeof(sig));
    419 	return r;
    420 }
    421 
    422 static int
    423 ssh_mldsa44_ed25519_verify(const struct sshkey *key,
    424     const u_char *sig, size_t siglen, const u_char *data, size_t dlen,
    425     const char *alg, u_int compat, struct sshkey_sig_details **detailsp)
    426 {
    427 	struct sshbuf *b = NULL;
    428 	char *ktype = NULL;
    429 	const u_char *sigblob;
    430 	size_t len;
    431 	int r;
    432 
    433 	if (key == NULL ||
    434 	    sshkey_type_plain(key->type) != KEY_MLDSA44_ED25519 ||
    435 	    key->mldsa_ed25519_pk == NULL ||
    436 	    sig == NULL || siglen == 0)
    437 		return SSH_ERR_INVALID_ARGUMENT;
    438 
    439 	if ((b = sshbuf_from(sig, siglen)) == NULL)
    440 		return SSH_ERR_ALLOC_FAIL;
    441 	if ((r = sshbuf_get_cstring(b, &ktype, NULL)) != 0 ||
    442 	    (r = sshbuf_get_string_direct(b, &sigblob, &len)) != 0)
    443 		goto out;
    444 	if (strcmp(SSH_MLDSA44_ED25519_ALG_NAME, ktype) != 0) {
    445 		r = SSH_ERR_KEY_TYPE_MISMATCH;
    446 		goto out;
    447 	}
    448 	if (sshbuf_len(b) != 0) {
    449 		r = SSH_ERR_UNEXPECTED_TRAILING_DATA;
    450 		goto out;
    451 	}
    452 	if (len != MLDSA44_ED25519_SIG_SZ) {
    453 		r = SSH_ERR_INVALID_FORMAT;
    454 		goto out;
    455 	}
    456 
    457 	if (crypto_sign_mldsa44_ed25519_verify(sigblob, data, dlen, NULL, 0,
    458 	    key->mldsa_ed25519_pk) != 0) {
    459 		r = SSH_ERR_SIGNATURE_INVALID;
    460 		goto out;
    461 	}
    462 
    463 	r = 0;
    464  out:
    465 	sshbuf_free(b);
    466 	free(ktype);
    467 	return r;
    468 }
    469 
    470 const struct sshkey_impl_funcs sshkey_mldsa44_ed25519_funcs = {
    471 	/* .size = */		NULL,
    472 	/* .alloc = */		NULL,
    473 	/* .cleanup = */	ssh_mldsa44_ed25519_cleanup,
    474 	/* .equal = */		ssh_mldsa44_ed25519_equal,
    475 	/* .ssh_serialize_public = */ ssh_mldsa44_ed25519_serialize_public,
    476 	/* .ssh_deserialize_public = */ ssh_mldsa44_ed25519_deserialize_public,
    477 	/* .ssh_serialize_private = */ ssh_mldsa44_ed25519_serialize_private,
    478 	/* .ssh_deserialize_private = */ ssh_mldsa44_ed25519_deserialize_private,
    479 	/* .generate = */	ssh_mldsa44_ed25519_generate,
    480 	/* .copy_public = */	ssh_mldsa44_ed25519_copy_public,
    481 	/* .sign = */		ssh_mldsa44_ed25519_sign,
    482 	/* .verify = */		ssh_mldsa44_ed25519_verify,
    483 };
    484 
    485 const struct sshkey_impl sshkey_mldsa44_ed25519_impl = {
    486 	/* .name = */		"ssh-mldsa44-ed25519 (at) openssh.com",
    487 	/* .shortname = */	"MLDSA44-ED25519",
    488 	/* .sigalg = */		NULL,
    489 	/* .type = */		KEY_MLDSA44_ED25519,
    490 	/* .nid = */		0,
    491 	/* .cert = */		0,
    492 	/* .sigonly = */	0,
    493 	/* .keybits = */	256,
    494 	/* .funcs = */		&sshkey_mldsa44_ed25519_funcs,
    495 };
    496 
    497 const struct sshkey_impl sshkey_mldsa44_ed25519_cert_impl = {
    498 	/* .name = */		"ssh-mldsa44-ed25519-cert-v01 (at) openssh.com",
    499 	/* .shortname = */	"MLDSA44-ED25519-CERT",
    500 	/* .sigalg = */		NULL,
    501 	/* .type = */		KEY_MLDSA44_ED25519_CERT,
    502 	/* .nid = */		0,
    503 	/* .cert = */		1,
    504 	/* .sigonly = */	0,
    505 	/* .keybits = */	256,
    506 	/* .funcs = */		&sshkey_mldsa44_ed25519_funcs,
    507 };
    508