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