File 1351-crypto-Add-support-for-ML-DSA-and-ML-KEM.patch of Package erlang

From 6675b73ff8e0597451e8240ecee1e3355eb9a130 Mon Sep 17 00:00:00 2001
From: Sverker Eriksson <sverker@erlang.org>
Date: Mon, 24 Feb 2025 17:04:39 +0100
Subject: [PATCH] crypto: Add support for ML-DSA and ML-KEM

Requires OpenSSL 3.5
---
 lib/crypto/c_src/algorithms.c     |  18 ++-
 lib/crypto/c_src/algorithms.h     |   1 +
 lib/crypto/c_src/atoms.c          |  26 ++++
 lib/crypto/c_src/atoms.h          |  13 ++
 lib/crypto/c_src/bn.c             |   2 +-
 lib/crypto/c_src/bn.h             |   2 +-
 lib/crypto/c_src/cipher.c         |   1 +
 lib/crypto/c_src/common.h         |   1 +
 lib/crypto/c_src/crypto.c         |   7 +-
 lib/crypto/c_src/evp.c            | 130 ++++++++++++++++-
 lib/crypto/c_src/evp.h            |   2 +
 lib/crypto/c_src/openssl_config.h |   8 ++
 lib/crypto/c_src/pkey.c           | 232 +++++++++++++++++++++++++++---
 lib/crypto/c_src/pkey.h           |  16 +++
 lib/crypto/src/crypto.erl         |  78 +++++++++-
 lib/crypto/test/crypto_SUITE.erl  |  75 +++++++++-
 16 files changed, 581 insertions(+), 31 deletions(-)

diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c
index 8fab065932..2e1465e15e 100644
--- a/lib/crypto/c_src/algorithms.c
+++ b/lib/crypto/c_src/algorithms.c
@@ -193,10 +193,26 @@ void init_pubkey_types(ErlNifEnv* env) {
     algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddh");
 #endif
     algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp");
-
+#ifdef HAVE_ML_DSA
+    algo_pubkey[algo_pubkey_cnt++] = atom_mldsa44;
+    algo_pubkey[algo_pubkey_cnt++] = atom_mldsa65;
+    algo_pubkey[algo_pubkey_cnt++] = atom_mldsa87;
+#endif
     ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM));
 }
 
+ERL_NIF_TERM kem_algorithms_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef HAVE_ML_KEM
+    return enif_make_list3(env,
+                           atom_mlkem512,
+                           atom_mlkem768,
+                           atom_mlkem1024);
+#else
+    return enif_make_list(env, 0);
+#endif
+}
+
 
 /*================================================================
   Cipher key algorithms
diff --git a/lib/crypto/c_src/algorithms.h b/lib/crypto/c_src/algorithms.h
index 2a1e6deb49..7a2c75106b 100644
--- a/lib/crypto/c_src/algorithms.h
+++ b/lib/crypto/c_src/algorithms.h
@@ -31,6 +31,7 @@ void init_algorithms_types(ErlNifEnv* env);
 
 ERL_NIF_TERM hash_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM pubkey_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM kem_algorithms_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM cipher_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM mac_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM curve_algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c
index cbd81de3ca..41e171afbc 100644
--- a/lib/crypto/c_src/atoms.c
+++ b/lib/crypto/c_src/atoms.c
@@ -151,6 +151,20 @@ ERL_NIF_TERM atom_key_id;
 ERL_NIF_TERM atom_password;
 #endif
 
+#ifdef HAVE_ML_DSA
+ERL_NIF_TERM atom_mldsa44;
+ERL_NIF_TERM atom_mldsa65;
+ERL_NIF_TERM atom_mldsa87;
+ERL_NIF_TERM atom_seed;
+ERL_NIF_TERM atom_expandedkey;
+#endif
+
+#ifdef HAVE_ML_KEM
+ERL_NIF_TERM atom_mlkem512;
+ERL_NIF_TERM atom_mlkem768;
+ERL_NIF_TERM atom_mlkem1024;
+#endif
+
 int init_atoms(ErlNifEnv *env) {
     atom_true  = enif_make_atom(env,"true");
     atom_false = enif_make_atom(env,"false");
@@ -274,5 +288,17 @@ int init_atoms(ErlNifEnv *env) {
     atom_password = enif_make_atom(env,"password");
 #endif
 
+#ifdef HAVE_ML_DSA
+    atom_mldsa44 = enif_make_atom(env,"mldsa44");
+    atom_mldsa65 = enif_make_atom(env,"mldsa65");
+    atom_mldsa87 = enif_make_atom(env,"mldsa87");
+    atom_seed = enif_make_atom(env,"seed");
+    atom_expandedkey = enif_make_atom(env,"expandedkey");
+#endif
+#ifdef HAVE_ML_KEM
+    atom_mlkem512  = enif_make_atom(env,"mlkem512");
+    atom_mlkem768  = enif_make_atom(env,"mlkem768");
+    atom_mlkem1024 = enif_make_atom(env,"mlkem1024");
+#endif
     return 1;
 }
diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h
index c8d88fcf75..4ee6a56057 100644
--- a/lib/crypto/c_src/atoms.h
+++ b/lib/crypto/c_src/atoms.h
@@ -150,6 +150,19 @@ extern ERL_NIF_TERM atom_key_id;
 extern ERL_NIF_TERM atom_password;
 #endif
 
+#ifdef HAVE_ML_DSA
+extern ERL_NIF_TERM atom_mldsa44;
+extern ERL_NIF_TERM atom_mldsa65;
+extern ERL_NIF_TERM atom_mldsa87;
+extern ERL_NIF_TERM atom_seed;
+extern ERL_NIF_TERM atom_expandedkey;
+#endif
+#ifdef HAVE_ML_KEM
+extern ERL_NIF_TERM atom_mlkem512;
+extern ERL_NIF_TERM atom_mlkem768;
+extern ERL_NIF_TERM atom_mlkem1024;
+#endif
+
 int init_atoms(ErlNifEnv *env);
 
 #endif /* E_ATOMS_H__ */
