Home | History | Annotate | Line # | Download | only in dist
      1 /*	$NetBSD: kexmlkem768x25519.c,v 1.4 2025/04/09 15:49:32 christos Exp $	*/
      2 /* $OpenBSD: kexmlkem768x25519.c,v 1.2 2024/10/27 02:06:59 djm Exp $ */
      3 
      4 /*
      5  * Copyright (c) 2023 Markus Friedl.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 #include "includes.h"
     28 __RCSID("$NetBSD: kexmlkem768x25519.c,v 1.4 2025/04/09 15:49:32 christos Exp $");
     29 
     30 #include <sys/types.h>
     31 
     32 #include <stdio.h>
     33 #include <stdint.h>
     34 #include <stdbool.h>
     35 #include <string.h>
     36 #include <signal.h>
     37 #include <endian.h>
     38 
     39 #include "sshkey.h"
     40 #include "kex.h"
     41 #include "sshbuf.h"
     42 #include "digest.h"
     43 #include "ssherr.h"
     44 #include "log.h"
     45 
     46 #include "libcrux_mlkem768_sha3.h"
     47 
     48 int
     49 kex_kem_mlkem768x25519_keypair(struct kex *kex)
     50 {
     51 	struct sshbuf *buf = NULL;
     52 	u_char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN], *cp = NULL;
     53 	size_t need;
     54 	int r = SSH_ERR_INTERNAL_ERROR;
     55 	struct libcrux_mlkem768_keypair keypair;
     56 
     57 	if ((buf = sshbuf_new()) == NULL)
     58 		return SSH_ERR_ALLOC_FAIL;
     59 	need = crypto_kem_mlkem768_PUBLICKEYBYTES + CURVE25519_SIZE;
     60 	if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
     61 		goto out;
     62 	arc4random_buf(rnd, sizeof(rnd));
     63 	keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd);
     64 	memcpy(cp, keypair.pk.value, crypto_kem_mlkem768_PUBLICKEYBYTES);
     65 	memcpy(kex->mlkem768_client_key, keypair.sk.value,
     66 	    sizeof(kex->mlkem768_client_key));
     67 #ifdef DEBUG_KEXECDH
     68 	dump_digest("client public key mlkem768:", cp,
     69 	    crypto_kem_mlkem768_PUBLICKEYBYTES);
     70 #endif
     71 	cp += crypto_kem_mlkem768_PUBLICKEYBYTES;
     72 	kexc25519_keygen(kex->c25519_client_key, cp);
     73 #ifdef DEBUG_KEXECDH
     74 	dump_digest("client public key c25519:", cp, CURVE25519_SIZE);
     75 #endif
     76 	/* success */
     77 	r = 0;
     78 	kex->client_pub = buf;
     79 	buf = NULL;
     80  out:
     81 	explicit_bzero(&keypair, sizeof(keypair));
     82 	explicit_bzero(rnd, sizeof(rnd));
     83 	sshbuf_free(buf);
     84 	return r;
     85 }
     86 
     87 int
     88 kex_kem_mlkem768x25519_enc(struct kex *kex,
     89    const struct sshbuf *client_blob, struct sshbuf **server_blobp,
     90    struct sshbuf **shared_secretp)
     91 {
     92 	struct sshbuf *server_blob = NULL;
     93 	struct sshbuf *buf = NULL;
     94 	const u_char *client_pub;
     95 	u_char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN];
     96 	u_char server_pub[CURVE25519_SIZE], server_key[CURVE25519_SIZE];
     97 	u_char hash[SSH_DIGEST_MAX_LENGTH];
     98 	size_t need;
     99 	int r = SSH_ERR_INTERNAL_ERROR;
    100 	struct libcrux_mlkem768_enc_result enc;
    101 	struct libcrux_mlkem768_pk mlkem_pub;
    102 
    103 	*server_blobp = NULL;
    104 	*shared_secretp = NULL;
    105 	memset(&mlkem_pub, 0, sizeof(mlkem_pub));
    106 
    107 	/* client_blob contains both KEM and ECDH client pubkeys */
    108 	need = crypto_kem_mlkem768_PUBLICKEYBYTES + CURVE25519_SIZE;
    109 	if (sshbuf_len(client_blob) != need) {
    110 		r = SSH_ERR_SIGNATURE_INVALID;
    111 		goto out;
    112 	}
    113 	client_pub = sshbuf_ptr(client_blob);
    114 #ifdef DEBUG_KEXECDH
    115 	dump_digest("client public key mlkem768:", client_pub,
    116 	    crypto_kem_mlkem768_PUBLICKEYBYTES);
    117 	dump_digest("client public key 25519:",
    118 	    client_pub + crypto_kem_mlkem768_PUBLICKEYBYTES,
    119 	    CURVE25519_SIZE);
    120 #endif
    121 	/* check public key validity */
    122 	memcpy(mlkem_pub.value, client_pub, crypto_kem_mlkem768_PUBLICKEYBYTES);
    123 	if (!libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub)) {
    124 		r = SSH_ERR_SIGNATURE_INVALID;
    125 		goto out;
    126 	}
    127 
    128 	/* allocate buffer for concatenation of KEM key and ECDH shared key */
    129 	/* the buffer will be hashed and the result is the shared secret */
    130 	if ((buf = sshbuf_new()) == NULL) {
    131 		r = SSH_ERR_ALLOC_FAIL;
    132 		goto out;
    133 	}
    134 	/* allocate space for encrypted KEM key and ECDH pub key */
    135 	if ((server_blob = sshbuf_new()) == NULL) {
    136 		r = SSH_ERR_ALLOC_FAIL;
    137 		goto out;
    138 	}
    139 	/* generate and encrypt KEM key with client key */
    140 	arc4random_buf(rnd, sizeof(rnd));
    141 	enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd);
    142 	/* generate ECDH key pair, store server pubkey after ciphertext */
    143 	kexc25519_keygen(server_key, server_pub);
    144 	if ((r = sshbuf_put(buf, enc.snd, sizeof(enc.snd))) != 0 ||
    145 	    (r = sshbuf_put(server_blob, enc.fst.value, sizeof(enc.fst.value))) != 0 ||
    146 	    (r = sshbuf_put(server_blob, server_pub, sizeof(server_pub))) != 0)
    147 		goto out;
    148 	/* append ECDH shared key */
    149 	client_pub += crypto_kem_mlkem768_PUBLICKEYBYTES;
    150 	if ((r = kexc25519_shared_key_ext(server_key, client_pub, buf, 1)) < 0)
    151 		goto out;
    152 	if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
    153 		goto out;
    154 #ifdef DEBUG_KEXECDH
    155 	dump_digest("server public key 25519:", server_pub, CURVE25519_SIZE);
    156 	dump_digest("server cipher text:",
    157 	    enc.fst.value, sizeof(enc.fst.value));
    158 	dump_digest("server kem key:", enc.snd, sizeof(enc.snd));
    159 	dump_digest("concatenation of KEM key and ECDH shared key:",
    160 	    sshbuf_ptr(buf), sshbuf_len(buf));
    161 #endif
    162 	/* string-encoded hash is resulting shared secret */
    163 	sshbuf_reset(buf);
    164 	if ((r = sshbuf_put_string(buf, hash,
    165 	    ssh_digest_bytes(kex->hash_alg))) != 0)
    166 		goto out;
    167 #ifdef DEBUG_KEXECDH
    168 	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
    169 #endif
    170 	/* success */
    171 	r = 0;
    172 	*server_blobp = server_blob;
    173 	*shared_secretp = buf;
    174 	server_blob = NULL;
    175 	buf = NULL;
    176  out:
    177 	explicit_bzero(hash, sizeof(hash));
    178 	explicit_bzero(server_key, sizeof(server_key));
    179 	explicit_bzero(rnd, sizeof(rnd));
    180 	explicit_bzero(&enc, sizeof(enc));
    181 	sshbuf_free(server_blob);
    182 	sshbuf_free(buf);
    183 	return r;
    184 }
    185 
    186 int
    187 kex_kem_mlkem768x25519_dec(struct kex *kex,
    188     const struct sshbuf *server_blob, struct sshbuf **shared_secretp)
    189 {
    190 	struct sshbuf *buf = NULL;
    191 	u_char mlkem_key[crypto_kem_mlkem768_BYTES];
    192 	const u_char *ciphertext, *server_pub;
    193 	u_char hash[SSH_DIGEST_MAX_LENGTH];
    194 	size_t need;
    195 	int r;
    196 	struct libcrux_mlkem768_sk mlkem_priv;
    197 	struct libcrux_mlkem768_ciphertext mlkem_ciphertext;
    198 
    199 	*shared_secretp = NULL;
    200 	memset(&mlkem_priv, 0, sizeof(mlkem_priv));
    201 	memset(&mlkem_ciphertext, 0, sizeof(mlkem_ciphertext));
    202 
    203 	need = crypto_kem_mlkem768_CIPHERTEXTBYTES + CURVE25519_SIZE;
    204 	if (sshbuf_len(server_blob) != need) {
    205 		r = SSH_ERR_SIGNATURE_INVALID;
    206 		goto out;
    207 	}
    208 	ciphertext = sshbuf_ptr(server_blob);
    209 	server_pub = ciphertext + crypto_kem_mlkem768_CIPHERTEXTBYTES;
    210 	/* hash concatenation of KEM key and ECDH shared key */
    211 	if ((buf = sshbuf_new()) == NULL) {
    212 		r = SSH_ERR_ALLOC_FAIL;
    213 		goto out;
    214 	}
    215 	memcpy(mlkem_priv.value, kex->mlkem768_client_key,
    216 	    sizeof(kex->mlkem768_client_key));
    217 	memcpy(mlkem_ciphertext.value, ciphertext,
    218 	    sizeof(mlkem_ciphertext.value));
    219 #ifdef DEBUG_KEXECDH
    220 	dump_digest("server cipher text:", mlkem_ciphertext.value,
    221 	    sizeof(mlkem_ciphertext.value));
    222 	dump_digest("server public key c25519:", server_pub, CURVE25519_SIZE);
    223 #endif
    224 	libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv,
    225 	    &mlkem_ciphertext, mlkem_key);
    226 	if ((r = sshbuf_put(buf, mlkem_key, sizeof(mlkem_key))) != 0)
    227 		goto out;
    228 	if ((r = kexc25519_shared_key_ext(kex->c25519_client_key, server_pub,
    229 	    buf, 1)) < 0)
    230 		goto out;
    231 	if ((r = ssh_digest_buffer(kex->hash_alg, buf,
    232 	    hash, sizeof(hash))) != 0)
    233 		goto out;
    234 #ifdef DEBUG_KEXECDH
    235 	dump_digest("client kem key:", mlkem_key, sizeof(mlkem_key));
    236 	dump_digest("concatenation of KEM key and ECDH shared key:",
    237 	    sshbuf_ptr(buf), sshbuf_len(buf));
    238 #endif
    239 	sshbuf_reset(buf);
    240 	if ((r = sshbuf_put_string(buf, hash,
    241 	    ssh_digest_bytes(kex->hash_alg))) != 0)
    242 		goto out;
    243 #ifdef DEBUG_KEXECDH
    244 	dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
    245 #endif
    246 	/* success */
    247 	r = 0;
    248 	*shared_secretp = buf;
    249 	buf = NULL;
    250  out:
    251 	explicit_bzero(hash, sizeof(hash));
    252 	explicit_bzero(&mlkem_priv, sizeof(mlkem_priv));
    253 	explicit_bzero(&mlkem_ciphertext, sizeof(mlkem_ciphertext));
    254 	explicit_bzero(mlkem_key, sizeof(mlkem_key));
    255 	sshbuf_free(buf);
    256 	return r;
    257 }
    258