Commit 8c8768f9e8 for strongswan.org

commit 8c8768f9e8995960b85476a352660ff992087eaf
Author: Tobias Brunner <tobias@strongswan.org>
Date:   Mon Nov 9 18:04:41 2026 +0100

    openssl: Add support for ML-DSA via AWS-LC

    Besides the API differences, AWS-LC also doesn't support deterministic
    mode, so we can't test whether the signatures are as expected via
    test vectors.  Instead, we sign twice and make sure the two signatures
    are actually different.  And while AWS-LC supports loading keys in
    expanded format, it can't encode them (it checks for a seed).  So to
    avoid issues with swanctl (loading an encrypted key and encoding it
    again to pass via vici), we reject them.

    Due to the version check AWS-LC v5.6.0 is required.  While older 5.x
    releases also have the functions, they use API version 35, which some
    versions announce that don't provide all the functions (and key encoding
    is a problem with older versions too).

diff --git a/src/libstrongswan/plugins/openssl/openssl_ml_dsa_private_key.c b/src/libstrongswan/plugins/openssl/openssl_ml_dsa_private_key.c
index 12e0d2c248..1f775c9ac1 100644
--- a/src/libstrongswan/plugins/openssl/openssl_ml_dsa_private_key.c
+++ b/src/libstrongswan/plugins/openssl/openssl_ml_dsa_private_key.c
@@ -16,10 +16,14 @@

 #include <openssl/evp.h>

-#if OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA)
+#if OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA) || \
+	(defined(OPENSSL_IS_AWSLC) && AWSLC_API_VERSION >= 36)
+
+#ifndef OPENSSL_IS_AWSLC
 #include <openssl/core_names.h>
 #include <openssl/params.h>
 #include <openssl/provider.h>
+#endif

 #include "openssl_ml_dsa_private_key.h"
 #include "openssl_util.h"
@@ -57,7 +61,11 @@ struct private_private_key_t {
 };

 /* from openssl_ml_dsa public key */
+#ifndef OPENSSL_IS_AWSLC
 char *openssl_ml_dsa_alg_name(key_type_t type);
