Commit 4b57ee0b63 for strongswan.org

commit 4b57ee0b6356b777cdb7ffbe84beca3ea2ef9bc2
Author: Andreas Steffen <andreas.steffen@strongswan.org>
Date:   Mon Dec 23 21:12:15 2024 +0100

    botan: Add ML-DSA support

    While 3.13 added support for contexts, we currently can't pass them
    via FFI.

diff --git a/src/libstrongswan/plugins/botan/Makefile.am b/src/libstrongswan/plugins/botan/Makefile.am
index 20826c3a3f..32feff863d 100644
--- a/src/libstrongswan/plugins/botan/Makefile.am
+++ b/src/libstrongswan/plugins/botan/Makefile.am
@@ -27,6 +27,8 @@ libstrongswan_botan_la_SOURCES = \
 	botan_ec_private_key.h botan_ec_private_key.c \
 	botan_ed_public_key.h botan_ed_public_key.c \
 	botan_ed_private_key.h botan_ed_private_key.c \
+	botan_ml_dsa_public_key.h botan_ml_dsa_public_key.c \
+	botan_ml_dsa_private_key.h botan_ml_dsa_private_key.c \
 	botan_util.h botan_util.c \
 	botan_util_keys.h botan_util_keys.c \
 	botan_aead.h botan_aead.c \
