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