+#else
+int openssl_ml_dsa_alg_nid(key_type_t type);
+#endif
 key_type_t openssl_ml_dsa_evp_pkey_key_type(EVP_PKEY *key);

 METHOD(private_key_t, sign, bool,
@@ -65,10 +73,6 @@ METHOD(private_key_t, sign, bool,
 	void *params, chunk_t data, chunk_t *signature)
 {
 	pqc_params_t pqc_params;
-	OSSL_PARAM ossl_params[] = { OSSL_PARAM_END, OSSL_PARAM_END, OSSL_PARAM_END};
-	EVP_PKEY_CTX *ctx = NULL;
-	EVP_SIGNATURE *sig_alg = NULL;
-	int deterministic;
 	bool success = FALSE;

 	if ((this->type == KEY_ML_DSA_44 && scheme != SIGN_ML_DSA_44) ||
@@ -85,6 +89,13 @@ METHOD(private_key_t, sign, bool,
 	{
 		return FALSE;
 	}
+
+#ifndef OPENSSL_IS_AWSLC
+	EVP_PKEY_CTX *ctx = NULL;
+	OSSL_PARAM ossl_params[] = { OSSL_PARAM_END, OSSL_PARAM_END, OSSL_PARAM_END};
+	EVP_SIGNATURE *sig_alg = NULL;
+	int deterministic;
+
 	deterministic = pqc_params.deterministic ? 1 : 0;
 	ossl_params[0] = OSSL_PARAM_construct_int(
 									OSSL_SIGNATURE_PARAM_DETERMINISTIC,
@@ -102,7 +113,6 @@ METHOD(private_key_t, sign, bool,
 		goto error;
 	}
 	sig_alg = EVP_SIGNATURE_fetch(NULL, openssl_ml_dsa_alg_name(this->type), NULL);
-
 	if (EVP_PKEY_sign_message_init(ctx, sig_alg, ossl_params) <= 0)
 	{
 		goto error;
@@ -116,17 +126,53 @@ METHOD(private_key_t, sign, bool,
 	*signature = chunk_alloc(signature->len);

 	if (EVP_PKEY_sign(ctx, signature->ptr, &signature->len,
-	                  data.ptr, data.len) <= 0)
+					  data.ptr, data.len) <= 0)
 	{
 		chunk_free(signature);
 		goto error;
 	}
+#else /* !OPENSSL_IS_AWSLC */
+	EVP_MD_CTX *ctx = NULL;
+	EVP_PKEY_CTX *pk_ctx = NULL;
+
+	ctx = EVP_MD_CTX_new();
+	if (!ctx || !EVP_DigestSignInit(ctx, &pk_ctx, NULL, NULL, this->key))
+	{
+		goto error;
+	}
+	/* AWS-LC does not support deterministic signatures */
+	if (pqc_params.ctx.len &&
+		!EVP_PKEY_CTX_set1_signature_context_string(pk_ctx, pqc_params.ctx.ptr,
+													pqc_params.ctx.len))
+	{
+		goto error;
+	}
+
+	if (!EVP_DigestSign(ctx, NULL, &signature->len, data.ptr, data.len))
+	{
+		goto error;
+	}
+
+	*signature = chunk_alloc(signature->len);
+
+	if (!EVP_DigestSign(ctx, signature->ptr, &signature->len,
+						data.ptr, data.len))
+	{
+		chunk_free(signature);
+		goto error;
+	}
+#endif /* !OPENSSL_IS_AWSLC */
+
 	success = TRUE;

 error:
 	pqc_params_free(&pqc_params);
+#ifndef OPENSSL_IS_AWSLC
 	EVP_SIGNATURE_free(sig_alg);
 	EVP_PKEY_CTX_free(ctx);
+#else
+	EVP_MD_CTX_free(ctx);
+#endif

 	return success;
 }
@@ -158,8 +204,12 @@ METHOD(private_key_t, get_public_key, public_key_t*,
 	u_char buf[2592];
 	chunk_t key = {buf, sizeof(buf)};

+#ifndef OPENSSL_IS_AWSLC
 	if (!EVP_PKEY_get_octet_string_param(this->key, OSSL_PKEY_PARAM_PUB_KEY,
 										 buf, sizeof(buf), &key.len))
+#else
+	if (!EVP_PKEY_get_raw_public_key(this->key, key.ptr, &key.len))
+#endif
 	{
 		return NULL;
 	}
@@ -265,6 +315,17 @@ private_key_t *openssl_ml_dsa_private_key_create(EVP_PKEY *key, bool engine)
 		EVP_PKEY_free(key);
 		return NULL;
 	}
+#ifdef OPENSSL_IS_AWSLC
+	size_t len;
+	/* AWS-LC can't export private keys in expanded format, so we reject them
+	 * when loading to avoid issues with swanctl */
+	if (!EVP_PKEY_get_private_seed(key, NULL, &len))
+	{
+		DBG1(DBG_LIB, "failed to load ML-DSA private key without seed");
+		EVP_PKEY_free(key);
+		return NULL;
+	}
+#endif
 	return create_internal(type, key);
 }

@@ -276,13 +337,27 @@ static private_key_t *create_instance(key_type_t type, chunk_t keyseed)
 	EVP_PKEY_CTX *ctx = NULL;
 	EVP_PKEY *key = NULL;

+#ifndef OPENSSL_IS_AWSLC
 	ctx = EVP_PKEY_CTX_new_from_name(NULL, openssl_ml_dsa_alg_name(type), NULL);
+#else
+	ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_PQDSA, NULL);
+	if (ctx)
+	{
+		if (!EVP_PKEY_CTX_pqdsa_set_params(ctx, openssl_ml_dsa_alg_nid(type)))
+		{
+			EVP_PKEY_CTX_free(ctx);
+			ctx = NULL;
+		}
+	}
+#endif
+
 	if (!ctx || EVP_PKEY_keygen_init(ctx) <= 0)
 	{
 		DBG1(DBG_LIB, "failed to create ctx");
 		goto end;
 	}

+#ifndef OPENSSL_IS_AWSLC
 	if (keyseed.ptr)
 	{
 		OSSL_PARAM params[] = {
@@ -303,6 +378,18 @@ static private_key_t *create_instance(key_type_t type, chunk_t keyseed)
 		DBG1(DBG_LIB, "failed to generate ML-DSA private key");
 		goto end;
 	}
+#else /* !OPENSSL_IS_AWSLC */
+	if (keyseed.ptr)
+	{
+		key = EVP_PKEY_pqdsa_new_raw_private_key(openssl_ml_dsa_alg_nid(type),
+												 keyseed.ptr, keyseed.len);
+	}
+	else if (!EVP_PKEY_keygen(ctx, &key))
+	{
+		DBG1(DBG_LIB, "failed to generate ML-DSA private key");
+		goto end;
+	}
+#endif /* !OPENSSL_IS_AWSLC */

 end:
 	EVP_PKEY_CTX_free(ctx);
@@ -380,4 +467,4 @@ private_key_t *openssl_ml_dsa_private_key_load(key_type_t type, va_list args)
 }


-#endif /* OPENSSL_NO_ML_DSA*/
+#endif /* OPENSSL_NO_ML_DSA || OPENSSL_IS_AWSLC */
diff --git a/src/libstrongswan/plugins/openssl/openssl_ml_dsa_public_key.c b/src/libstrongswan/plugins/openssl/openssl_ml_dsa_public_key.c
index 2678357aa7..192cbf8d3c 100644
--- a/src/libstrongswan/plugins/openssl/openssl_ml_dsa_public_key.c
+++ b/src/libstrongswan/plugins/openssl/openssl_ml_dsa_public_key.c
@@ -16,10 +16,14 @@

 #include <openssl/evp.h>

-#if OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA)
-#include <openssl/x509.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA) || \
+	(defined(OPENSSL_IS_AWSLC) && AWSLC_API_VERSION >= 36)
+
+#ifndef OPENSSL_IS_AWSLC
 #include <openssl/core_names.h>
 #include <openssl/params.h>
+#endif
+#include <openssl/x509.h>

 #include "openssl_ml_dsa_public_key.h"
 #include "openssl_util.h"
@@ -54,6 +58,7 @@ struct private_public_key_t {
 	refcount_t ref;
 };

+#ifndef OPENSSL_IS_AWSLC
 /**
  * Map a key type to an algorithm name (also used by private key)
  */
@@ -72,11 +77,49 @@ char *openssl_ml_dsa_alg_name(key_type_t type)
 	}
 }