diff --git a/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.c b/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.c
new file mode 100644
index 0000000000..b0a97a571c
--- /dev/null
+++ b/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.c
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2024 Andreas Steffen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "botan_ml_dsa_private_key.h"
+#include "botan_ml_dsa_public_key.h"
+#include "botan_util.h"
+
+#include <botan/build.h>
+
+#ifdef BOTAN_HAS_ML_DSA
+
+#include <asn1/asn1.h>
+#include <utils/debug.h>
+#include <credentials/keys/signature_params.h>
+
+typedef struct private_private_key_t private_private_key_t;
+
+#define ML_DSA_SEED_LEN 32
+
+/**
+ * Private data
+ */
+struct private_private_key_t {
+
+	/**
+	 * Public interface
+	 */
+	private_key_t public;
+
+	/**
+	 * Botan private key object
+	 */
+	botan_privkey_t key;
+
+	/**
+	 * Key type
+	 */
+	key_type_t type;
+
+	/**
+	 * Reference count
+	 */
+	refcount_t ref;
+};
+
+/* from botan_ml_dsa_public_key.c */
+const char *botan_ml_dsa_get_mldsa_mode(key_type_t type);
+
+METHOD(private_key_t, sign, bool,
+	private_private_key_t *this, signature_scheme_t scheme,
+	void *params, chunk_t data, chunk_t *signature)
+{
+	pqc_params_t pqc_params;
+	char *sig_mode;
+
+	if (key_type_from_signature_scheme(scheme) != this->type)
+	{
+		DBG1(DBG_LIB, "signature scheme %N not supported",
+			 signature_scheme_names, scheme);
+		return FALSE;
+	}
+
+	/* set PQC signature params. context strings are currently not supported
+	 * by Botan */
+	if (!pqc_params_create(params, &pqc_params))
+	{
+		return FALSE;
+	}
+	sig_mode = pqc_params.deterministic ? "Deterministic" : "Randomized";
+	pqc_params_free(&pqc_params);
+
+	return botan_get_signature(this->key, sig_mode, data, signature);
+}
+
+METHOD(private_key_t, decrypt, bool,
+	private_private_key_t *this, encryption_scheme_t scheme,
+	void *params, chunk_t crypto, chunk_t *plain)
+{
+	DBG1(DBG_LIB, "ML-DSA private key decryption not implemented");
+	return FALSE;
+}
+
+METHOD(private_key_t, get_keysize, int,
+	private_private_key_t *this)
+{
+	return BITS_PER_BYTE * get_public_key_size(this->type);
+}
+
+METHOD(private_key_t, get_type, key_type_t,
+	private_private_key_t *this)
+{
+	return this->type;
+}
+
+METHOD(private_key_t, get_public_key, public_key_t*,
+	private_private_key_t *this)
+{
+	botan_pubkey_t pubkey;
+
+	if (botan_privkey_export_pubkey(&pubkey, this->key))
+	{
+		return NULL;
+	}
+	return botan_ml_dsa_public_key_adopt(pubkey, this->type);
+}
+
+METHOD(private_key_t, get_fingerprint, bool,
+	private_private_key_t *this, cred_encoding_type_t type,	chunk_t *fingerprint)
+{
+	botan_pubkey_t pubkey;
+	bool success = FALSE;
+
+	/* check the cache before doing the export */
+	if (lib->encoding->get_cache(lib->encoding, type, this, fingerprint))
+	{
+		return TRUE;
+	}
+
+	if (botan_privkey_export_pubkey(&pubkey, this->key))
+	{
+		return FALSE;
+	}
+	success = botan_get_fingerprint(pubkey, this, type, fingerprint);
+	botan_pubkey_destroy(pubkey);
+
+	return success;
+}
+
+METHOD(private_key_t, get_encoding, bool,
+	private_private_key_t *this, cred_encoding_type_t type,
+	chunk_t *encoding)
+{
+	return botan_get_privkey_encoding(this->key, type, encoding);
+}
+
+METHOD(private_key_t, get_ref, private_key_t*,
+	private_private_key_t *this)
+{
+	ref_get(&this->ref);
+	return &this->public;
+}
+
+METHOD(private_key_t, destroy, void,
+	private_private_key_t *this)
+{
+	if (ref_put(&this->ref))
+	{
+		lib->encoding->clear_cache(lib->encoding, this);
+		botan_privkey_destroy(this->key);
+		free(this);
+	}
+}
+
+/**
+ * Internal generic constructor
+ */
+static private_private_key_t *create_empty(key_type_t type)
+{
+	private_private_key_t *this;
+
+	INIT(this,
+		.public = {
+			.get_type = _get_type,
+			.sign = _sign,
+			.decrypt = _decrypt,
+			.get_keysize = _get_keysize,
+			.get_public_key = _get_public_key,
+			.equals = private_key_equals,
+			.belongs_to = private_key_belongs_to,
+			.get_fingerprint = _get_fingerprint,
+			.has_fingerprint = private_key_has_fingerprint,
+			.get_encoding = _get_encoding,
+			.get_ref = _get_ref,
+			.destroy = _destroy,
+		},
+		.type = type,
+		.ref = 1,
+	);
+
+	return this;
+}
+
+/*
+ * Described in header
+ */
+private_key_t *botan_ml_dsa_private_key_adopt(botan_privkey_t key,
+											  key_type_t type)
+{
+	private_private_key_t *this;
+
+	this = create_empty(type);
+	this->key = key;
+
+	return &this->public;
+}
+
+/*
+ * Described in header
+ */
+private_key_t *botan_ml_dsa_private_key_gen(key_type_t type, va_list args)
+{
+	private_private_key_t *this;
+	botan_rng_t rng;
+
+	while (TRUE)
+	{
+		switch (va_arg(args, builder_part_t))
+		{
+			case BUILD_KEY_SIZE:
+				/* just ignore the key size */
+				va_arg(args, u_int);
+				continue;
+			case BUILD_END:
+				break;
+			default:
+				return NULL;
+		}
+		break;
+	}
+
+	if (!botan_get_rng(&rng, RNG_TRUE))
+	{
+		return NULL;
+	}
+
+	this = create_empty(type);
+
+	if (botan_privkey_create(&this->key, "ML-DSA",
+							 botan_ml_dsa_get_mldsa_mode(type), rng))
+	{
+		DBG1(DBG_LIB, "%N private key generation failed", key_type_names, type);
+		botan_rng_destroy(rng);
+		free(this);
+		return NULL;
+	}
+
+	botan_rng_destroy(rng);
+	return &this->public;
+}
+
+/*
+ * Described in header
+ */
+private_key_t *botan_ml_dsa_private_key_load(key_type_t type, va_list args)
+{
+	private_private_key_t *this;
+	chunk_t priv = chunk_empty;
+
+	while (TRUE)
+	{
+		switch (va_arg(args, builder_part_t))
+		{
+			case BUILD_BLOB:
+				priv = va_arg(args, chunk_t);
+				continue;
+			case BUILD_END:
+				break;
+			default:
+				return NULL;
+		}
+		break;
+	}
+
+	if (priv.len == ML_DSA_SEED_LEN + 2 &&
+		priv.ptr[0] == 0x80 && priv.ptr[1] == ML_DSA_SEED_LEN)
+	{
+		priv = chunk_skip(priv, 2);
+	}
+	if (priv.len != ML_DSA_SEED_LEN)
+	{
+		DBG1(DBG_LIB, "error: the size of the loaded ML-DSA private key seed "
+			 "is %zu bytes instead of %u bytes", priv.len, ML_DSA_SEED_LEN);
+		return NULL;
+	}
+
+	this = create_empty(type);
+
+	if (botan_privkey_load_ml_dsa(&this->key, priv.ptr, priv.len,
+								  botan_ml_dsa_get_mldsa_mode(type)))
+	{
+		destroy(this);
+		return NULL;
+	}
+
+	return &this->public;
+}
+
+#endif
diff --git a/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.h b/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.h
new file mode 100644
index 0000000000..df3ccddc9d
--- /dev/null
+++ b/src/libstrongswan/plugins/botan/botan_ml_dsa_private_key.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 Andreas Steffen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @defgroup botan_ml_dsa_private_key botan_ml_dsa_private_key
+ * @{ @ingroup botan_p
+ */
+
+#ifndef BOTAN_ML_DSA_PRIVATE_KEY_H_
+#define BOTAN_ML_DSA_PRIVATE_KEY_H_
+
+#include <botan/ffi.h>
+
+#include <credentials/builder.h>
+#include <credentials/keys/private_key.h>
+
+/**
+ * Generate an ML-DSA private key using Botan.
+ *
+ * @param type		key type must be KEY_ML_DSA_44, KEY_ML_DSA_65 or KEY_ML_DSA_87
+ * @param args		builder_part_t argument list
+ * @return 			generated key, NULL on failure
+ */
+private_key_t *botan_ml_dsa_private_key_gen(key_type_t type, va_list args);
+
+/**
+ * Load an ML-DSA private key using Botan.
+ *
+ * @param type		key type must be KEY_ML_DSA_44, KEY_ML_DSA_65 or KEY_ML_DSA_87
+ * @param args		builder_part_t argument list
+ * @return 			loaded key, NULL on failure
+ */
+private_key_t *botan_ml_dsa_private_key_load(key_type_t type, va_list args);
+
+/**
+ * Load an ML-DSA private key by adopting a botan_privkey_t object.
+ *
+ * @param key		private key object (adopted)
+ * @param type		key type must be KEY_ML_DSA_44, KEY_ML_DSA_65 or KEY_ML_DSA_87
+ * @return			loaded key, NULL on failure
+ */
+private_key_t *botan_ml_dsa_private_key_adopt(botan_privkey_t key,
+											  key_type_t type);
+#endif /** BOTAN_ML_DSA_PRIVATE_KEY_H_ @}*/
diff --git a/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.c b/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.c
new file mode 100644
index 0000000000..a55c6aa4e8
--- /dev/null
+++ b/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 Andreas Steffen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "botan_ml_dsa_public_key.h"
+#include "botan_util.h"
+
+#include <botan/build.h>
+
+#ifdef BOTAN_HAS_ML_DSA
+
+#include <utils/debug.h>
+
+typedef struct private_public_key_t private_public_key_t;
+
+/**
+ * Private data
+ */
+struct private_public_key_t {
+
+	/**
+	 * Public interface
+	 */
+	public_key_t public;
+
+	/**
+	 * Botan public key object
+	 */
+	botan_pubkey_t key;
+
+	/**
+	 * Key type
+	 */
+	key_type_t type;
+
+	/**
+	 * Reference counter
+	 */
+	refcount_t ref;
+};
+
+METHOD(public_key_t, get_type, key_type_t,
+	private_public_key_t *this)
+{
+	return this->type;
+}
+
+METHOD(public_key_t, get_keysize, int,
+	private_public_key_t *this)
+{
+	return BITS_PER_BYTE * get_public_key_size(this->type);
+}
+
+METHOD(public_key_t, verify, bool,
+	private_public_key_t *this, signature_scheme_t scheme,
+	void *params, chunk_t data, chunk_t signature)
+{
+	if (key_type_from_signature_scheme(scheme) != this->type)
+	{
+		DBG1(DBG_LIB, "signature scheme %N not supported via botan",
+			 signature_scheme_names, scheme);
+		return FALSE;
+	}
+	return botan_verify_signature(this->key, "Pure", data, signature);
+}
+
+METHOD(public_key_t, encrypt, bool,
+	private_public_key_t *this, encryption_scheme_t scheme,
+	void *params, chunk_t crypto, chunk_t *plain)
+{
+	DBG1(DBG_LIB, "ML-DSA public key encryption not implemented");
+	return FALSE;
+}
+
+METHOD(public_key_t, get_fingerprint, bool,
+	private_public_key_t *this, cred_encoding_type_t type, chunk_t *fingerprint)
+{
+	return botan_get_fingerprint(this->key, this, type, fingerprint);
+}
+
+METHOD(public_key_t, get_encoding, bool,
+	private_public_key_t *this, cred_encoding_type_t type,
+	chunk_t *encoding)
+{
+	return botan_get_encoding(this->key, type, encoding);
+}
+
+METHOD(public_key_t, get_ref, public_key_t*,
+	private_public_key_t *this)
+{
+	ref_get(&this->ref);
+	return &this->public;
+}
+
+METHOD(public_key_t, destroy, void,
+	private_public_key_t *this)
+{
+	if (ref_put(&this->ref))
+	{
+		lib->encoding->clear_cache(lib->encoding, this);
+		botan_pubkey_destroy(this->key);
+		free(this);
+	}
+}
+
+/**
+ * Internal generic constructor
+ */
+static private_public_key_t *create_empty(key_type_t type)
+{
+	private_public_key_t *this;
+
+	INIT(this,
+		.public = {
+			.get_type = _get_type,
+			.verify = _verify,
+			.encrypt = _encrypt,
+			.get_keysize = _get_keysize,
+			.equals = public_key_equals,
+			.get_fingerprint = _get_fingerprint,
+			.has_fingerprint = public_key_has_fingerprint,
+			.get_encoding = _get_encoding,
+			.get_ref = _get_ref,
+			.destroy = _destroy,
+		},
+		.type = type,
+		.ref = 1,
+	);
+
+	return this;
+}
+
+/*
+ * Described in header
+ */
+public_key_t *botan_ml_dsa_public_key_adopt(botan_pubkey_t key,
+											key_type_t type)
+{
+	private_public_key_t *this;
+
+	this = create_empty(type);
+	this->key = key;
+
+	return &this->public;
+}
+
+/**
+ * Returns the Botan ML-DSA mode for a given key type
+ */
+const char *botan_ml_dsa_get_mldsa_mode(key_type_t type)
+{
+	switch (type)
+	{
+		case KEY_ML_DSA_44:
+			return "ML-DSA-4x4";
+		case KEY_ML_DSA_65:
+			return "ML-DSA-6x5";
+		case KEY_ML_DSA_87:
+			return "ML-DSA-8x7";
+		default:
+			return NULL;
+	}
+}
+
+/*
+ * Described in header
+ */
+public_key_t *botan_ml_dsa_public_key_load(key_type_t type, va_list args)
+{
+	private_public_key_t *this;
+	chunk_t key = chunk_empty;
+
+	while (TRUE)
+	{
+		switch (va_arg(args, builder_part_t))
+		{
+			case BUILD_BLOB:
+				key = va_arg(args, chunk_t);
+				continue;
+			case BUILD_END:
+				break;
+			default:
+				return NULL;
+		}
+		break;
+	}
+
+	this = create_empty(type);
+
+	if (botan_pubkey_load_ml_dsa(&this->key, key.ptr, key.len,
+								 botan_ml_dsa_get_mldsa_mode(type)))
+	{
+		free(this);
+		return NULL;
+	}
+
+	return &this->public;
+}
+
+#endif
diff --git a/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.h b/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.h
new file mode 100644
index 0000000000..63ce0dc57d
--- /dev/null
+++ b/src/libstrongswan/plugins/botan/botan_ml_dsa_public_key.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2024 Andreas Steffen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/**
+ * @defgroup botan_ml_dsa_public_key botan_ml_dsa_public_key
+ * @{ @ingroup botan_p
+ */
+
+#ifndef BOTAN_ML_DSA_PUBLIC_KEY_H_
+#define BOTAN_ML_DSA_PUBLIC_KEY_H_
+
+#include <botan/ffi.h>
+
+#include <credentials/builder.h>
+#include <credentials/keys/public_key.h>
+
+/**
+ * Load an ML-DSA public key by adopting a botan_pubkey_t object.
+ *
+ * @param key		public key object (adopted)
+ * @param type		key type must be KEY_ML_DSA_44, KEY_ML_DSA_65 or KEY_ML_DSA_87
+ * @return			loaded key, NULL on failure
+ */
+public_key_t *botan_ml_dsa_public_key_adopt(botan_pubkey_t key,
+											key_type_t type);
+
+/**
+ * Load an ML-DSA public key using Botan.
+ *
+ * @param type		key type must be KEY_ML_DSA_44, KEY_ML_DSA_65 or KEY_ML_DSA_87
+ * @param args		builder_part_t argument list
+ * @return 			loaded key, NULL on failure
+ */
+public_key_t *botan_ml_dsa_public_key_load(key_type_t type, va_list args);
+
+#endif /** BOTAN_ML_DSA_PUBLIC_KEY_H_ @}*/
diff --git a/src/libstrongswan/plugins/botan/botan_plugin.c b/src/libstrongswan/plugins/botan/botan_plugin.c
index 876a0754e3..b506bc6580 100644
--- a/src/libstrongswan/plugins/botan/botan_plugin.c
+++ b/src/libstrongswan/plugins/botan/botan_plugin.c
@@ -40,6 +40,8 @@
 #include "botan_ec_private_key.h"
 #include "botan_ed_public_key.h"
 #include "botan_ed_private_key.h"
+#include "botan_ml_dsa_public_key.h"
+#include "botan_ml_dsa_private_key.h"
 #include "botan_aead.h"
 #include "botan_util_keys.h"
 #include "botan_x25519.h"
@@ -220,7 +222,7 @@ METHOD(plugin_t, get_features, int,

 		/* generic key loaders */
 #if defined (BOTAN_HAS_RSA) || defined(BOTAN_HAS_ECDSA) || \
-	defined(BOTAN_HAS_ED25519)
+	defined(BOTAN_HAS_ED25519) || defined(BOTAN_HAS_ML_DSA)
 		PLUGIN_REGISTER(PUBKEY, botan_public_key_load, TRUE),
 			PLUGIN_PROVIDE(PUBKEY, KEY_ANY),
 #ifdef BOTAN_HAS_RSA
@@ -231,6 +233,11 @@ METHOD(plugin_t, get_features, int,
 #endif
 #ifdef BOTAN_HAS_ED25519
 			PLUGIN_PROVIDE(PUBKEY, KEY_ED25519),
+#endif
+#ifdef BOTAN_HAS_ML_DSA
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_44),
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_65),
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_87),
 #endif
 		PLUGIN_REGISTER(PRIVKEY, botan_private_key_load, TRUE),
 			PLUGIN_PROVIDE(PRIVKEY, KEY_ANY),