diff --git a/lib/crypto/c_src/bn.c b/lib/crypto/c_src/bn.c
index 7bd9529c4e..c7bdb1c5c7 100644
--- a/lib/crypto/c_src/bn.c
+++ b/lib/crypto/c_src/bn.c
@@ -198,7 +198,7 @@ ERL_NIF_TERM bn2term(ErlNifEnv* env, size_t size, const BIGNUM *bn)
 
 #ifdef HAS_3_0_API
 
-int get_ossl_octet_string_param_from_bin(ErlNifEnv* env, char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest)
+int get_ossl_octet_string_param_from_bin(ErlNifEnv* env, const char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest)
 {
     ErlNifBinary tmp;
 
diff --git a/lib/crypto/c_src/bn.h b/lib/crypto/c_src/bn.h
index 44994f8358..e0ad63ab76 100644
--- a/lib/crypto/c_src/bn.h
+++ b/lib/crypto/c_src/bn.h
@@ -37,7 +37,7 @@ int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp);
 int get_bn_from_bin_sz(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp, size_t* binsize);
 
 #ifdef HAS_3_0_API
-int get_ossl_octet_string_param_from_bin(ErlNifEnv* env, char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest);
+int get_ossl_octet_string_param_from_bin(ErlNifEnv* env, const char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest);
 int get_ossl_BN_param_from_bin(ErlNifEnv* env, char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest);
 int get_ossl_BN_param_from_bin_sz(ErlNifEnv* env, char* key, ERL_NIF_TERM bin, OSSL_PARAM *dest, size_t *size);
 int get_ossl_BN_param_from_bn(ErlNifEnv* env, char* key, const BIGNUM* bn, OSSL_PARAM *dest);
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index 307dddc4c8..b57563b3a7 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -22,6 +22,7 @@
 
 #include "cipher.h"
 #include "info.h"
+#include "evp.h"
 
 #define NOT_AEAD {{0,0,0}}
 #define AEAD_CTRL {{EVP_CTRL_AEAD_SET_IVLEN,EVP_CTRL_AEAD_GET_TAG,EVP_CTRL_AEAD_SET_TAG}}
diff --git a/lib/crypto/c_src/common.h b/lib/crypto/c_src/common.h
index 92fb31c38b..2c5c306c9d 100644
--- a/lib/crypto/c_src/common.h
+++ b/lib/crypto/c_src/common.h
@@ -32,6 +32,7 @@
 #include <string.h>
 #include <limits.h>
 #include <stdint.h>
+#include <stdbool.h>
 
 #include <erl_nif.h>
 #include "openssl_config.h"
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
index af493a2cdb..daff2a4d51 100644
--- a/lib/crypto/c_src/crypto.c
+++ b/lib/crypto/c_src/crypto.c
@@ -104,6 +104,9 @@ static ErlNifFunc nif_funcs[] = {
     {"pkey_sign_nif", 5, pkey_sign_nif, 0},
     {"pkey_verify_nif", 6, pkey_verify_nif, 0},
     {"pkey_crypt_nif", 6, pkey_crypt_nif, 0},
+    {"encapsulate_key_nif", 2, encapsulate_key_nif, 0},
+    {"decapsulate_key_nif", 3, decapsulate_key_nif, 0},
+    {"kem_algorithms_nif", 0, kem_algorithms_nif, 0},
     {"rsa_generate_key_nif", 2, rsa_generate_key_nif, 0},
     {"dh_generate_key_nif", 4, dh_generate_key_nif, 0},
     {"dh_compute_key_nif", 3, dh_compute_key_nif, 0},
@@ -266,12 +269,14 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info)
         ret = __LINE__; goto done;
     }
     if (!(prov[prov_cnt++] = OSSL_PROVIDER_load(NULL, "base"))) {
-        ret = __LINE__; goto done;
+            ret = __LINE__; goto done;
     }
     if ((prov[prov_cnt] = OSSL_PROVIDER_load(NULL, "legacy"))) {
         /* Don't fail loading if the legacy provider is missing */
         prov_cnt++;
     }