+/**
+ * Map a key type to an EVP key type
+ */
+static int openssl_ml_dsa_key_type(key_type_t type)
+{
+	switch (type)
+	{
+		case KEY_ML_DSA_44:
+			return EVP_PKEY_ML_DSA_44;
+		case KEY_ML_DSA_65:
+			return EVP_PKEY_ML_DSA_65;
+		case KEY_ML_DSA_87:
+			return EVP_PKEY_ML_DSA_87;
+		default:
+			return 0;
+	}
+}
+#else /* !OPENSSL_IS_AWSLC */
+/**
+ * Map a key type to a NID (also used by private key)
+ */
+int openssl_ml_dsa_alg_nid(key_type_t type)
+{
+	switch (type)
+	{
+		case KEY_ML_DSA_44:
+			return NID_MLDSA44;
+		case KEY_ML_DSA_65:
+			return NID_MLDSA65;
+		case KEY_ML_DSA_87:
+			return NID_MLDSA87;
+		default:
+			return 0;
+	}
+}
+#endif /* !OPENSSL_IS_AWSLC */
+
 /**
  * Determine the ML-DSA key type for the given key (also used by private key)
  */
 key_type_t openssl_ml_dsa_evp_pkey_key_type(EVP_PKEY *key)
 {
+#ifndef OPENSSL_IS_AWSLC
 	if (EVP_PKEY_is_a(key, "ML-DSA-44"))
 	{
 		return KEY_ML_DSA_44;
@@ -90,24 +133,20 @@ key_type_t openssl_ml_dsa_evp_pkey_key_type(EVP_PKEY *key)
 		return KEY_ML_DSA_87;
 	}
 	return KEY_ANY;
-}
-
-/**
- * Map a key type to an EVP key type
- */
-static int openssl_ml_dsa_key_type(key_type_t type)
-{
-	switch (type)
+#else
+	int nid = EVP_PKEY_pqdsa_get_type(key);
+	switch (nid)
 	{
-		case KEY_ML_DSA_44:
-			return EVP_PKEY_ML_DSA_44;
-		case KEY_ML_DSA_65:
-			return EVP_PKEY_ML_DSA_65;
-		case KEY_ML_DSA_87:
-			return EVP_PKEY_ML_DSA_87;
+		case NID_MLDSA44:
+			return KEY_ML_DSA_44;
+		case NID_MLDSA65:
+			return KEY_ML_DSA_65;
+		case NID_MLDSA87:
+			return KEY_ML_DSA_87;
 		default:
-			return 0;
+			return KEY_ANY;
 	}
+#endif
 }

 METHOD(public_key_t, get_type, key_type_t,
@@ -121,9 +160,6 @@ METHOD(public_key_t, verify, bool,
 	void *params, chunk_t data, chunk_t signature)
 {
 	pqc_params_t pqc_params;
-	EVP_PKEY_CTX *ctx = NULL;
-	EVP_SIGNATURE *sig_alg = NULL;
-	OSSL_PARAM ossl_params[] = { OSSL_PARAM_END, OSSL_PARAM_END};
 	bool success = FALSE;

 	if ((this->type == KEY_ML_DSA_44 && scheme != SIGN_ML_DSA_44) ||
@@ -140,6 +176,12 @@ METHOD(public_key_t, verify, bool,
 	{
 		return FALSE;
 	}
+
+#ifndef OPENSSL_IS_AWSLC
+	EVP_PKEY_CTX *ctx = NULL;
+	OSSL_PARAM ossl_params[] = { OSSL_PARAM_END, OSSL_PARAM_END};
+	EVP_SIGNATURE *sig_alg = NULL;
+
 	if (pqc_params.ctx.len)
 	{
 		ossl_params[0] = OSSL_PARAM_construct_octet_string(
@@ -158,17 +200,42 @@ METHOD(public_key_t, verify, bool,
 	{
 		goto error;
 	}
-
 	if (EVP_PKEY_verify(ctx, signature.ptr, signature.len, data.ptr, data.len) <= 0)
 	{
 		goto error;
 	}
+#else /* !OPENSSL_IS_AWSLC */
+	EVP_MD_CTX *ctx = NULL;
+	EVP_PKEY_CTX *pk_ctx = NULL;
+
+	ctx = EVP_MD_CTX_new();
+	if (!ctx || !EVP_DigestVerifyInit(ctx, &pk_ctx, NULL, NULL, this->key))
+	{
+		goto error;
+	}
+	if (pqc_params.ctx.len &&
+		!EVP_PKEY_CTX_set1_signature_context_string(pk_ctx, pqc_params.ctx.ptr,
+													pqc_params.ctx.len))
+	{
+		goto error;
+	}
+	if (!EVP_DigestVerify(ctx, signature.ptr, signature.len,
+						  data.ptr, data.len))
+	{
+		goto error;
+	}
+#endif /* !OPENSSL_IS_AWSLC */
+
 	success = TRUE;

 error:
 	pqc_params_free(&pqc_params);
+#ifndef OPENSSL_IS_AWSLC
 	EVP_SIGNATURE_free(sig_alg);
 	EVP_PKEY_CTX_free(ctx);
+#else
+	EVP_MD_CTX_free(ctx);
+#endif

 	return success;
 }
@@ -303,8 +370,13 @@ public_key_t *openssl_ml_dsa_public_key_load(key_type_t type, va_list args)

 	if (blob.len)
 	{
+#ifndef OPENSSL_IS_AWSLC
 		key = EVP_PKEY_new_raw_public_key(openssl_ml_dsa_key_type(type), NULL,
 										  blob.ptr, blob.len);
+#else
+		key = EVP_PKEY_pqdsa_new_raw_public_key(openssl_ml_dsa_alg_nid(type),
+												blob.ptr, blob.len);
+#endif
 		if (key)
 		{
 			return create_internal(type, key);
@@ -326,4 +398,4 @@ public_key_t *openssl_ml_dsa_public_key_load(key_type_t type, va_list args)
 	return NULL;
 }

-#endif /* OPENSSL_VERSION_NUMBER */
+#endif /* OPENSSL_VERSION_NUMBER || OPENSSL_IS_AWSLC */
diff --git a/src/libstrongswan/plugins/openssl/openssl_plugin.c b/src/libstrongswan/plugins/openssl/openssl_plugin.c
index ae7af87772..1c9247f77c 100644
--- a/src/libstrongswan/plugins/openssl/openssl_plugin.c
+++ b/src/libstrongswan/plugins/openssl/openssl_plugin.c
@@ -701,7 +701,8 @@ METHOD(plugin_t, get_features, int,
 		PLUGIN_REGISTER(HASHER, return_null),
 			PLUGIN_PROVIDE(HASHER, HASH_IDENTITY),
 #endif /* OPENSSL_VERSION_NUMBER && !OPENSSL_NO_EC && !OPENSSL_IS_AWSLC */
-#if (OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA))
+#if (OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA)) || \
+	(defined(OPENSSL_IS_AWSLC) && AWSLC_API_VERSION >= 36)
 		/* ML-DSA private/public key loading */
 		PLUGIN_REGISTER(PUBKEY, openssl_ml_dsa_public_key_load, TRUE),
 			PLUGIN_PROVIDE(PUBKEY, KEY_ML_DSA_44),
@@ -721,7 +722,7 @@ METHOD(plugin_t, get_features, int,
 		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 /* OPENSSL_NO_ML_DSA */
+#endif /* OPENSSL_NO_ML_DSA || OPENSSL_IS_AWSLC */

 		/* generic key loader */
 		PLUGIN_REGISTER(PUBKEY, openssl_public_key_load, TRUE),
diff --git a/src/libstrongswan/plugins/openssl/openssl_util.c b/src/libstrongswan/plugins/openssl/openssl_util.c
index 5bdfb42822..9d75b85fb6 100644
--- a/src/libstrongswan/plugins/openssl/openssl_util.c
+++ b/src/libstrongswan/plugins/openssl/openssl_util.c
@@ -189,6 +189,20 @@ private_key_t *openssl_wrap_private_key(EVP_PKEY *key, bool engine)
 #endif
 				return openssl_ed_private_key_create(key, engine);
 #endif /* OPENSSL_VERSION_NUMBER && !OPENSSL_NO_EC && !OPENSSL_IS_AWSLC */
+#if defined(OPENSSL_IS_AWSLC) && AWSLC_API_VERSION >= 36
+			case EVP_PKEY_PQDSA:
+				switch (EVP_PKEY_pqdsa_get_type(key))
+				{
+					case NID_MLDSA44:
+					case NID_MLDSA65:
+					case NID_MLDSA87:
+						return openssl_ml_dsa_private_key_create(key, FALSE);
+					default:
+						EVP_PKEY_free(key);
+						break;
+				}
+				break;
+#endif /* OPENSSL_IS_AWSLC */
 			default:
 #if (OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA))
 				/* OpenSSL does not provide an ID for ML-DSA keys, which is
@@ -231,6 +245,20 @@ public_key_t *openssl_wrap_public_key(EVP_PKEY *key)
 #endif
 				return openssl_ed_public_key_create(key);
 #endif /* OPENSSL_VERSION_NUMBER && !OPENSSL_NO_EC && !OPENSSL_IS_AWSLC */
+#if defined(OPENSSL_IS_AWSLC) && AWSLC_API_VERSION >= 36
+			case EVP_PKEY_PQDSA:
+				switch (EVP_PKEY_pqdsa_get_type(key))
+				{
+					case NID_MLDSA44:
+					case NID_MLDSA65:
+					case NID_MLDSA87:
+						return openssl_ml_dsa_public_key_create(key);
+					default:
+						EVP_PKEY_free(key);
+						break;
+				}
+				break;
+#endif /* OPENSSL_IS_AWSLC */
 			default:
 #if (OPENSSL_VERSION_NUMBER >= 0x30500000L && !defined(OPENSSL_NO_ML_DSA))
 				/* OpenSSL does not provide an ID for ML-DSA keys, which is
diff --git a/src/libstrongswan/tests/suites/test_ml_dsa.c b/src/libstrongswan/tests/suites/test_ml_dsa.c
index 9c42ce6d56..fc73556645 100644
--- a/src/libstrongswan/tests/suites/test_ml_dsa.c
+++ b/src/libstrongswan/tests/suites/test_ml_dsa.c
@@ -2640,12 +2640,17 @@ START_TEST(test_ml_dsa_sig)
 	private_key_t *key;
 	signature_scheme_t scheme;
 	pqc_params_t pqc_params;
-	chunk_t msg, signature, asn1, asn1_privkey, encoding;
+	chunk_t msg, signature, randsig = chunk_empty, asn1, asn1_privkey, encoding;

 	/* load private key in seed format */
 	key = lib->creds->create(lib->creds, CRED_PRIVATE_KEY, tests[_i].type,
 							 BUILD_BLOB, tests[_i].seed, BUILD_END);
-	ck_assert(key != NULL);
+	ck_assert(key);
+	/* load the corresponding public key */
+	pubkey = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, tests[_i].type,
+								BUILD_BLOB, tests[_i].pubkey, BUILD_END);
+
+	ck_assert(pubkey);

 	/* prepare signature generation */
 	scheme = signature_scheme_from_oid(key_type_to_oid(tests[_i].type));
@@ -2663,9 +2668,24 @@ START_TEST(test_ml_dsa_sig)
 	{
 		warn("non-empty context string in ML-DSA signature not supported");
 	}
-	else
+	else if (!chunk_equals(signature, tests[_i].sig_ctx))
 	{
-		ck_assert_chunk_eq(signature, tests[_i].sig_ctx);
+		/* if the implementation only supports random signatures, make sure
+		 * they are different and validate against the public key */
+		ck_assert(key->sign(key, scheme, &pqc_params, msg, &randsig));
+		if (!chunk_equals(signature, randsig))
+		{
+			warn("deterministic ML-DSA signatures not supported");
+		}
+		else
+		{
+			ck_assert_chunk_eq(signature, tests[_i].sig_ctx);
+		}
+	}
+	ck_assert(pubkey->verify(pubkey, scheme, &pqc_params, msg, signature));
+	if (randsig.len)
+	{
+		ck_assert(pubkey->verify(pubkey, scheme, &pqc_params, msg, randsig));
 	}
 	chunk_free(&signature);

@@ -2674,9 +2694,14 @@ START_TEST(test_ml_dsa_sig)

 	/* generate and verify signature with empty context string */
 	ck_assert(key->sign(key, scheme, &pqc_params, msg, &signature));
-	ck_assert_chunk_eq(signature, tests[_i].sig);
+	if (!randsig.len)
+	{
+		ck_assert_chunk_eq(signature, tests[_i].sig);
+	}
+	ck_assert(pubkey->verify(pubkey, scheme, &pqc_params, msg, signature));
 	chunk_free(&signature);
 	key->destroy(key);
+	pubkey->destroy(pubkey);

 	/* load private key in expanded format */
 	asn1 = chunk_cat("cc", tests[_i].asn1_privkey, tests[_i].privkey);
@@ -2740,7 +2765,10 @@ START_TEST(test_ml_dsa_sig)
 		chunk_free(&encoding);
 		/* generate and verify signature with empty context string */
 		ck_assert(key->sign(key, scheme, &pqc_params, msg, &signature));
-		ck_assert_chunk_eq(signature, tests[_i].sig);
+		if (!randsig.len)
+		{
+			ck_assert_chunk_eq(signature, tests[_i].sig);
+		}
 		chunk_free(&signature);
 		key->destroy(key);
 	}
@@ -2752,6 +2780,7 @@ START_TEST(test_ml_dsa_sig)
 							 BUILD_BLOB_ASN1_DER, asn1, BUILD_END);
 	ck_assert(!key);
 	chunk_free(&asn1);
+	chunk_free(&randsig);
 }
 END_TEST