@@ -243,6 +250,11 @@ METHOD(plugin_t, get_features, int,
 #ifdef BOTAN_HAS_ED25519
 			PLUGIN_PROVIDE(PRIVKEY, KEY_ED25519),
 #endif
+#ifdef BOTAN_HAS_ML_DSA
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_44),
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_65),
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_87),
+#endif
 #endif
 		/* RSA */
 #ifdef BOTAN_HAS_RSA
@@ -354,6 +366,31 @@ METHOD(plugin_t, get_features, int,
 			PLUGIN_PROVIDE(PRIVKEY_GEN, KEY_ED25519),
 		PLUGIN_PROVIDE(PRIVKEY_SIGN, SIGN_ED25519),
 		PLUGIN_PROVIDE(PUBKEY_VERIFY, SIGN_ED25519),
+#endif
+
+#ifdef BOTAN_HAS_ML_DSA
+		/* ML-DSA private/public key loading */
+		PLUGIN_REGISTER(PUBKEY, botan_ml_dsa_public_key_load, TRUE),
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_44),
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_65),
+			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_87),
+		PLUGIN_REGISTER(PRIVKEY, botan_ml_dsa_private_key_load, TRUE),
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_44),
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_65),
+			PLUGIN_PROVIDE(PRIVKEY, KEY_ML_DSA_87),
+		PLUGIN_REGISTER(PRIVKEY_GEN, botan_ml_dsa_private_key_gen, FALSE),
+			PLUGIN_PROVIDE(PRIVKEY_GEN, KEY_ML_DSA_44),
+			PLUGIN_PROVIDE(PRIVKEY_GEN, KEY_ML_DSA_65),
+			PLUGIN_PROVIDE(PRIVKEY_GEN, KEY_ML_DSA_87),
+		PLUGIN_PROVIDE(PRIVKEY_SIGN, SIGN_ML_DSA_44),
+		PLUGIN_PROVIDE(PRIVKEY_SIGN, SIGN_ML_DSA_65),
+		PLUGIN_PROVIDE(PRIVKEY_SIGN, SIGN_ML_DSA_87),
+		PLUGIN_PROVIDE(PUBKEY_VERIFY, SIGN_ML_DSA_44),
+		PLUGIN_PROVIDE(PUBKEY_VERIFY, SIGN_ML_DSA_65),
+		PLUGIN_PROVIDE(PUBKEY_VERIFY, SIGN_ML_DSA_87),
+#endif
+
+#if defined(BOTAN_HAS_ED25519) || defined(BOTAN_HAS_ML_DSA)
 		/* register a pro forma identity hasher, never instantiated */
 		PLUGIN_REGISTER(HASHER, return_null),
 			PLUGIN_PROVIDE(HASHER, HASH_IDENTITY),