+    prefetched_sign_algo_init();
+
 #endif
 
     if (!init_atoms(env)) {
diff --git a/lib/crypto/c_src/evp.c b/lib/crypto/c_src/evp.c
index 30870ef5ad..f09eedb00e 100644
--- a/lib/crypto/c_src/evp.c
+++ b/lib/crypto/c_src/evp.c
@@ -19,8 +19,114 @@
  *
  * %CopyrightEnd%
  */
+#include <ctype.h>
 
 #include "evp.h"
+#include "bn.h"
+#include "pkey.h"
+
+/* (Type, OthersPublicKey) -> {Secret, EncapSecret}
+ */
+ERL_NIF_TERM encapsulate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef HAVE_ML_KEM
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *peer_pkey = NULL;
+    size_t encaps_len, secret_len;
+    unsigned char *encaps_data, *secret_data;
+    ERL_NIF_TERM encaps_bin, secret_bin;
+    ERL_NIF_TERM ret;
+
+    if (!get_pkey_from_octet_string(env, argv[0], argv[1], PKEY_PUB,
+                                    &peer_pkey, &ret)) {
+        goto err;
+    }
+
+    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, peer_pkey, NULL);
+    if (!ctx) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't create PKEY_CTX from key"));
+    }
+
+    if (EVP_PKEY_encapsulate_init(ctx, NULL) != 1) {
+        ERR_print_errors_fp(stderr);
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't encapsulate_init"));
+    }
+    if (EVP_PKEY_encapsulate(ctx, NULL, &encaps_len, NULL, &secret_len) != 1) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't get encapsulate sizes"));
+    }
+    encaps_data = enif_make_new_binary(env, encaps_len, &encaps_bin);
+    secret_data = enif_make_new_binary(env, secret_len, &secret_bin);
+    if (EVP_PKEY_encapsulate(ctx, encaps_data, &encaps_len, secret_data, &secret_len) != 1) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't encapsulate"));
+    }
+
+    ret = enif_make_tuple2(env, secret_bin, encaps_bin);
+
+err:
+    if (peer_pkey) {
+        EVP_PKEY_free(peer_pkey);
+    }
+    if (ctx) {
+        EVP_PKEY_CTX_free(ctx);
+    }
+    return ret;
+#else
+    return RAISE_NOTSUP(env);
+#endif
+}
+
+/* (Type, MyPrivKey, EncapSecret) -> Secret
+ */
+ERL_NIF_TERM decapsulate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+#ifdef HAVE_ML_KEM
+    EVP_PKEY_CTX *ctx = NULL;
+    EVP_PKEY *my_pkey = NULL;
+    size_t secret_len;
+    unsigned char *secret_data;
+    ERL_NIF_TERM secret_bin;
+    ErlNifBinary encaps;
+    ERL_NIF_TERM ret;
+
+    if (!enif_inspect_binary(env, argv[2], &encaps)) {
+        assign_goto(ret, err, EXCP_ERROR_N(env, 2, "Invalid encapsulated secret"));
+    }
+    if (!get_pkey_from_octet_string(env, argv[0], argv[1], PKEY_PRIV,
+                                    &my_pkey, &ret)) {
+        goto err;
+    }
+
+    ctx = EVP_PKEY_CTX_new_from_pkey(NULL, my_pkey, NULL);
+    if (!ctx) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't create PKEY_CTX from key"));
+    }
+
+    if (EVP_PKEY_decapsulate_init(ctx, NULL) != 1) {
+        ERR_print_errors_fp(stderr);
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't decapsulate_init"));
+    }
+    if (EVP_PKEY_decapsulate(ctx, NULL, &secret_len, encaps.data, encaps.size) != 1) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't get encapsulate sizes"));
+    }
+    secret_data = enif_make_new_binary(env, secret_len, &secret_bin);
+    if (EVP_PKEY_decapsulate(ctx, secret_data, &secret_len, encaps.data, encaps.size) != 1) {
+        assign_goto(ret, err, EXCP_ERROR(env, "Can't encapsulate"));
+    }
+
+    ret = secret_bin;
+
+err:
+    if (my_pkey) {
+        EVP_PKEY_free(my_pkey);
+    }
+    if (ctx) {
+        EVP_PKEY_CTX_free(ctx);
+    }
+    return ret;
+#else
+    return RAISE_NOTSUP(env);
+#endif
+}
 
 ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
     /*    (Curve, PeerBin, MyBin) */
@@ -136,8 +242,27 @@ ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
     else if (argv[0] == atom_ed448)
         type = EVP_PKEY_ED448;
 #endif
