From d6c153ae721516435f50c85f054005d35e17b0fa Mon Sep 17 00:00:00 2001 From: Dmitry Belyavskiy Date: Mon, 3 Nov 2025 12:56:38 +0100 Subject: [PATCH] Implement mlkem768nistp256-sha256 and mlkem1024nistp384-sha384 KEX methods Resolves: RHEL-70824 --- openssh-10.0-mlkem-nist.patch | 987 ++++++++++++++++++++++++++++++++++ openssh.spec | 8 +- 2 files changed, 994 insertions(+), 1 deletion(-) create mode 100644 openssh-10.0-mlkem-nist.patch diff --git a/openssh-10.0-mlkem-nist.patch b/openssh-10.0-mlkem-nist.patch new file mode 100644 index 0000000..22b8ec6 --- /dev/null +++ b/openssh-10.0-mlkem-nist.patch @@ -0,0 +1,987 @@ +diff --git a/crypto_api.h b/crypto_api.h +index 8bbc3a08a..6940bbf0e 100644 +--- a/crypto_api.h ++++ b/crypto_api.h +@@ -58,4 +58,8 @@ int crypto_kem_sntrup761_keypair(unsigned char *pk, unsigned char *sk); + #define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088 + #define crypto_kem_mlkem768_BYTES 32 + ++#define crypto_kem_mlkem1024_PUBLICKEYBYTES 1568 ++#define crypto_kem_mlkem1024_SECRETKEYBYTES 3168 ++#define crypto_kem_mlkem1024_CIPHERTEXTBYTES 1568 ++ + #endif /* crypto_api_h */ +diff --git a/kex-names.c b/kex-names.c +index 36a953ab2..a728e0b38 100644 +--- a/kex-names.c ++++ b/kex-names.c +@@ -90,6 +90,10 @@ static const struct kexalg kexalgs[] = { + #ifdef USE_MLKEM768X25519 + { KEX_MLKEM768X25519_SHA256, KEX_KEM_MLKEM768X25519_SHA256, 0, + SSH_DIGEST_SHA256 }, ++ { KEX_MLKEM768NISTP256_SHA256, KEX_KEM_MLKEM768NISTP256_SHA256, 0, ++ SSH_DIGEST_SHA256 }, ++ { KEX_MLKEM1024NISTP384_SHA384, KEX_KEM_MLKEM1024NISTP384_SHA384, 0, ++ SSH_DIGEST_SHA384 }, + #endif + #endif /* HAVE_EVP_SHA256 || !WITH_OPENSSL */ + { NULL, 0, -1, -1}, +@@ -129,7 +133,9 @@ kex_alg_list_internal(char sep, const struct kexalg *algs) + const struct kexalg *k; + + for (k = algs; k->name != NULL; k++) { +- if (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0 ++ if ( (strcmp(k->name, KEX_MLKEM768X25519_SHA256) == 0 ++ || strcmp(k->name, KEX_MLKEM768NISTP256_SHA256) == 0 ++ || strcmp(k->name, KEX_MLKEM1024NISTP384_SHA384) == 0) + && !is_mlkem768_available()) + continue; + if (ret != NULL) +@@ -163,7 +169,9 @@ kex_alg_by_name(const char *name) + { + const struct kexalg *k; + +- if (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0 ++ if ( (strcmp(name, KEX_MLKEM768X25519_SHA256) == 0 ++ || strcmp(name, KEX_MLKEM768NISTP256_SHA256) == 0 ++ || strcmp(name, KEX_MLKEM1024NISTP384_SHA384) == 0) + && !is_mlkem768_available()) + return NULL; + +diff --git a/kex.c b/kex.c +index ce6a7b818..efaf5749c 100644 +--- a/kex.c ++++ b/kex.c +@@ -753,6 +753,7 @@ kex_free(struct kex *kex) + #ifdef OPENSSL_HAS_ECC + EC_KEY_free(kex->ec_client_key); + #endif /* OPENSSL_HAS_ECC */ ++ EVP_PKEY_free(kex->ec_hybrid_client_key); + #endif /* WITH_OPENSSL */ + for (mode = 0; mode < MODE_MAX; mode++) { + kex_free_newkeys(kex->newkeys[mode]); +diff --git a/kex.h b/kex.h +index 48f3bb875..50a461ddb 100644 +--- a/kex.h ++++ b/kex.h +@@ -72,6 +72,8 @@ + #define KEX_SNTRUP761X25519_SHA512 "sntrup761x25519-sha512" + #define KEX_SNTRUP761X25519_SHA512_OLD "sntrup761x25519-sha512@openssh.com" + #define KEX_MLKEM768X25519_SHA256 "mlkem768x25519-sha256" ++#define KEX_MLKEM768NISTP256_SHA256 "mlkem768nistp256-sha256" ++#define KEX_MLKEM1024NISTP384_SHA384 "mlkem1024nistp384-sha384" + + #define COMP_NONE 0 + #define COMP_DELAYED 2 +@@ -110,6 +112,8 @@ enum kex_exchange { + KEX_C25519_SHA256, + KEX_KEM_SNTRUP761X25519_SHA512, + KEX_KEM_MLKEM768X25519_SHA256, ++ KEX_KEM_MLKEM768NISTP256_SHA256, ++ KEX_KEM_MLKEM1024NISTP384_SHA384, + #ifdef GSSAPI + KEX_GSS_GRP1_SHA1, + KEX_GSS_GRP14_SHA1, +@@ -206,6 +210,9 @@ struct kex { + u_char sntrup761_client_key[crypto_kem_sntrup761_SECRETKEYBYTES]; /* KEM */ + u_char mlkem768_client_key[crypto_kem_mlkem768_SECRETKEYBYTES]; /* KEM */ + struct sshbuf *client_pub; ++ /* FIXME */ ++ EVP_PKEY *ec_hybrid_client_key; /* NIST hybrids */ ++ u_char mlkem1024_client_key[crypto_kem_mlkem1024_SECRETKEYBYTES]; /* ML-KEM 1024 + NIST */ + }; + + int kex_name_valid(const char *); +@@ -287,6 +294,18 @@ int kex_kem_mlkem768x25519_enc(struct kex *, const struct sshbuf *, + int kex_kem_mlkem768x25519_dec(struct kex *, const struct sshbuf *, + struct sshbuf **); + ++int kex_kem_mlkem768nistp256_keypair(struct kex *); ++int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *, ++ struct sshbuf **, struct sshbuf **); ++int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *, ++ struct sshbuf **); ++ ++int kex_kem_mlkem1024nistp384_keypair(struct kex *); ++int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *, ++ struct sshbuf **, struct sshbuf **); ++int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *, ++ struct sshbuf **); ++ + int kex_dh_keygen(struct kex *); + int kex_dh_compute_key(struct kex *, BIGNUM *, struct sshbuf *); + +diff --git a/kexgen.c b/kexgen.c +index eecdceba2..6e910f84f 100644 +--- a/kexgen.c ++++ b/kexgen.c +@@ -139,6 +139,22 @@ kex_gen_client(struct ssh *ssh) + r = kex_kem_mlkem768x25519_keypair(kex); + } + break; ++ case KEX_KEM_MLKEM768NISTP256_SHA256: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768nistp256 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768nistp256_keypair(kex); ++ } ++ break; ++ case KEX_KEM_MLKEM1024NISTP384_SHA384: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem1024nistp384 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem1024nistp384_keypair(kex); ++ } ++ break; + default: + r = SSH_ERR_INVALID_ARGUMENT; + break; +@@ -230,6 +246,24 @@ input_kex_gen_reply(int type, u_int32_t seq, struct ssh *ssh) + &shared_secret); + } + break; ++ case KEX_KEM_MLKEM768NISTP256_SHA256: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768nistp256 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768nistp256_dec(kex, server_blob, ++ &shared_secret); ++ } ++ break; ++ case KEX_KEM_MLKEM1024NISTP384_SHA384: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem1024nistp384 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem1024nistp384_dec(kex, server_blob, ++ &shared_secret); ++ } ++ break; + default: + r = SSH_ERR_INVALID_ARGUMENT; + break; +@@ -283,6 +317,8 @@ out: + sizeof(kex->sntrup761_client_key)); + explicit_bzero(kex->mlkem768_client_key, + sizeof(kex->mlkem768_client_key)); ++ explicit_bzero(kex->mlkem1024_client_key, ++ sizeof(kex->mlkem1024_client_key)); + sshbuf_free(server_host_key_blob); + free(signature); + sshbuf_free(tmp); +@@ -369,6 +405,24 @@ input_kex_gen_init(int type, u_int32_t seq, struct ssh *ssh) + &server_pubkey, &shared_secret); + } + break; ++ case KEX_KEM_MLKEM768NISTP256_SHA256: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem768nistp256 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem768nistp256_enc(kex, client_pubkey, ++ &server_pubkey, &shared_secret); ++ } ++ break; ++ case KEX_KEM_MLKEM1024NISTP384_SHA384: ++ if (FIPS_mode()) { ++ logit_f("Key exchange type mlkem1024nistp384 is not allowed in FIPS mode"); ++ r = SSH_ERR_INVALID_ARGUMENT; ++ } else { ++ r = kex_kem_mlkem1024nistp384_enc(kex, client_pubkey, ++ &server_pubkey, &shared_secret); ++ } ++ break; + default: + r = SSH_ERR_INVALID_ARGUMENT; + break; +diff --git a/kexmlkem768x25519.c b/kexmlkem768x25519.c +index 670049dcd..463d18771 100644 +--- a/kexmlkem768x25519.c ++++ b/kexmlkem768x25519.c +@@ -53,14 +53,15 @@ + #include + + static int +-mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf) ++mlkem_keypair_gen(const char *algname, unsigned char *pubkeybuf, size_t pubkey_size, ++ unsigned char *privkeybuf, size_t privkey_size) + { + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + int ret = SSH_ERR_INTERNAL_ERROR; +- size_t pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES; ++ size_t got_pub_size = pubkey_size, got_priv_size = privkey_size; + +- ctx = EVP_PKEY_CTX_new_from_name(NULL, "mlkem768", NULL); ++ ctx = EVP_PKEY_CTX_new_from_name(NULL, algname, NULL); + if (ctx == NULL) { + ret = SSH_ERR_LIBCRYPTO_ERROR; + goto err; +@@ -72,17 +73,23 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf) + goto err; + } + +- if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &pubkey_size) <= 0 +- || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &privkey_size) <= 0) { ++ if (EVP_PKEY_get_raw_public_key(pkey, NULL, &got_pub_size) <= 0 ++ || EVP_PKEY_get_raw_private_key(pkey, NULL, &got_priv_size) <= 0) { ++ ret = SSH_ERR_LIBCRYPTO_ERROR; ++ goto err; ++ } ++ ++ if (privkey_size != got_priv_size || pubkey_size != got_pub_size) { + ret = SSH_ERR_LIBCRYPTO_ERROR; + goto err; + } + +- if (privkey_size != crypto_kem_mlkem768_SECRETKEYBYTES +- || pubkey_size != crypto_kem_mlkem768_PUBLICKEYBYTES) { ++ if (EVP_PKEY_get_raw_public_key(pkey, pubkeybuf, &got_pub_size) <= 0 ++ || EVP_PKEY_get_raw_private_key(pkey, privkeybuf, &got_priv_size) <= 0) { + ret = SSH_ERR_LIBCRYPTO_ERROR; + goto err; + } ++ + ret = 0; + + err: +@@ -94,16 +101,40 @@ mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf) + } + + static int +-mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out) ++mlkem768_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf) ++{ ++ return mlkem_keypair_gen("mlkem768", pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES, ++ privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES); ++} ++ ++static int ++mlkem1024_keypair_gen(unsigned char *pubkeybuf, unsigned char *privkeybuf) ++{ ++ return mlkem_keypair_gen("mlkem1024", pubkeybuf, crypto_kem_mlkem1024_PUBLICKEYBYTES, ++ privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES); ++} ++ ++static int ++mlkem_encap_secret(const char *mlkem_alg, const u_char *pubkeybuf, u_char *secret, u_char *out) + { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int r = SSH_ERR_INTERNAL_ERROR; +- size_t outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES, +- secretlen = crypto_kem_mlkem768_BYTES; ++ size_t outlen, expected_outlen, publen, secretlen = crypto_kem_mlkem768_BYTES; ++ ++ if (strcmp(mlkem_alg, "mlkem768") == 0) { ++ outlen = crypto_kem_mlkem768_CIPHERTEXTBYTES; ++ publen = crypto_kem_mlkem768_PUBLICKEYBYTES; ++ } else if (strcmp(mlkem_alg, "mlkem1024") == 0) { ++ outlen = crypto_kem_mlkem1024_CIPHERTEXTBYTES; ++ publen = crypto_kem_mlkem1024_PUBLICKEYBYTES; ++ } else ++ return r; + +- pkey = EVP_PKEY_new_raw_public_key_ex(NULL, "mlkem768", NULL, +- pubkeybuf, crypto_kem_mlkem768_PUBLICKEYBYTES); ++ expected_outlen = outlen; ++ ++ pkey = EVP_PKEY_new_raw_public_key_ex(NULL, mlkem_alg, NULL, ++ pubkeybuf, publen); + if (pkey == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto err; +@@ -112,9 +143,9 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out) + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); + if (ctx == NULL + || EVP_PKEY_encapsulate_init(ctx, NULL) <= 0 +- || EVP_PKEY_encapsulate(ctx, out, &outlen, secret, &secretlen) <= 0 ++ || EVP_PKEY_encapsulate(ctx, out, &expected_outlen, secret, &secretlen) <= 0 + || secretlen != crypto_kem_mlkem768_BYTES +- || outlen != crypto_kem_mlkem768_CIPHERTEXTBYTES) { ++ || outlen != expected_outlen) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto err; + } +@@ -130,16 +161,30 @@ mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out) + } + + static int +-mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret) ++mlkem768_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out) ++{ ++ return mlkem_encap_secret("mlkem768", pubkeybuf, secret, out); ++} ++ ++static int ++mlkem1024_encap_secret(const u_char *pubkeybuf, u_char *secret, u_char *out) ++{ ++ return mlkem_encap_secret("mlkem1024", pubkeybuf, secret, out); ++} ++ ++static int ++mlkem_decap_secret(const char *algname, ++ const u_char *privkeybuf, size_t privkey_len, ++ const u_char *wrapped, size_t wrapped_len, ++ u_char *secret) + { + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int r = SSH_ERR_INTERNAL_ERROR; +- size_t wrappedlen = crypto_kem_mlkem768_CIPHERTEXTBYTES, +- secretlen = crypto_kem_mlkem768_BYTES; ++ size_t secretlen = crypto_kem_mlkem768_BYTES; + +- pkey = EVP_PKEY_new_raw_private_key_ex(NULL, "mlkem768", NULL, +- privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES); ++ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algname, ++ NULL, privkeybuf, privkey_len); + if (pkey == NULL) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto err; +@@ -148,7 +193,7 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, NULL); + if (ctx == NULL + || EVP_PKEY_decapsulate_init(ctx, NULL) <= 0 +- || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrappedlen) <= 0 ++ || EVP_PKEY_decapsulate(ctx, secret, &secretlen, wrapped, wrapped_len) <= 0 + || secretlen != crypto_kem_mlkem768_BYTES) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto err; +@@ -165,6 +210,20 @@ mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *s + return r; + } + ++static int ++mlkem768_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret) ++{ ++ return mlkem_decap_secret("mlkem768", privkeybuf, crypto_kem_mlkem768_SECRETKEYBYTES, ++ wrapped, crypto_kem_mlkem768_CIPHERTEXTBYTES, secret); ++} ++ ++static int ++mlkem1024_decap_secret(const u_char *privkeybuf, const u_char *wrapped, u_char *secret) ++{ ++ return mlkem_decap_secret("mlkem1024", privkeybuf, crypto_kem_mlkem1024_SECRETKEYBYTES, ++ wrapped, crypto_kem_mlkem1024_CIPHERTEXTBYTES, secret); ++} ++ + int + kex_kem_mlkem768x25519_keypair(struct kex *kex) + { +@@ -341,7 +400,7 @@ kex_kem_mlkem768x25519_enc(struct kex *kex, + u_char hash[SSH_DIGEST_MAX_LENGTH]; + size_t need; + int r = SSH_ERR_INTERNAL_ERROR; +- struct libcrux_mlkem768_enc_result enc; /* FIXME */ ++ struct libcrux_mlkem768_enc_result enc; + + *server_blobp = NULL; + *shared_secretp = NULL; +@@ -550,6 +609,468 @@ kex_kem_mlkem768x25519_dec(struct kex *kex, + return r; + #endif + } ++ ++#define NIST_P256_COMPRESSED_LEN 33 ++#define NIST_P256_UNCOMPRESSED_LEN 65 ++#define NIST_P384_COMPRESSED_LEN 49 ++#define NIST_P384_UNCOMPRESSED_LEN 97 ++#define NIST_BUF_MAX_SIZE NIST_P384_UNCOMPRESSED_LEN ++ ++static const char ec256[] = "P-256"; ++static const char ec384[] = "P-384"; ++static const char *len2curve_name(size_t len) ++{ ++ switch (len) { ++ case NIST_P256_COMPRESSED_LEN: ++ case NIST_P256_UNCOMPRESSED_LEN: ++ return ec256; ++ break; ++ case NIST_P384_COMPRESSED_LEN: ++ case NIST_P384_UNCOMPRESSED_LEN: ++ return ec384; ++ break; ++ } ++ return NULL; ++} ++ ++static EVP_PKEY * ++buf2nist_key(const unsigned char *pub_key_buf, size_t pub_key_len) ++{ ++ EVP_PKEY *pkey = NULL; ++ EVP_PKEY_CTX *ctx = NULL; ++ OSSL_PARAM params[3]; ++ const char *curve_name = len2curve_name(pub_key_len); ++ ++ if (curve_name == NULL) ++ return NULL; ++ ++ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); ++ if (!ctx) ++ goto err; ++ ++ if (EVP_PKEY_fromdata_init(ctx) <= 0) ++ goto err; ++ ++ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0); ++ params[1] = OSSL_PARAM_construct_octet_string( ++ OSSL_PKEY_PARAM_PUB_KEY, (void *)pub_key_buf, pub_key_len); ++ params[2] = OSSL_PARAM_construct_end(); ++ ++ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) ++ goto err; ++ ++ EVP_PKEY_CTX_free(ctx); ++ return pkey; ++ ++err: ++ EVP_PKEY_CTX_free(ctx); ++ EVP_PKEY_free(pkey); ++ return NULL; ++} ++ ++static int ++kex_nist_shared_key_ext(EVP_PKEY *priv_key, ++ const u_char *pub_key_buf, size_t pub_key_len, struct sshbuf *out) ++{ ++ EVP_PKEY_CTX *ctx = NULL; ++ unsigned char *shared_secret = NULL; ++ size_t shared_secret_len = 0; ++ EVP_PKEY *peer_key = buf2nist_key(pub_key_buf, pub_key_len); ++ int r = SSH_ERR_INTERNAL_ERROR; ++ ++ if (peer_key == NULL) ++ return SSH_ERR_KEY_LENGTH; ++ ++ ctx = EVP_PKEY_CTX_new_from_pkey(NULL, priv_key, NULL); ++ if (!ctx) ++ goto end; ++ ++ if ((EVP_PKEY_derive_init(ctx) <= 0) ++ || EVP_PKEY_derive_set_peer(ctx, peer_key) <= 0 ++ || EVP_PKEY_derive(ctx, NULL, &shared_secret_len) <= 0) ++ goto end; ++ ++ shared_secret = OPENSSL_malloc(shared_secret_len); ++ if (shared_secret == NULL) ++ goto end; ++ ++ if (EVP_PKEY_derive(ctx, shared_secret, &shared_secret_len) <= 0) ++ goto end; ++ ++ if ((r = sshbuf_put(out, shared_secret, shared_secret_len)) != 0) ++ goto end; ++ ++ r = 0; ++ ++end: ++ EVP_PKEY_free(peer_key); ++ if (shared_secret) ++ OPENSSL_clear_free(shared_secret, shared_secret_len); ++ EVP_PKEY_CTX_free(ctx); ++ ++ return r; ++} ++ ++static EVP_PKEY * ++nist_pkey_keygen(size_t pub_key_len) ++{ ++ const char *curve_name = len2curve_name(pub_key_len); ++ EVP_PKEY_CTX *pctx = NULL; ++ EVP_PKEY *pkey = NULL; ++ OSSL_PARAM params[2]; ++ ++ if (curve_name == NULL) ++ return NULL; ++ ++ pctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); ++ if (!pctx) ++ return NULL; ++ ++ if (EVP_PKEY_keygen_init(pctx) <= 0) { ++ EVP_PKEY_CTX_free(pctx); ++ return NULL; ++ } ++ ++ params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0); ++ params[1] = OSSL_PARAM_construct_end(); ++ ++ if (EVP_PKEY_CTX_set_params(pctx, params) <= 0 ++ || EVP_PKEY_keygen(pctx, &pkey) <= 0) { ++ EVP_PKEY_CTX_free(pctx); ++ return NULL; ++ } ++ ++ EVP_PKEY_CTX_free(pctx); ++ return pkey; ++} ++ ++static int ++get_uncompressed_ec_pubkey(EVP_PKEY *pkey, unsigned char *buf, size_t buf_len) ++{ ++ OSSL_PARAM params[2]; ++ size_t required_len = 0, out_len = 0; ++ ++ params[0] = OSSL_PARAM_construct_utf8_string( ++ OSSL_PKEY_PARAM_EC_POINT_CONVERSION_FORMAT, ++ "uncompressed", 0); ++ params[1] = OSSL_PARAM_construct_end(); ++ ++ if (EVP_PKEY_set_params(pkey, params) <= 0 ++ || EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, ++ NULL, 0, &required_len) <= 0) { ++ return SSH_ERR_LIBCRYPTO_ERROR; ++ } ++ ++ if (required_len != buf_len) ++ return SSH_ERR_INTERNAL_ERROR; ++ ++ if (EVP_PKEY_get_octet_string_param(pkey, OSSL_PKEY_PARAM_PUB_KEY, ++ buf, required_len, &out_len) <= 0) { ++ return SSH_ERR_LIBCRYPTO_ERROR; ++ } ++ ++ return 0; ++} ++/* nist_bytes_len should always be uncompressed */ ++static int ++kex_kem_mlkem_nist_keypair(struct kex *kex, size_t mlkem_bytes_len, size_t nist_bytes_len) ++{ ++ struct sshbuf *buf = NULL; ++ u_char *cp = NULL; ++ size_t need; ++ int r = SSH_ERR_INTERNAL_ERROR; ++ u_char *client_key = NULL; ++ ++ if ((buf = sshbuf_new()) == NULL) ++ return SSH_ERR_ALLOC_FAIL; ++ need = mlkem_bytes_len + nist_bytes_len; ++ if ((r = sshbuf_reserve(buf, need, &cp)) != 0) ++ goto out; ++ ++ if (mlkem_bytes_len == crypto_kem_mlkem768_PUBLICKEYBYTES) { ++ client_key = kex->mlkem768_client_key; ++ r = mlkem768_keypair_gen(cp, client_key); ++ } ++ ++ if (mlkem_bytes_len == crypto_kem_mlkem1024_PUBLICKEYBYTES) { ++ client_key = kex->mlkem1024_client_key; ++ r = mlkem1024_keypair_gen(cp, client_key); ++ } ++ ++ if (client_key == NULL) ++ goto out; ++ ++ if (r != 0) ++ goto out; ++#ifdef DEBUG_KEXECDH ++ dump_digest("client public key mlkemXXX:", cp, mlkem_bytes_len); ++#endif ++ cp += mlkem_bytes_len; ++ if ((kex->ec_hybrid_client_key = nist_pkey_keygen(nist_bytes_len)) == NULL) ++ goto out; ++ ++ if ((r = get_uncompressed_ec_pubkey(kex->ec_hybrid_client_key, cp, nist_bytes_len)) != 0) ++ goto out; ++ ++#ifdef DEBUG_KEXECDH ++ dump_digest("client public key NIST:", cp, nist_bytes_len); ++#endif ++ /* success */ ++ r = 0; ++ kex->client_pub = buf; ++ buf = NULL; ++ out: ++ sshbuf_free(buf); ++ if (r == SSH_ERR_LIBCRYPTO_ERROR) ++ ERR_print_errors_fp(stderr); ++ ++ return r; ++} ++ ++static int ++kex_kem_mlkem_nist_enc(struct kex *kex, const char *nist_curve, ++ const struct sshbuf *client_blob, struct sshbuf **server_blobp, ++ struct sshbuf **shared_secretp) ++{ ++ struct sshbuf *server_blob = NULL; ++ struct sshbuf *buf = NULL; ++ const u_char *client_pub; ++ u_char server_pub[NIST_BUF_MAX_SIZE]; ++ u_char enc_out[crypto_kem_mlkem1024_CIPHERTEXTBYTES]; ++ u_char secret[crypto_kem_mlkem768_BYTES]; ++ EVP_PKEY *server_key = NULL; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ size_t client_buf_len, mlkem_buf_len, ecdh_buf_len, server_key_len, enc_out_len; ++ int r = SSH_ERR_INTERNAL_ERROR; ++ ++ *server_blobp = NULL; ++ *shared_secretp = NULL; ++ ++ client_buf_len = sshbuf_len(client_blob); ++ /* client_blob contains both KEM and ECDH client pubkeys */ ++ if (strcmp(nist_curve, "P-256") == 0) { ++ if (crypto_kem_mlkem768_PUBLICKEYBYTES > client_buf_len) ++ return r; ++ ++ ecdh_buf_len = client_buf_len - crypto_kem_mlkem768_PUBLICKEYBYTES; ++ if (ecdh_buf_len != NIST_P256_COMPRESSED_LEN && ++ ecdh_buf_len != NIST_P256_UNCOMPRESSED_LEN) ++ return r; ++ mlkem_buf_len = crypto_kem_mlkem768_PUBLICKEYBYTES; ++ enc_out_len = crypto_kem_mlkem768_CIPHERTEXTBYTES; ++ server_key_len = NIST_P256_UNCOMPRESSED_LEN; ++ } else if (strcmp(nist_curve, "P-384") == 0) { ++ if (crypto_kem_mlkem1024_PUBLICKEYBYTES > client_buf_len) ++ return r; ++ ++ ecdh_buf_len = client_buf_len - crypto_kem_mlkem1024_PUBLICKEYBYTES; ++ if (ecdh_buf_len != NIST_P384_COMPRESSED_LEN && ++ ecdh_buf_len != NIST_P384_UNCOMPRESSED_LEN) ++ return r; ++ mlkem_buf_len = crypto_kem_mlkem1024_PUBLICKEYBYTES; ++ enc_out_len = crypto_kem_mlkem1024_CIPHERTEXTBYTES; ++ server_key_len = NIST_P384_UNCOMPRESSED_LEN; ++ } else ++ return r; ++ ++ client_pub = sshbuf_ptr(client_blob); ++#ifdef DEBUG_KEXECDH ++ dump_digest("client public key mlkem:", client_pub, mlkem_buf_len); ++ dump_digest("client public key NIST:", client_pub + mlkem_buf_len, ecdh_buf_len); ++#endif ++ ++ /* allocate buffer for concatenation of KEM key and ECDH shared key */ ++ /* the buffer will be hashed and the result is the shared secret */ ++ if ((buf = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ /* allocate space for encrypted KEM key and ECDH pub key */ ++ if ((server_blob = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++ r = (mlkem_buf_len == crypto_kem_mlkem768_PUBLICKEYBYTES) ? ++ mlkem768_encap_secret(client_pub, secret, enc_out) : ++ mlkem1024_encap_secret(client_pub, secret, enc_out); ++ ++ if (r != 0) ++ goto out; ++ ++ /* generate ECDH key pair, store server pubkey after ciphertext */ ++ server_key = nist_pkey_keygen(server_key_len); ++ ++ if ((r = get_uncompressed_ec_pubkey(server_key, server_pub, server_key_len) != 0) || ++ (r = sshbuf_put(buf, secret, sizeof(secret))) != 0 || ++ (r = sshbuf_put(server_blob, enc_out, enc_out_len) != 0)|| ++ (r = sshbuf_put(server_blob, server_pub, server_key_len)) != 0) ++ goto out; ++ ++ /* append ECDH shared key */ ++ client_pub += mlkem_buf_len; ++ if ((r = kex_nist_shared_key_ext(server_key, client_pub, ecdh_buf_len, buf)) < 0) ++ goto out; ++ if ((r = ssh_digest_buffer(kex->hash_alg, buf, hash, sizeof(hash))) != 0) ++ goto out; ++#ifdef DEBUG_KEXECDH ++ dump_digest("server public NIST:", server_pub, server_key_len); ++ dump_digest("server cipher text:", enc_out, enc_out_len); ++ dump_digest("server kem key:", secret, sizeof(secret)); ++ dump_digest("concatenation of KEM key and ECDH shared key:", ++ sshbuf_ptr(buf), sshbuf_len(buf)); ++#endif ++ /* string-encoded hash is resulting shared secret */ ++ 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 ++ /* success */ ++ r = 0; ++ *server_blobp = server_blob; ++ *shared_secretp = buf; ++ server_blob = NULL; ++ buf = NULL; ++ out: ++ explicit_bzero(hash, sizeof(hash)); ++ EVP_PKEY_free(server_key); ++ explicit_bzero(enc_out, sizeof(enc_out)); ++ explicit_bzero(secret, sizeof(secret)); ++ sshbuf_free(server_blob); ++ sshbuf_free(buf); ++ return r; ++} ++ ++static int ++kex_kem_mlkem_nist_dec(struct kex *kex, ++ const struct sshbuf *server_blob, struct sshbuf **shared_secretp, ++ size_t mlkem_len) ++{ ++ struct sshbuf *buf = NULL; ++ const u_char *ciphertext, *server_pub; ++ u_char hash[SSH_DIGEST_MAX_LENGTH]; ++ u_char decap[crypto_kem_mlkem768_BYTES]; ++ int r; ++ size_t nist_len; ++ ++ *shared_secretp = NULL; ++ ++ if (sshbuf_len(server_blob) < mlkem_len) { ++ r = SSH_ERR_SIGNATURE_INVALID; ++ goto out; ++ } ++ ++ nist_len = sshbuf_len(server_blob) - mlkem_len; ++ ++ switch (mlkem_len) { ++ case crypto_kem_mlkem768_CIPHERTEXTBYTES: ++ if (nist_len != NIST_P256_COMPRESSED_LEN ++ && nist_len != NIST_P256_UNCOMPRESSED_LEN) { ++ r = SSH_ERR_SIGNATURE_INVALID; ++ goto out; ++ } ++ break; ++ case crypto_kem_mlkem1024_CIPHERTEXTBYTES: ++ if (nist_len != NIST_P384_COMPRESSED_LEN ++ && nist_len != NIST_P384_UNCOMPRESSED_LEN) { ++ r = SSH_ERR_SIGNATURE_INVALID; ++ goto out; ++ } ++ break; ++ } ++ ++ ciphertext = sshbuf_ptr(server_blob); ++ server_pub = ciphertext + mlkem_len; ++ /* hash concatenation of KEM key and ECDH shared key */ ++ if ((buf = sshbuf_new()) == NULL) { ++ r = SSH_ERR_ALLOC_FAIL; ++ goto out; ++ } ++#ifdef DEBUG_KEXECDH ++ dump_digest("server cipher text:", ciphertext, mlkem_len); ++ dump_digest("server public key NIST:", server_pub, nist_len); ++#endif ++ r = (mlkem_len == crypto_kem_mlkem768_CIPHERTEXTBYTES) ? ++ mlkem768_decap_secret(kex->mlkem768_client_key, ciphertext, decap) : ++ mlkem1024_decap_secret(kex->mlkem1024_client_key, ciphertext, decap); ++ ++ if (r != 0) ++ goto out; ++ if ((r = sshbuf_put(buf, decap, sizeof(decap))) != 0) ++ goto out; ++ if ((r = kex_nist_shared_key_ext(kex->ec_hybrid_client_key, server_pub, ++ nist_len, buf)) < 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:", decap, sizeof(decap)); ++ 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 ++ /* success */ ++ r = 0; ++ *shared_secretp = buf; ++ buf = NULL; ++ out: ++ explicit_bzero(hash, sizeof(hash)); ++ explicit_bzero(decap, sizeof(decap)); ++ sshbuf_free(buf); ++ return r; ++} ++ ++int ++kex_kem_mlkem768nistp256_keypair(struct kex *kex) ++{ ++ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem768_PUBLICKEYBYTES, NIST_P256_UNCOMPRESSED_LEN); ++} ++ ++int ++kex_kem_mlkem768nistp256_enc(struct kex *kex, const struct sshbuf *client_blob, ++ struct sshbuf **server_blobp, struct sshbuf **shared_secretp) ++{ ++ return kex_kem_mlkem_nist_enc(kex, "P-256", client_blob, server_blobp, shared_secretp); ++} ++ ++int ++kex_kem_mlkem768nistp256_dec(struct kex *kex, const struct sshbuf *server_blob, ++ struct sshbuf **shared_secretp) ++{ ++ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp, ++ crypto_kem_mlkem768_CIPHERTEXTBYTES); ++} ++ ++int ++kex_kem_mlkem1024nistp384_keypair(struct kex *kex) ++{ ++ return kex_kem_mlkem_nist_keypair(kex, crypto_kem_mlkem1024_PUBLICKEYBYTES, NIST_P384_UNCOMPRESSED_LEN); ++} ++ ++int ++kex_kem_mlkem1024nistp384_enc(struct kex *kex, const struct sshbuf *client_blob, ++ struct sshbuf **server_blobp, struct sshbuf **shared_secretp) ++{ ++ return kex_kem_mlkem_nist_enc(kex, "P-384", client_blob, server_blobp, shared_secretp); ++} ++ ++int ++kex_kem_mlkem1024nistp384_dec(struct kex *kex, const struct sshbuf *server_blob, ++ struct sshbuf **shared_secretp) ++{ ++ return kex_kem_mlkem_nist_dec(kex, server_blob, shared_secretp, ++ crypto_kem_mlkem1024_CIPHERTEXTBYTES); ++} ++ + #else /* USE_MLKEM768X25519 */ + int + kex_kem_mlkem768x25519_keypair(struct kex *kex) +@@ -571,4 +1092,39 @@ kex_kem_mlkem768x25519_dec(struct kex *kex, + { + return SSH_ERR_SIGN_ALG_UNSUPPORTED; + } ++ ++int kex_kem_mlkem768nistp256_keypair(struct kex *) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ ++int kex_kem_mlkem768nistp256_enc(struct kex *, const struct sshbuf *, ++ struct sshbuf **, struct sshbuf **) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ ++int kex_kem_mlkem768nistp256_dec(struct kex *, const struct sshbuf *, ++ struct sshbuf **) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ ++int kex_kem_mlkem1024nistp384_keypair(struct kex *) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ ++int kex_kem_mlkem1024nistp384_enc(struct kex *, const struct sshbuf *, ++ struct sshbuf **, struct sshbuf **) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ ++int kex_kem_mlkem1024nistp384_dec(struct kex *, const struct sshbuf *, ++ struct sshbuf **) ++{ ++ return SSH_ERR_SIGN_ALG_UNSUPPORTED; ++} ++ + #endif /* USE_MLKEM768X25519 */ +diff --git a/monitor.c b/monitor.c +index 58fbac9df..75413ce71 100644 +--- a/monitor.c ++++ b/monitor.c +@@ -2009,6 +2009,8 @@ monitor_apply_keystate(struct ssh *ssh, struct monitor *pmonitor) + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; ++ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server; ++ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server; + kex->load_host_public_key=&get_hostkey_public_by_type; + kex->load_host_private_key=&get_hostkey_private_by_type; + kex->host_key_index=&get_hostkey_index; +diff --git a/regress/unittests/kex/test_kex.c b/regress/unittests/kex/test_kex.c +index 09016aead..2baf9a7a0 100644 +--- a/regress/unittests/kex/test_kex.c ++++ b/regress/unittests/kex/test_kex.c +@@ -155,6 +155,8 @@ do_kex_with_key(char *kex, int keytype, int bits) + server2->kex->kex[KEX_C25519_SHA256] = kex_gen_server; + server2->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + server2->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; ++ server2->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server; ++ server2->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server; + server2->kex->load_host_public_key = server->kex->load_host_public_key; + server2->kex->load_host_private_key = server->kex->load_host_private_key; + server2->kex->sign = server->kex->sign; +@@ -211,6 +213,8 @@ kex_tests(void) + do_kex("diffie-hellman-group1-sha1"); + # ifdef USE_MLKEM768X25519 + do_kex("mlkem768x25519-sha256"); ++ do_kex("mlkem768nistp256-sha256"); ++ do_kex("mlkem1024nistp384-sha384"); + # endif /* USE_MLKEM768X25519 */ + # ifdef USE_SNTRUP761X25519 + do_kex("sntrup761x25519-sha512@openssh.com"); +diff --git a/ssh-keyscan.c b/ssh-keyscan.c +index 9f76ad225..8660088b7 100644 +--- a/ssh-keyscan.c ++++ b/ssh-keyscan.c +@@ -304,6 +304,8 @@ keygrab_ssh2(con *c) + c->c_ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + c->c_ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; + c->c_ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client; ++ c->c_ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client; ++ c->c_ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client; + ssh_set_verify_host_key_callback(c->c_ssh, key_print_wrapper); + /* + * do the key-exchange until an error occurs or until +diff --git a/ssh_api.c b/ssh_api.c +index 7bdcee148..d20b03a00 100644 +--- a/ssh_api.c ++++ b/ssh_api.c +@@ -135,6 +135,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_server; + ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; ++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server; ++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server; + ssh->kex->load_host_public_key=&_ssh_host_public_key; + ssh->kex->load_host_private_key=&_ssh_host_private_key; + ssh->kex->sign=&_ssh_host_key_sign; +@@ -154,6 +156,8 @@ ssh_init(struct ssh **sshp, int is_server, struct kex_params *kex_params) + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client; ++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client; ++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client; + ssh->kex->verify_host_key =&_ssh_verify_host_key; + } + *sshp = ssh; +diff --git a/sshconnect2.c b/sshconnect2.c +index 5beae1c10..6d9f1238b 100644 +--- a/sshconnect2.c ++++ b/sshconnect2.c +@@ -347,6 +347,8 @@ ssh_kex2(struct ssh *ssh, char *host, struct sockaddr *hostaddr, u_short port, + ssh->kex->kex[KEX_C25519_SHA256] = kex_gen_client; + ssh->kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_client; + ssh->kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_client; ++ ssh->kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_client; ++ ssh->kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_client; + ssh->kex->verify_host_key=&verify_host_key_callback; + + #if defined(GSSAPI) && defined(WITH_OPENSSL) +diff -up openssh-9.9p1-build/openssh-9.9p1/sshd-session.c.xxx openssh-9.9p1-build/openssh-9.9p1/sshd-session.c +--- a/sshd-session.c.xxx 2025-11-03 12:07:44.196167561 +0100 ++++ b/sshd-session.c 2025-11-03 12:08:26.809214111 +0100 +@@ -1659,6 +1659,8 @@ do_ssh2_kex(struct ssh *ssh) + kex->kex[KEX_C25519_SHA256] = kex_gen_server; + kex->kex[KEX_KEM_SNTRUP761X25519_SHA512] = kex_gen_server; + kex->kex[KEX_KEM_MLKEM768X25519_SHA256] = kex_gen_server; ++ kex->kex[KEX_KEM_MLKEM768NISTP256_SHA256] = kex_gen_server; ++ kex->kex[KEX_KEM_MLKEM1024NISTP384_SHA384] = kex_gen_server; + kex->load_host_public_key=&get_hostkey_public_by_type; + kex->load_host_private_key=&get_hostkey_private_by_type; + kex->host_key_index=&get_hostkey_index; diff --git a/openssh.spec b/openssh.spec index 68a04f2..efa22f2 100644 --- a/openssh.spec +++ b/openssh.spec @@ -43,7 +43,7 @@ Summary: An open source implementation of SSH protocol version 2 Name: openssh Version: %{openssh_ver} -Release: 15%{?dist} +Release: 16%{?dist} URL: http://www.openssh.com/portable.html Source0: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz Source1: ftp://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-%{version}.tar.gz.asc @@ -220,6 +220,7 @@ Patch1028: openssh-9.9p1-fips-gss.patch #upstream 4f14ca8633a2c8c0a1a19165663421f0ab32f6ab Patch1029: openssh-9.9p1-scp-traversing.patch Patch1030: openssh-9.9p1-canonical-match-user.patch +Patch1031: openssh-10.0-mlkem-nist.patch License: BSD-3-Clause AND BSD-2-Clause AND ISC AND SSH-OpenSSH AND ssh-keyscan AND sprintf AND LicenseRef-Fedora-Public-Domain AND X11-distribute-modifications-variant Requires: /sbin/nologin @@ -415,6 +416,7 @@ gpgv2 --quiet --keyring %{SOURCE3} %{SOURCE1} %{SOURCE0} %patch -P 1028 -p1 -b .gss-fips %patch -P 1029 -p1 -b .scp-traversing %patch -P 1030 -p1 -b .canonical-match-user +%patch -P 1031 -p1 -b .mlkem-nist %patch -P 100 -p1 -b .coverity @@ -695,6 +697,10 @@ test -f %{sysconfig_anaconda} && \ %attr(0755,root,root) %{_libdir}/sshtest/sk-dummy.so %changelog +* Mon Nov 03 2025 Dmitry Belyavskiy - 9.9p1-16 +- Implement mlkem768nistp256-sha256 and mlkem1024nistp384-sha384 KEX methods + Resolves: RHEL-70824 + * Mon Oct 27 2025 Zoltan Fridrich - 9.9p1-15 - Fix implicit destination path selection when source path ends with ".." Resolves: RHEL-118406