diff --git a/src/libstrongswan/plugins/botan/botan_util.c b/src/libstrongswan/plugins/botan/botan_util.c
index 0c867037a0..e218f221a6 100644
--- a/src/libstrongswan/plugins/botan/botan_util.c
+++ b/src/libstrongswan/plugins/botan/botan_util.c
@@ -177,6 +177,10 @@ bool botan_get_encoding(botan_pubkey_t pubkey, cred_encoding_type_t type,
 		{
 			part = CRED_PART_EDDSA_PUB_ASN1_DER;
 		}
+		else if (streq(algo, "ML-DSA"))
+		{
+			part = CRED_PART_PUB_ASN1_DER;
+		}
 		success = lib->encoding->encode(lib->encoding, type, NULL, encoding,
 										part, asn1_encoding, CRED_PART_END);
 	}
diff --git a/src/libstrongswan/plugins/botan/botan_util_keys.c b/src/libstrongswan/plugins/botan/botan_util_keys.c
index 127d988cb2..9ee7076eee 100644
--- a/src/libstrongswan/plugins/botan/botan_util_keys.c
+++ b/src/libstrongswan/plugins/botan/botan_util_keys.c
@@ -26,6 +26,8 @@
 #include "botan_ec_private_key.h"
 #include "botan_ed_public_key.h"
 #include "botan_ed_private_key.h"
