Commit c184efd78d for strongswan.org

commit c184efd78df54da8d6d73ad553b335630a7b3c83
Author: Andreas Steffen <andreas.steffen@strongswan.org>
Date:   Thu Sep 25 18:16:29 2025 +0200

    ml: Support all ML-DSA private key formats

diff --git a/src/libstrongswan/plugins/ml/ml_dsa_private_key.c b/src/libstrongswan/plugins/ml/ml_dsa_private_key.c
index b36079516c..088ec44bc6 100644
--- a/src/libstrongswan/plugins/ml/ml_dsa_private_key.c
+++ b/src/libstrongswan/plugins/ml/ml_dsa_private_key.c
@@ -975,7 +975,8 @@ private_key_t *ml_dsa_private_key_gen(key_type_t type, va_list args)
 private_key_t *ml_dsa_private_key_load(key_type_t type, va_list args)
 {
 	private_private_key_t *this;
-	chunk_t priv = chunk_empty;
+	chunk_t priv = chunk_empty, seed = chunk_empty;
+	int asn1_type;

 	while (TRUE)
 	{
@@ -996,17 +997,6 @@ private_key_t *ml_dsa_private_key_load(key_type_t type, va_list args)
 	{
 		return NULL;
 	}
-	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_instance(type);
 	if (!this)
@@ -1014,10 +1004,78 @@ private_key_t *ml_dsa_private_key_load(key_type_t type, va_list args)
 		return NULL;
 	}

-	if (!generate_keypair(this, chunk_clone(priv)))
+	if (priv.len == ML_DSA_SEED_LEN)
 	{
-		destroy(this);
-		return NULL;
+		asn1_type = ASN1_CONTEXT_S_0;
+	}
+	else
+	{
+		asn1_type = asn1_unwrap(&priv, &priv);
+	}
+
+	/* three supported ML-DSA private key formats */
+	switch(asn1_type)
+	{
+		/* private key in seed-only format */
+		case ASN1_CONTEXT_S_0:
+			seed = priv;
+			if (seed.len != ML_DSA_SEED_LEN ||
+			   !generate_keypair(this, chunk_clone(seed)))
+			{
+				DBG1(DBG_LIB, "failed to load ML-DSA private key seed");
+				destroy(this);
+				return NULL;
+			}
+			break;
+
+		/* private key in expanded format */
+		case ASN1_OCTET_STRING:
+			if (priv.len != this->params->privkey_len)
+			{
+				DBG1(DBG_LIB, "failed to load ML-DSA expanded private key");
+				destroy(this);
+				return NULL;
+			}
+			memcpy(this->privkey.ptr, priv.ptr, priv.len);
+			break;
+
+		/* private key in both seed and expanded format */
+		case ASN1_SEQUENCE:
+			if (asn1_unwrap(&priv, &seed) != ASN1_OCTET_STRING ||
+				seed.len != ML_DSA_SEED_LEN)
+			{
+				DBG1(DBG_LIB, "failed to identify ML-DSA private key seed");
+				destroy(this);
+				return NULL;
+			}
+			if (!generate_keypair(this, chunk_clone(seed)))
+			{
+				DBG1(DBG_LIB, "failed to load ML-DSA private key seed");
+				destroy(this);
+				return NULL;
+			}
+			if (asn1_unwrap(&priv, &priv) != ASN1_OCTET_STRING ||
+				priv.len != this->params->privkey_len)
+			{
+				DBG1(DBG_LIB, "failed to identify ML-DSA expanded private key");
+				destroy(this);
+				return NULL;
+			}
+			if (!chunk_equals(priv, this->privkey))
+			{
+				DBG1(DBG_LIB, "loaded expanded private key is not derived "
+							  "from loaded seed");
+				destroy(this);
+				return NULL;
+			}
+			break;
+
+		/* invalid private key format */
+		case ASN1_INVALID:
+		default:
+			DBG1(DBG_LIB, "unknown ML-DSA private key format");
+			destroy(this);
+			return NULL;
 	}

 	return &this->public;