#include <sys/types.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <endian.h>
#include "sshkey.h"
#include "kex.h"
#include "sshbuf.h"
#include "digest.h"
#include "ssherr.h"
int
kex_kem_mlkem768ecdh_keypair(struct kex *kex)
{
struct sshbuf *buf = NULL;
struct sshbuf *ec_blob = NULL;
EC_KEY *client_key = NULL;
const EC_GROUP *group;
const EC_POINT *public_key;
u_char *cp = NULL;
size_t need;
int r = SSH_ERR_INTERNAL_ERROR;
if ((buf = sshbuf_new()) == NULL)
return SSH_ERR_ALLOC_FAIL;
need = MLKEM768_PUBLICKEYBYTES;
if ((r = sshbuf_reserve(buf, need, &cp)) != 0)
goto out;
if (crypto_kem_mlkem768_keypair(cp, kex->mlkem768_client_key) != 0) {
r = SSH_ERR_INTERNAL_ERROR;
goto out;
}
#ifdef DEBUG_KEXECDH
dump_digest("client public key mlkem768:", cp,
MLKEM768_PUBLICKEYBYTES);
#endif
if ((client_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (EC_KEY_generate_key(client_key) != 1) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
group = EC_KEY_get0_group(client_key);
public_key = EC_KEY_get0_public_key(client_key);
if ((ec_blob = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put_ec(ec_blob, public_key, group)) != 0 ||
(r = sshbuf_get_u32(ec_blob, NULL)) != 0 ||
(r = sshbuf_putb(buf, ec_blob)) != 0)
goto out;
#ifdef DEBUG_KEXECDH
fputs("client private key EC:\n", stderr);
sshkey_dump_ec_key(client_key);
#endif
r = 0;
kex->ec_client_key = client_key;
kex->ec_group = group;
client_key = NULL;
kex->client_pub = buf;
buf = NULL;
out:
sshbuf_free(buf);
sshbuf_free(ec_blob);
EC_KEY_free(client_key);
return r;
}
int
kex_kem_mlkem768ecdh_enc(struct kex *kex,
const struct sshbuf *client_blob, struct sshbuf **server_blobp,
struct sshbuf **shared_secretp)
{
const EC_GROUP *group;
const EC_POINT *pub_key;
EC_KEY *server_key = NULL;
struct sshbuf *ec_pub = NULL;
struct sshbuf *ec_blob = NULL;
struct sshbuf *ec_shared = NULL;
struct sshbuf *server_blob = NULL;
struct sshbuf *buf = NULL;
const u_char *client_pub;
u_char hash[SSH_DIGEST_MAX_LENGTH];
u_char ct[MLKEM768_CIPHERTEXTBYTES];
u_char shared_secret[MLKEM768_BYTES];
size_t need;
int r = SSH_ERR_INTERNAL_ERROR;
*server_blobp = NULL;
*shared_secretp = NULL;
need = MLKEM768_PUBLICKEYBYTES;
if (sshbuf_len(client_blob) <= need) {
r = SSH_ERR_SIGNATURE_INVALID;
goto out;
}
client_pub = sshbuf_ptr(client_blob);
#ifdef DEBUG_KEXECDH
dump_digest("client public key mlkem768:", client_pub,
MLKEM768_PUBLICKEYBYTES);
#endif
if ((buf = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((server_blob = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (crypto_kem_mlkem768_enc(ct, shared_secret, client_pub) != 0) {
r = SSH_ERR_INTERNAL_ERROR;
goto out;
}
if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0 ||
(r = sshbuf_put(server_blob, ct, sizeof(ct))) != 0)
goto out;
client_pub += MLKEM768_PUBLICKEYBYTES;
if ((ec_pub = sshbuf_from(client_pub, sshbuf_len(client_blob) -
MLKEM768_PUBLICKEYBYTES)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((server_key = EC_KEY_new_by_curve_name(kex->ec_nid)) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (EC_KEY_generate_key(server_key) != 1) {
r = SSH_ERR_LIBCRYPTO_ERROR;
goto out;
}
group = EC_KEY_get0_group(server_key);
#ifdef DEBUG_KEXECDH
fputs("server private key EC:\n", stderr);
sshkey_dump_ec_key(server_key);
#endif
pub_key = EC_KEY_get0_public_key(server_key);
if ((ec_blob = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = sshbuf_put_ec(ec_blob, pub_key, group)) != 0 ||
(r = sshbuf_get_u32(ec_blob, NULL)) != 0 ||
(r = sshbuf_putb(server_blob, ec_blob)) != 0)
goto out;
if ((r = kex_ecdh_dec_key_group(kex, ec_pub, server_key, group,
&ec_shared)) != 0 ||
(r = sshbuf_putb(buf, ec_shared)) != 0)
goto out;
if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0)
goto out;
#ifdef DEBUG_KEXECDH
dump_digest("server cipher text:", ct, sizeof(ct));
dump_digest("server kem key:", shared_secret, sizeof(shared_secret));
dump_digest("concatenation of KEM key and ECDH shared key:",
sshbuf_ptr(buf), sshbuf_len(buf));
#endif
sshbuf_reset(buf);
if ((r = sshbuf_put_string(buf, hash,
ssh_digest_bytes(kex->hash_alg))) != 0)
goto out;
#ifdef DEBUG_KEXECDH
dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
#endif
r = 0;
*server_blobp = server_blob;
*shared_secretp = buf;
server_blob = NULL;
buf = NULL;
out:
explicit_bzero(hash, sizeof(hash));
explicit_bzero(shared_secret, sizeof(shared_secret));
EC_KEY_free(server_key);
sshbuf_free(ec_pub);
sshbuf_free(ec_blob);
sshbuf_free(ec_shared);
sshbuf_free(server_blob);
sshbuf_free(buf);
return r;
}
int
kex_kem_mlkem768ecdh_dec(struct kex *kex,
const struct sshbuf *server_blob, struct sshbuf **shared_secretp)
{
struct sshbuf *buf = NULL;
struct sshbuf *ec_pub = NULL;
struct sshbuf *ec_shared = NULL;
u_char shared_secret[MLKEM768_BYTES];
const u_char *ciphertext, *server_pub;
u_char hash[SSH_DIGEST_MAX_LENGTH];
size_t need;
int r;
*shared_secretp = NULL;
need = MLKEM768_CIPHERTEXTBYTES;
if (sshbuf_len(server_blob) <= need) {
r = SSH_ERR_SIGNATURE_INVALID;
goto out;
}
ciphertext = sshbuf_ptr(server_blob);
server_pub = ciphertext + MLKEM768_CIPHERTEXTBYTES;
#ifdef DEBUG_KEXECDH
dump_digest("server cipher text (dec):", ciphertext,
MLKEM768_CIPHERTEXTBYTES);
#endif
if ((buf = sshbuf_new()) == NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if (crypto_kem_mlkem768_dec(shared_secret, ciphertext,
kex->mlkem768_client_key) != 0) {
r = SSH_ERR_INTERNAL_ERROR;
goto out;
}
if ((r = sshbuf_put(buf, shared_secret, sizeof(shared_secret))) != 0)
goto out;
if ((ec_pub = sshbuf_from(server_pub, sshbuf_len(server_blob) - need))
== NULL) {
r = SSH_ERR_ALLOC_FAIL;
goto out;
}
if ((r = kex_ecdh_dec(kex, ec_pub, &ec_shared)) != 0 ||
(r = sshbuf_putb(buf, ec_shared)) != 0)
goto out;
if ((r = ssh_digest_buffer(kex->hash_alg, buf,
hash, sizeof(hash))) != 0)
goto out;
#ifdef DEBUG_KEXECDH
dump_digest("client kem key:", shared_secret, sizeof(shared_secret));
dump_digest("concatenation of KEM key and ECDH shared key:",
sshbuf_ptr(buf), sshbuf_len(buf));
#endif
sshbuf_reset(buf);
if ((r = sshbuf_put_string(buf, hash,
ssh_digest_bytes(kex->hash_alg))) != 0)
goto out;
#ifdef DEBUG_KEXECDH
dump_digest("encoded shared secret:", sshbuf_ptr(buf), sshbuf_len(buf));
#endif
r = 0;
*shared_secretp = buf;
buf = NULL;
out:
explicit_bzero(hash, sizeof(hash));
explicit_bzero(shared_secret, sizeof(shared_secret));
EC_KEY_free(kex->ec_client_key);
kex->ec_client_key = NULL;
sshbuf_free(ec_pub);
sshbuf_free(ec_shared);
sshbuf_free(buf);
return r;
}