+#include "botan_ml_dsa_public_key.h"
+#include "botan_ml_dsa_private_key.h"
 #include "botan_rsa_public_key.h"
 #include "botan_rsa_private_key.h"

@@ -128,6 +130,29 @@ public_key_t *botan_public_key_load(key_type_t type, va_list args)
 		this = botan_ed_public_key_adopt(pubkey);
 	}
 	else
+#endif
+#ifdef BOTAN_HAS_ML_DSA
+	if (streq(name, "ML-DSA") && (type == KEY_ANY || type == KEY_ML_DSA_44 ||
+								  type == KEY_ML_DSA_65 || type == KEY_ML_DSA_87))
+	{
+		if (type == KEY_ANY)
+		{
+			type = public_key_info_decode(blob, NULL);
+		}
+		else if (type != public_key_info_decode(blob, NULL))
+		{	/* reject inconsistent request */
+			type = KEY_ANY;
+		}
+		if (type != KEY_ANY)
+		{
+			this = botan_ml_dsa_public_key_adopt(pubkey, type);
+		}
+		else
+		{
+			botan_pubkey_destroy(pubkey);
+		}
+	}
+	else
 #endif
 	{
 		botan_pubkey_destroy(pubkey);
@@ -158,6 +183,25 @@ static int determine_ec_oid(chunk_t pkcs8)
 }
 #endif