-    else
-        assign_goto(ret, bad_arg, EXCP_BADARG_N(env, 0, "Bad curve"));
+#ifdef HAVE_ML_DSA
+    else if (argv[0] == atom_mldsa44) {
+        type = EVP_PKEY_ML_DSA_44;
+    } else if (argv[0] == atom_mldsa65) {
+        type = EVP_PKEY_ML_DSA_65;
+    } else if (argv[0] == atom_mldsa87) {
+        type = EVP_PKEY_ML_DSA_87;
+    }
+#endif
+#ifdef HAVE_ML_KEM
+    else if (argv[0] == atom_mlkem512) {
+        type = NID_ML_KEM_512;
+    } else if (argv[0] == atom_mlkem768) {
+        type = NID_ML_KEM_768;
+    } else if (argv[0] == atom_mlkem1024) {
+        type = NID_ML_KEM_1024;
+    }
+#endif
+    else {
+        assign_goto(ret, err, EXCP_BADARG_N(env, 0, "Bad key type"));
+    }
 
     if (argv[1] == atom_undefined) {
         if ((ctx = EVP_PKEY_CTX_new_id(type, NULL)) == NULL)
@@ -170,7 +295,6 @@ ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM a
     ret = enif_make_tuple2(env, ret_pub, ret_prv);
     goto done;
 
- bad_arg:
  err:
  done:
     if (pkey)
diff --git a/lib/crypto/c_src/evp.h b/lib/crypto/c_src/evp.h
index 35674b0eb0..9ca11eaba3 100644
--- a/lib/crypto/c_src/evp.h
+++ b/lib/crypto/c_src/evp.h
@@ -25,6 +25,8 @@
 
 #include "common.h"
 
+ERL_NIF_TERM encapsulate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+ERL_NIF_TERM decapsulate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 
diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h
index f65f1858a4..9d7ef23a60 100644
--- a/lib/crypto/c_src/openssl_config.h
+++ b/lib/crypto/c_src/openssl_config.h
@@ -379,6 +379,14 @@
 #endif
 #endif
 
+#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(3,5,0)
+#  ifndef OPENSSL_NO_ML_KEM
+#    define HAVE_ML_KEM
+#  endif
+#  ifndef OPENSSL_NO_ML_DSA
+#    define HAVE_ML_DSA
+#  endif
+#endif
 
 #if defined(HAS_ENGINE_SUPPORT)
 # include <openssl/engine.h>
diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c
index 0b1e1bbb2c..f10998baff 100644
--- a/lib/crypto/c_src/pkey.c
+++ b/lib/crypto/c_src/pkey.c
@@ -46,6 +46,7 @@ typedef struct PKeySignOptions {
 
 static int check_pkey_algorithm_type(ErlNifEnv *env,
                                      int alg_arg_num, ERL_NIF_TERM algorithm,
+                                     int allow_unknown,
                                      ERL_NIF_TERM *err_return);
 static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm,
                                 int type_arg_num, ERL_NIF_TERM type,
@@ -81,8 +82,42 @@ static int get_pkey_crypt_options(ErlNifEnv *env,
 static size_t size_of_RSA(EVP_PKEY *pkey);
 #endif
 
+#ifdef HAVE_ML_DSA
+static EVP_SIGNATURE* the_mldsa44_algo;
+static EVP_SIGNATURE* the_mldsa65_algo;
+static EVP_SIGNATURE* the_mldsa87_algo;
+#endif
+
+void prefetched_sign_algo_init(void)
+{
+#ifdef HAVE_ML_DSA
+    the_mldsa44_algo = EVP_SIGNATURE_fetch(NULL, "mldsa44", NULL);
+    the_mldsa65_algo = EVP_SIGNATURE_fetch(NULL, "mldsa65", NULL);
+    the_mldsa87_algo = EVP_SIGNATURE_fetch(NULL, "mldsa87", NULL);
+#endif
+}
+
+#ifdef HAS_3_0_API
+static EVP_SIGNATURE* get_prefetched_sign_algo(ERL_NIF_TERM alg_atom)
+{
+#ifdef HAVE_ML_DSA
+    if (alg_atom == atom_mldsa44) {
+        return the_mldsa44_algo;
+    }
+    else if (alg_atom == atom_mldsa65) {
+        return the_mldsa65_algo;
+    }
+    else if (alg_atom == atom_mldsa87) {
+        return the_mldsa87_algo;
+    }
+#endif
+    return NULL;
+}
+#endif
+
 static int check_pkey_algorithm_type(ErlNifEnv *env,
                                      int alg_arg_num, ERL_NIF_TERM algorithm,
+                                     int allow_unknown,
                                      ERL_NIF_TERM *err_return)
 {
     if (
@@ -106,7 +141,8 @@ static int check_pkey_algorithm_type(ErlNifEnv *env,
         assign_goto(*err_return, err, EXCP_NOTSUP_N(env, alg_arg_num, "Unsupported algorithm in FIPS mode"));
 #endif    
 
-    if ((algorithm != atom_rsa) &&
+    if (!allow_unknown &&
+        (algorithm != atom_rsa) &&
         (algorithm != atom_dss) &&
         (algorithm != atom_ecdsa) &&
         (algorithm != atom_eddsa)
@@ -128,8 +164,18 @@ static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm,
     struct digest_type_t *digp = NULL;
     *md = NULL;
 
-    if (type == atom_none && algorithm == atom_rsa)
-        return 1;
+    if (type == atom_none) {
+        if (algorithm == atom_rsa) {
+            return 1;
+        }
+#ifdef HAVE_ML_DSA
+        if (algorithm == atom_mldsa44 ||
+            algorithm == atom_mldsa65 ||
+            algorithm == atom_mldsa87) {
+            return 1;
+        }
+#endif
+    }
 
     if (algorithm == atom_eddsa) /* Type was skipped for eddsa in < OTP-25
                                     For eddsa the RFC 8032 mandates sha512 in
@@ -173,8 +219,10 @@ static int get_pkey_sign_digest(ErlNifEnv *env,
     tbs = *tbsp;
     tbslen = *tbslenp;
 
-    if (!check_pkey_algorithm_type(env, algorithm_arg_num, argv[algorithm_arg_num], err_return))
+    if (!check_pkey_algorithm_type(env, algorithm_arg_num, argv[algorithm_arg_num],
+                                   1, err_return)) {
         goto err; /* An exception is present in ret */
+    }
     
     if (!get_pkey_digest_type(env, argv[algorithm_arg_num],
                               type_arg_num, argv[type_arg_num],
@@ -329,10 +377,11 @@ static int get_pkey_sign_options(ErlNifEnv *env,
 }
 
 static int get_pkey_private_key(ErlNifEnv *env,
-                               const ERL_NIF_TERM argv[],
-                               int algorithm_arg_num, int key_arg_num,
-			       EVP_PKEY **pkey,
-                               ERL_NIF_TERM *err_return)
+                                const ERL_NIF_TERM argv[],
+                                int algorithm_arg_num,
+                                int key_arg_num,
+                                EVP_PKEY **pkey,
+                                ERL_NIF_TERM *err_return)
 {
     char *id = NULL;
     char *password = NULL;
@@ -385,8 +434,39 @@ static int get_pkey_private_key(ErlNifEnv *env,
         assign_goto(*err_return, err, EXCP_NOTSUP_N(env, algorithm_arg_num, "DSA not supported"));
 #endif
 
-    } else
-          assign_goto(*err_return, err, EXCP_BADARG_N(env, algorithm_arg_num, "Bad algorithm"));  
+    } else {
+#ifndef HAS_3_0_API
+        assign_goto(*err_return, err, EXCP_BADARG_N(env, algorithm_arg_num, "Bad algorithm"));
+#else
+        enum pkey_format_t pkey_format = PKEY_PRIV;
+        ERL_NIF_TERM key_bin = argv[key_arg_num];
+
+#  ifdef HAVE_ML_DSA
+        if (argv[algorithm_arg_num] == atom_mldsa44 ||
+            argv[algorithm_arg_num] == atom_mldsa65 ||
+            argv[algorithm_arg_num] == atom_mldsa87) {
+            int tpl_arity;
+            const ERL_NIF_TERM* tpl_array;
+
+            if (!enif_get_tuple(env, argv[key_arg_num], &tpl_arity, &tpl_array)
+                || tpl_arity != 2) {
+                assign_goto(*err_return, err, EXCP_BADARG_N(env, key_arg_num, "MLDSA key not 2-tuple"));
+            }
+            if (tpl_array[0] == atom_seed) {
+                pkey_format = PKEY_PRIV_SEED;
+            }
+            else if (tpl_array[0] != atom_expandedkey) {
+                assign_goto(*err_return, err, EXCP_BADARG_N(env, key_arg_num, "Invalid MLDSA key tuple"));
+            }
+            key_bin = tpl_array[1];
+        }
+#  endif
+        if (!get_pkey_from_octet_string(env, argv[algorithm_arg_num], key_bin,
+                                        pkey_format, pkey, err_return)) {
+            goto err;
+        }
+#endif
+    }
 
     ret = 1;
  done:
@@ -405,6 +485,79 @@ static int get_pkey_private_key(ErlNifEnv *env,
     goto done;
 }
 
+#ifdef HAS_3_0_API
+int get_pkey_from_octet_string(ErlNifEnv *env,
+                               ERL_NIF_TERM alg_atom,
+                               ERL_NIF_TERM key_bin,
+                               enum pkey_format_t pkey_format,
+                               EVP_PKEY **pkey_p,
+                               ERL_NIF_TERM *ret_p)
+{
+    char type[40];
+    OSSL_PARAM params[2];
+    int i = 0;
+    EVP_PKEY *pkey = NULL;
+    EVP_PKEY_CTX *ctx = NULL;
+    ErlNifBinary keyb;
+    const char* key_type;
+    int selection;
+
+    if (!enif_get_atom(env, alg_atom, type, sizeof(type), ERL_NIF_UTF8)) {
+        assign_goto(*ret_p, err, EXCP_ERROR_N(env, 0, "Invalid key type"));
+    }
+    if (!enif_inspect_binary(env, key_bin, &keyb)) {
+        assign_goto(*ret_p, err, EXCP_ERROR_N(env, 1, "Invalid public key"));
+    }
+    switch (pkey_format) {
+#ifdef HAVE_ML_DSA
+    case PKEY_PRIV_SEED:
+        key_type = "seed"; // OSSL_PKEY_PARAM_ML_DSA_SEED
+        selection = EVP_PKEY_KEYPAIR;
+        break;
+#endif
+    case PKEY_PRIV:
+        key_type = "priv"; // OSSL_PKEY_PARAM_PRIV_KEY;
+        selection = EVP_PKEY_KEYPAIR;
+        break;
+    default:
+        ASSERT(pkey_format == PKEY_PUB);
+        key_type = "pub"; // OSSL_PKEY_PARAM_PUB_KEY;
+        selection = EVP_PKEY_PUBLIC_KEY;
+        break;
+    }
+
+    params[i++] = OSSL_PARAM_construct_octet_string(key_type, keyb.data, keyb.size);
+    params[i++] = OSSL_PARAM_construct_end();
+
+    ctx = EVP_PKEY_CTX_new_from_name(NULL, type, NULL);
+    if (!ctx) {
+        assign_goto(*ret_p, err, EXCP_ERROR(env, "Can't create PKEY_CTX from name"));
+    }
+
+    if (EVP_PKEY_fromdata_init(ctx) <= 0) {
+        assign_goto(*ret_p, err, EXCP_ERROR(env, "Can't init fromdata"));
+    }
+
+    if (EVP_PKEY_fromdata(ctx, &pkey, selection, params) <= 0) {
+        assign_goto(*ret_p, err, EXCP_ERROR(env, "Can't create PKEY fromdata"));
+    }
+
+    /*
+    ** The caller might need an EVP_PKEY_CTX, but the one we have here isn't
+    ** good enough for EVP_PKEY_encapsulate_init and friends for some reason!?
+    */
+    EVP_PKEY_CTX_free(ctx);
+
+    *pkey_p = pkey;
+
+    return 1;
+
+err:
+    EVP_PKEY_CTX_free(ctx);
+    EVP_PKEY_free(pkey);
+    return 0;
+}
+#endif  // HAS_3_0_API
 
 static int get_pkey_public_key(ErlNifEnv *env,
                                const ERL_NIF_TERM argv[],
@@ -462,8 +615,17 @@ static int get_pkey_public_key(ErlNifEnv *env,
 #else
         assign_goto(*err_return, err, EXCP_NOTSUP_N(env, algorithm_arg_num, "DSA not supported"));
 #endif
-    } else
+    } else {
+#ifdef HAS_3_0_API
+        if (!get_pkey_from_octet_string(env, argv[algorithm_arg_num], argv[key_arg_num],
+                                        PKEY_PUB, pkey, err_return)) {
+            goto err;
+        }
+#else
         assign_goto(*err_return, err, EXCP_BADARG_N(env, algorithm_arg_num, "Bad algorithm"));
+#endif
+    }
+
 
     ret = 1;
 
@@ -528,8 +690,27 @@ ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
             assign_goto(ret, err, EXCP_ERROR(env, "Can't allocate new EVP_PKEY_CTX"));
 
         if (argv[0] != atom_eddsa) {
-            if (EVP_PKEY_sign_init(ctx) != 1)
+#ifdef HAS_3_0_API
+            EVP_SIGNATURE *sig_alg = get_prefetched_sign_algo(argv[0]);
+
+            if (!sig_alg) {
+                if (EVP_PKEY_sign_init(ctx) != 1) {
+                    assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_sign_init"));
+                }
+            }
+            else {
+#  ifdef HAVE_ML_DSA
+                if (EVP_PKEY_sign_message_init(ctx, sig_alg, NULL) != 1)
+                    assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_sign_message_init"));
+#  else
+                assign_goto(ret, err, EXCP_ERROR(env, "Unknown signature algorithm"));
+#  endif
+            }
+#else
+            if (EVP_PKEY_sign_init(ctx) != 1) {
                 assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_sign_init"));
+            }
+#endif
             if (md != NULL) {
                 if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
                     assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_CTX_set_signature_md"));
@@ -764,8 +945,27 @@ ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]
             assign_goto(ret, err, EXCP_ERROR(env, "Can't allocate new EVP_PKEY_CTX"));
 
         if (argv[0] != atom_eddsa) {
-            if (EVP_PKEY_verify_init(ctx) != 1)
-                assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_sign_init"));
+#ifdef HAS_3_0_API
+            EVP_SIGNATURE *sig_alg = get_prefetched_sign_algo(argv[0]);
+
+            if (!sig_alg) {
+                if (EVP_PKEY_verify_init(ctx) != 1) {
+                    assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_verify_init"));
+                }
+            }
+            else {
+#  ifdef HAVE_ML_DSA
+                if (EVP_PKEY_verify_message_init(ctx, sig_alg, NULL) != 1)
+                    assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_verify_message_init"));
+#  else
+                assign_goto(ret, err, EXCP_ERROR(env, "Unknown signature algorithm"));
+#  endif
+            }
+#else
+            if (EVP_PKEY_verify_init(ctx) != 1) {
+                assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_verify_init"));
+            }            
+#endif
             if (md != NULL) {
                 if (EVP_PKEY_CTX_set_signature_md(ctx, md) != 1)
                     assign_goto(ret, err, EXCP_ERROR(env, "Can't EVP_PKEY_CTX_set_signature_md"));
@@ -1042,7 +1242,7 @@ ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
     is_private = (argv[4] == atom_true);
     is_encrypt = (argv[5] == atom_true);
 
-    if (!check_pkey_algorithm_type(env, 0, argv[0], &ret))
+    if (!check_pkey_algorithm_type(env, 0, argv[0], 0, &ret))
         goto err; /* An exception is present in ret */
 
     if (!enif_inspect_binary(env, argv[1], &in_bin))
@@ -1294,7 +1494,7 @@ ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM
 
     ASSERT(argc == 2);
 
-    if (!check_pkey_algorithm_type(env, 0, argv[0], &ret))
+    if (!check_pkey_algorithm_type(env, 0, argv[0], 0, &ret))
         goto err; /* An exception is present in ret */
     
     if (!get_pkey_private_key(env, argv, 0, 1, &pkey, &ret)) // handles engine
diff --git a/lib/crypto/c_src/pkey.h b/lib/crypto/c_src/pkey.h
index a4eac62118..257a755aef 100644
--- a/lib/crypto/c_src/pkey.h
+++ b/lib/crypto/c_src/pkey.h
@@ -25,6 +25,22 @@
 
 #include "common.h"
 
+void prefetched_sign_algo_init(void);
+
+#ifdef HAS_3_0_API
+enum pkey_format_t {
+    PKEY_PUB  = 0,
+    PKEY_PRIV = 1,
+    PKEY_PRIV_SEED = 2
+};
+int get_pkey_from_octet_string(ErlNifEnv*,
+                               ERL_NIF_TERM alg_atom,
+                               ERL_NIF_TERM key_bin,
+                               enum pkey_format_t,
+                               EVP_PKEY **pkey_p,
+                               ERL_NIF_TERM *ret_p);
+#endif
+
 ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index a2c66bad54..938b898118 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -157,6 +157,7 @@ end
 -export([hash/2, hash_xof/3, hash_init/1, hash_update/2, hash_final/1, hash_final_xof/2]).
 -export([sign/4, sign/5, verify/5, verify/6]).
 -export([generate_key/2, generate_key/3, compute_key/4]).
+-export([encapsulate_key/2, decapsulate_key/3]).
 -export([exor/2, strong_rand_bytes/1, mod_pow/3]).
 -export([rand_seed/0, rand_seed_alg/1, rand_seed_alg/2]).
 -export([rand_seed_s/0, rand_seed_alg_s/1, rand_seed_alg_s/2]).
@@ -268,6 +269,7 @@ end
 
 -nifs([info_nif/0, info_lib/0, info_fips/0, enable_fips_mode_nif/1,
        hash_algorithms/0, pubkey_algorithms/0, cipher_algorithms/0,
+       kem_algorithms_nif/0,
        mac_algorithms/0, curve_algorithms/0, rsa_opts_algorithms/0,
        hash_info/1, hash_nif/2, hash_init_nif/1, hash_update_nif/2,
        hash_final_nif/1, hash_final_xof_nif/2, mac_nif/4, mac_init_nif/3, mac_update_nif/2,
@@ -277,6 +279,7 @@ end
        strong_rand_bytes_nif/1, strong_rand_range_nif/1, rand_uniform_nif/2,
        mod_exp_nif/4, do_exor/2, hash_equals_nif/2, pbkdf2_hmac_nif/5,
        pkey_sign_nif/5, pkey_verify_nif/6, pkey_crypt_nif/6,
+       encapsulate_key_nif/2, decapsulate_key_nif/3,
        rsa_generate_key_nif/2, dh_generate_key_nif/4, dh_compute_key_nif/3,
        evp_compute_key_nif/3, evp_generate_key_nif/2, privkey_to_pubkey_nif/2,
        srp_value_B_nif/5, srp_user_secret_nif/7, srp_host_secret_nif/5,
@@ -343,6 +346,10 @@ end
 -doc(#{title => <<"Keys">>}).
 -type key_integer() :: integer() | binary(). % Always binary() when used as return value
 
+-doc "Key encapsulation mechanisms.".
+-doc(#{title => <<"Key Encapsulation Mechanism">>}).
+-type kem() :: mlkem512 | mlkem768 | mlkem1024.
+
 %%% Keys
 -doc(#{title => <<"Public/Private Keys">>,equiv => rsa_params()}).
 -type rsa_public() :: [key_integer()] . % [E, N]
@@ -808,19 +815,22 @@ stop() ->
 -spec supports() -> [Support]
                         when Support :: {hashs,   Hashs}
                                       | {ciphers, Ciphers}
+                                      | {kems, KEMs}
                                       | {public_keys, PKs}
                                       | {macs,    Macs}
                                       | {curves,  Curves}
                                       | {rsa_opts, RSAopts},
                              Hashs :: [sha1() | sha2() | sha3() | sha3_xof() | blake2() | ripemd160 | sm3 | compatibility_only_hash()],
                              Ciphers :: [cipher()],
-                             PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
+                             KEMs :: [kem()],
+                             PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m | mldsa()],
                              Macs :: [hmac | cmac | poly1305],
                              Curves :: [ec_named_curve() | edwards_curve_dh() | edwards_curve_ed()],
                              RSAopts :: [rsa_sign_verify_opt() | rsa_opt()] .
 supports() ->
      [{hashs,       supports(hashs)},
-      {ciphers,     supports(ciphers)}
+      {ciphers,     supports(ciphers)},
+      {kems,        supports(kems)}
       | [{T,supports(T)} || T <- [public_keys,
                                   macs,
                                   curves,
@@ -840,18 +850,21 @@ algorithms.
 -spec supports(Type) -> Support
                         when Type :: hashs
 			           | ciphers
+                                   | kems
                                    | public_keys
                                    | macs
                                    | curves
                                    | rsa_opts,
 			     Support :: Hashs
                                       | Ciphers
+                                      | KEMs
                                       | PKs
                                       | Macs
                                       | Curves
                                       | RSAopts,
                              Hashs :: [sha1() | sha2() | sha3() | sha3_xof() | blake2() | ripemd160 | compatibility_only_hash()],
                              Ciphers :: [cipher()],
+                             KEMs :: [kem()],
                              PKs :: [rsa | dss | ecdsa | dh | ecdh | eddh | ec_gf2m],
                              Macs :: [hmac | cmac | poly1305],
                              Curves :: [ec_named_curve() | edwards_curve_dh() | edwards_curve_ed()],
@@ -863,6 +876,7 @@ algorithms.
 supports(hashs)       -> hash_algorithms();
 supports(public_keys) -> pubkey_algorithms();
 supports(ciphers)     -> add_cipher_aliases(cipher_algorithms());
+supports(kems)        -> kem_algorithms_nif();
 supports(macs)        -> mac_algorithms();
 supports(curves)      -> curve_algorithms();
 supports(rsa_opts)    -> rsa_opts_algorithms().
@@ -2424,7 +2438,10 @@ rand_seed_nif(_Seed) -> ?nif_stub.
 %%%================================================================
 -doc "Algorithms for sign and verify.".
 -doc(#{title => <<"Public Key Sign and Verify">>}).
--type pk_sign_verify_algs() :: rsa | dss | ecdsa | eddsa .
+-type pk_sign_verify_algs() :: rsa | dss | ecdsa | eddsa | mldsa().
+-type mldsa() :: mldsa44 | mldsa65 | mldsa87.
+-type mldsa_private() :: {seed | expandedkey, binary()}.
+-type mldsa_public() :: binary().
 
 -doc(#{title => <<"Public Key Sign and Verify">>,
        equiv => rsa_sign_verify_padding()}).
@@ -2470,6 +2487,7 @@ Options for sign and verify.
                            | dss_private()
                            | [ecdsa_private() | ecdsa_params()]
                            | [eddsa_private() | eddsa_params()]
+                           | mldsa_private()
                            | engine_key_ref(),
                       Signature :: binary() .
 
@@ -2503,6 +2521,7 @@ See also `public_key:sign/3`.
                            | dss_private()
                            | [ecdsa_private() | ecdsa_params()]
                            | [eddsa_private() | eddsa_params()]
+                           | mldsa_private()
                            | engine_key_ref(),
                       Options :: pk_sign_verify_opts(),
                       Signature :: binary() .
@@ -2534,6 +2553,7 @@ pkey_sign_nif(_Algorithm, _Type, _Digest, _Key, _Options) -> ?nif_stub.
                              | dss_public()
                              | [ecdsa_public() | ecdsa_params()]
                              | [eddsa_public() | eddsa_params()]
+                             | mldsa_public()
                              | engine_key_ref(),
                         Result :: boolean().
 
@@ -2567,6 +2587,7 @@ See also `public_key:verify/4`.
                              | dss_public()
                              | [ecdsa_public() | ecdsa_params()]
                              | [eddsa_public() | eddsa_params()]
+                             | mldsa_public()
                              | engine_key_ref(),
                         Options :: pk_sign_verify_opts(),
                         Result :: boolean().
@@ -2877,11 +2898,59 @@ generate_key(eddsa, Curve, PrivKey) when Curve == ed448 ;
     ?nif_call(evp_generate_key_nif(Curve, ensure_int_as_bin(PrivKey)),
               {2, 3},
               [eddsa, Curve, PrivKey]
-             ).
+             );
+generate_key(Type, [], PrivKey) ->
+    ?nif_call(evp_generate_key_nif(Type, PrivKey)).
+
 
 evp_generate_key_nif(_Curve, _PrivKey) -> ?nif_stub.
 
 
+-doc """
+Generate encapsulated shared secret from the other party's public key.
+
+Returns both a shared secret for encryption/decryption by local party and an
+encapsulated format of the same secret to be safely sent to the other
+party. With its private key, the other party can decapsulate the received secret
+(with `decapsulate_key/3` for example) to regenerate the same shared secret.
+
+Supported encapsulation methods can be obtained with
+[`supports(kems)`](`supports/1`).
+""".
+-doc(#{group => ~b"Key API",
+       since => ~b"OTP @OTP-19657@"}).
+-spec encapsulate_key(Type, OthersPublicKey) -> {Secret, EncapSecret}
+              when Type :: kem(),
+                   OthersPublicKey :: binary(),
+                   Secret :: binary(),
+                   EncapSecret :: binary().
+encapsulate_key(Type, OthersPublicKey) ->
+    encapsulate_key_nif(Type, OthersPublicKey).
+
+encapsulate_key_nif(_Type, _OthersPublicKey) -> ?nif_stub.
+
+
+-doc """
+Regenerate shared secret from encapsulated secret and private key.
+
+Returns a shared secret for encryption/decryption by local party.
+
+Supported encapsulation methods can be obtained with
+[`supports(kems)`](`supports/1`).
+""".
+-doc(#{group => ~b"Key API",
+       since => ~b"OTP @OTP-19657@"}).
+-spec decapsulate_key(Type, MyPrivKey, EncapSecret) -> Secret
+              when Type :: kem(),
+                   MyPrivKey :: binary(),
+                   EncapSecret :: binary(),
+                   Secret :: binary().
+decapsulate_key(Type, MyPrivKey, EncapSecret) ->
+    decapsulate_key_nif(Type, MyPrivKey, EncapSecret).
+
+decapsulate_key_nif(_Type, _MyPrivKey, _EncapSecret) -> ?nif_stub.
+
+
 -doc """
 Compute the shared secret from the private key and the other party's public
 key.
@@ -3822,6 +3891,7 @@ hash_equals_nif(_A, _B) -> ?nif_stub.
 hash_algorithms() -> ?nif_stub.
 pubkey_algorithms() -> ?nif_stub.
 cipher_algorithms() -> ?nif_stub.
+kem_algorithms_nif() -> ?nif_stub.
 mac_algorithms() -> ?nif_stub.
 curve_algorithms() -> ?nif_stub.
 rsa_opts_algorithms() -> ?nif_stub.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index dfdf8d8ffb..5be5da69bb 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -73,6 +73,7 @@
          compute_bug/1,
          crypto_load/1,
          crypto_load_and_call/1,
+         encapsulate/1,
          exor/0,
          exor/1,
          generate/0,
@@ -125,6 +126,7 @@
          hash_equals/1,
          sign_verify/0,
          sign_verify/1,
+         sign_verify_oqs/1,
          ec_key_padding/1,
          use_all_ec_sign_verify/1,
          use_all_ecdh_generate_compute/1,
@@ -219,6 +221,8 @@ all() ->
      ec_key_padding,
      node_supports_cache,
      mod_pow,
+     encapsulate,
+     sign_verify_oqs,
      exor,
      rand_uniform,
      rand_threads,
@@ -695,8 +699,6 @@ simple_cipher_test(Cipher) ->
             error
     end.
 
-
-
 enc_dec(Cipher, Key, 0, _Mode, Plain) ->
     case crypto:crypto_one_time(Cipher, Key, Plain, true) of
         Encrypted when is_binary(Encrypted) ->
@@ -1242,6 +1244,62 @@ compute(Config) when is_list(Config) ->
     end,
     lists:foreach(fun do_compute/1, Gen).
 
+%%--------------------------------------------------------------------
+encapsulate(_Config) ->
+    case openssl_version() of
+        V when V < {3,5,0} ->
+            {skip, "Requires OpenSSL 3.5 "};
+        _ ->
+            KEM_algs = [mlkem512, mlkem768, mlkem1024],
+            Supported = crypto:supports(kems),
+            [begin
+                 true = lists:member(Alg, Supported),
+                 encap_decap(Alg)
+             end || Alg <- KEM_algs],
+            ok
+    end.
+
+encap_decap(Alg) ->
+    io:format("Alg = ~p\n", [Alg]),
+    {Pub, Priv} = crypto:generate_key(Alg, []),
+    {Pub, Priv} = crypto:generate_key(Alg, [], Priv),
+    {Secret, Encap} = crypto:encapsulate_key(Alg, Pub),
+    Secret2 = crypto:decapsulate_key(Alg, Priv, Encap),
+    {Secret2,Secret} = {Secret,Secret2},
+    ok.
+
+%%--------------------------------------------------------------------
+sign_verify_oqs(_Config) ->
+    case openssl_version() of
+        V when V < {3,5,0} ->
+            {skip, "Requires OpenSSL 3.5"};
+        _ ->
+            Supported = crypto:supports(public_keys),
+            [begin
+                 true = lists:member(Alg, Supported),
+                 sign_verify_oqs_do(Alg)
+             end
+             || Alg <- mldsa_sign_ciphers()],
+            ok
+    end.
+
+sign_verify_oqs_do(Alg) ->
+    io:format("Alg = ~p\n", [Alg]),
+    {Pub, Priv} = crypto:generate_key(Alg, []),
+    {Pub, Priv} = crypto:generate_key(Alg, [], Priv),
+    Msg = "Hejsan",
+    [begin
+         io:format("Hash = ~p\n", [Hash]),
+         Sign = crypto:sign(Alg, Hash, Msg, {expandedkey,Priv}),
+         true = crypto:verify(Alg, Hash, Msg, Sign, Pub)
+     end
+     || Hash <- [none, sha]],
+    ok.
+
+%% Supported by OpenSSL 3.5
+mldsa_sign_ciphers() ->
+    [mldsa44, mldsa65, mldsa87].
+
 %%--------------------------------------------------------------------
 use_all_ec_sign_verify(_Config) ->
     Msg = <<"hello world!">>,
@@ -4908,13 +4966,13 @@ bad_sign_name(_Config) ->
     ?chk_api_name(crypto:sign(rsa, foobar, "nothing", <<1:1024>>),
                   error:{badarg, {"pkey.c",_}, "Bad digest type"++_}),
     ?chk_api_name(crypto:sign(foobar, sha, "nothing", <<1:1024>>),
-                  error:{badarg, {"pkey.c",_}, "Bad algorithm"++_}).
+                  error:{_, {"pkey.c",_}, _}).
     
 bad_verify_name(_Config) ->
     ?chk_api_name(crypto:verify(rsa, foobar, "nothing", <<"nothing">>,  <<1:1024>>),
                   error:{badarg,{"pkey.c",_},"Bad digest type"++_}),
     ?chk_api_name(crypto:verify(foobar, sha, "nothing", <<"nothing">>, <<1:1024>>),
-                  error:{badarg, {"pkey.c",_}, "Bad algorithm"++_}).
+                  error:{_, {"pkey.c",_}, _}).
 
 
 %%%----------------------------------------------------------------
@@ -5077,3 +5135,12 @@ get_priv_pub({Type, undefined=_Hash, Private, Public, _Msg, _Signature}, Acc) ->
 get_priv_pub({Type, _Hash, Public, Private, _Msg}, Acc) -> [{Type,Private,Public} | Acc];
 get_priv_pub({Type, _Hash, Public, Private, _Msg, _Options}, Acc) -> [{Type,Private,Public} | Acc];
 get_priv_pub(_, Acc) -> Acc.
+
+openssl_version() ->
+    case crypto:info_lib() of
+        [{<<"OpenSSL">>,Ver,<<"OpenSSL",_/binary>>}] ->
+            <<Maj,Min,Patch>> = <<(Ver bsr 12):24/integer>>,
+            {Maj,Min,Patch};
+        _ ->
+            undefined
+    end.
-- 
2.43.0

openSUSE Build Service is sponsored by