+#ifdef BOTAN_HAS_ML_DSA
+/**
+ * Determine the ML-DSA private key type from a PKCS#8 structure
+ */
+static key_type_t key_type_from_pkcs8(chunk_t pkcs8)
+{
+	int oid = OID_UNKNOWN;
+	chunk_t inner;
+
+	if (asn1_unwrap(&pkcs8, &pkcs8) == ASN1_SEQUENCE &&
+		asn1_unwrap(&pkcs8, &inner) == ASN1_INTEGER &&
+		asn1_parse_integer_uint64(inner) == 0)
+	{
+		oid = asn1_parse_algorithmIdentifier(pkcs8, 0, NULL);
+	}
+	return key_type_from_oid(oid);
+}
+#endif
+
 /*
  * Described in header
  */
@@ -232,6 +276,25 @@ private_key_t *botan_private_key_load(key_type_t type, va_list args)
 	{
 		this = botan_ed_private_key_adopt(key);
 	}
+	else
+#endif
+#ifdef BOTAN_HAS_ML_DSA
+	if (streq(name, "ML-DSA") && (type == KEY_ANY || type == KEY_ML_DSA_44 ||
+								  type == KEY_ML_DSA_65 || type == KEY_ML_DSA_87))
+	{
+		if (type == KEY_ANY)
+		{
+			type = key_type_from_pkcs8(blob);
+		}
+		else if (type != key_type_from_pkcs8(blob))
+		{	/* reject inconsistent request */
+			type = KEY_ANY;
+		}
+		if (type != KEY_ANY)
+		{
+			this = botan_ml_dsa_private_key_adopt(key, type);
+		}
+	}
 #endif

 	if (!this)