[v4,1/4] libcamera: pub_key: Add ML-DSA-65 signature algorithm for PQC compliance
diff mbox series

Message ID 20260701040721.145659-2-hpa@redhat.com
State New
Headers show
Series
  • Implement ML-DSA-65 for Post-Quantum Cryptographic compliance
Related show

Commit Message

Kate Hsuan July 1, 2026, 4:07 a.m. UTC
With RSA slated for deprecation by 2035, the IPA signature algorithm must
be migrated to a Post-Quantum Cryptography (PQC) standard. Accordingly,
we are adopting the NIST-finalized ML-DSA signature scheme. Among the
available variants, ML-DSA-44, ML-DSA-65, and ML-DSA-87, which offer
varying levels of data integrity protection. ML-DSA-65 has been selected
to sign the IPA due to its balanced performance for general-purpose
applications.

Link: https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/evaluation-criteria/security-(evaluation-criteria)
Link: https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf
Signed-off-by: Kate Hsuan <hpa@redhat.com>
---
 src/libcamera/pub_key.cpp | 45 ++++++++++++++++++++++-----------------
 1 file changed, 25 insertions(+), 20 deletions(-)

Comments

Barnabás Pőcze July 1, 2026, 8:56 a.m. UTC | #1
Hi

2026. 07. 01. 6:07 keltezéssel, Kate Hsuan írta:
> With RSA slated for deprecation by 2035, the IPA signature algorithm must
> be migrated to a Post-Quantum Cryptography (PQC) standard. Accordingly,
> we are adopting the NIST-finalized ML-DSA signature scheme. Among the
> available variants, ML-DSA-44, ML-DSA-65, and ML-DSA-87, which offer
> varying levels of data integrity protection. ML-DSA-65 has been selected
> to sign the IPA due to its balanced performance for general-purpose
> applications.
> 
> Link: https://csrc.nist.gov/projects/post-quantum-cryptography/post-quantum-cryptography-standardization/evaluation-criteria/security-(evaluation-criteria)
> Link: https://nvlpubs.nist.gov/nistpubs/ir/2024/NIST.IR.8547.ipd.pdf
> Signed-off-by: Kate Hsuan <hpa@redhat.com>
> ---
>   src/libcamera/pub_key.cpp | 45 ++++++++++++++++++++++-----------------
>   1 file changed, 25 insertions(+), 20 deletions(-)
> 
> diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
> index f1d73a5c..539a4e6d 100644
> --- a/src/libcamera/pub_key.cpp
> +++ b/src/libcamera/pub_key.cpp
> @@ -14,8 +14,13 @@
>   #include <openssl/x509.h>
>   #elif HAVE_GNUTLS
>   #include <gnutls/abstract.h>
> +#include <gnutls/gnutls.h>
>   #endif
>   
> +#include <libcamera/base/utils.h>
> +
> +#include "libcamera/internal/pub_key.h"
> +
>   /**
>    * \file pub_key.h
>    * \brief Public key signature verification
> @@ -28,12 +33,17 @@ namespace libcamera {
>    * \brief Public key wrapper for signature verification
>    *
>    * The PubKey class wraps a public key and implements signature verification. It
> - * only supports RSA keys and the RSA-SHA256 signature algorithm.
> + * supports RSA keys with the RSA-SHA256 signature algorithm, or ML-DSA-65 keys
> + * as specified in NIST FIPS 204. The signature algorithm is determined at
> + * compile time.
>    */
>   
>   /**
>    * \brief Construct a PubKey from key data
>    * \param[in] key Key data encoded in DER format
> + *
> + * Supported key types are RSA (verified with RSA-SHA256) and ML-DSA-65
> + * (verified as ML-DSA-65 according to FIPS 204).
>    */
>   PubKey::PubKey([[maybe_unused]] Span<const uint8_t> key)
>   	: valid_(false)
> @@ -83,7 +93,7 @@ PubKey::~PubKey()
>    * \param[in] sig The signature
>    *
>    * Verify that the signature \a sig matches the signed \a data for the public
> - * key. The signture algorithm is hardcoded to RSA-SHA256.
> + * key.
>    *
>    * \return True if the signature is valid, false otherwise
>    */
> @@ -94,30 +104,21 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
>   		return false;
>   
>   #if HAVE_CRYPTO

Just for my understanding, the reason `IPA_MODULE_DIR_SIGNATURE_ALGO` is not checked
with libcrypto is because it will automatically determine the correct algorithm
(from the public key or such?)? And I imagine that is not an option with gnutls?


> -	/*
> -	 * Create and initialize a public key algorithm context for signature
> -	 * verification.
> -	 */
> -	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pubkey_, nullptr);
> +	EVP_MD_CTX *ctx = EVP_MD_CTX_new();
>   	if (!ctx)
>   		return false;
>   
> -	if (EVP_PKEY_verify_init(ctx) <= 0 ||
> -	    EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0 ||
> -	    EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) {
> -		EVP_PKEY_CTX_free(ctx);
> +	utils::scope_exit ctxGuard([&] { EVP_MD_CTX_free(ctx); });
> +
> +	if (EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr,
> +				 pubkey_) <= 0)
>   		return false;
> -	}
>   
> -	/* Calculate the SHA256 digest of the data. */
> -	uint8_t digest[SHA256_DIGEST_LENGTH];
> -	SHA256(data.data(), data.size(), digest);
> +	int ret = EVP_DigestVerify(ctx, sig.data(), sig.size(),
> +				   data.data(), data.size());
>   
> -	/* Decrypt the signature and verify it matches the digest. */
> -	int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
> -				  SHA256_DIGEST_LENGTH);
> -	EVP_PKEY_CTX_free(ctx);
>   	return ret == 1;
> +
>   #elif HAVE_GNUTLS
>   	const gnutls_datum_t gnuTlsData{
>   		const_cast<unsigned char *>(data.data()),
> @@ -129,8 +130,12 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
>   		static_cast<unsigned int>(sig.size())
>   	};
>   
> -	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_RSA_SHA256, 0,
> +	constexpr gnutls_sign_algorithm_t algo =
> +		std::string(IPA_MODULE_DIR_SIGNATURE_ALGO) == std::string("ml-dsa-65") ? GNUTLS_SIGN_MLDSA65 : GNUTLS_SIGN_RSA_SHA256;

Let's doy

	constexpr gnutls_sign_algorithm_t algo = [] {
		constexpr std::string_view name = IPA_MODULE_DIR_SIGNATURE_ALGO;

		if constexpr (name == "ml-dsa-65")
			return GNUTLS_SIGN_MLDSA65;
		if constexpr (name == "rsa-sha256")
			return GNUTLS_SIGN_RSA_SHA256;
	} ();

This way it should be a compilation error if you use an unexpected name for some reason.

> +
> +	int ret = gnutls_pubkey_verify_data2(pubkey_, algo, 0,
>   					     &gnuTlsData, &gnuTlsSig);
> +
>   	return ret >= 0;
>   #else
>   	return false;

Patch
diff mbox series

diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
index f1d73a5c..539a4e6d 100644
--- a/src/libcamera/pub_key.cpp
+++ b/src/libcamera/pub_key.cpp
@@ -14,8 +14,13 @@ 
 #include <openssl/x509.h>
 #elif HAVE_GNUTLS
 #include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
 #endif
 
+#include <libcamera/base/utils.h>
+
+#include "libcamera/internal/pub_key.h"
+
 /**
  * \file pub_key.h
  * \brief Public key signature verification
@@ -28,12 +33,17 @@  namespace libcamera {
  * \brief Public key wrapper for signature verification
  *
  * The PubKey class wraps a public key and implements signature verification. It
- * only supports RSA keys and the RSA-SHA256 signature algorithm.
+ * supports RSA keys with the RSA-SHA256 signature algorithm, or ML-DSA-65 keys
+ * as specified in NIST FIPS 204. The signature algorithm is determined at
+ * compile time.
  */
 
 /**
  * \brief Construct a PubKey from key data
  * \param[in] key Key data encoded in DER format
+ *
+ * Supported key types are RSA (verified with RSA-SHA256) and ML-DSA-65
+ * (verified as ML-DSA-65 according to FIPS 204).
  */
 PubKey::PubKey([[maybe_unused]] Span<const uint8_t> key)
 	: valid_(false)
@@ -83,7 +93,7 @@  PubKey::~PubKey()
  * \param[in] sig The signature
  *
  * Verify that the signature \a sig matches the signed \a data for the public
- * key. The signture algorithm is hardcoded to RSA-SHA256.
+ * key.
  *
  * \return True if the signature is valid, false otherwise
  */
@@ -94,30 +104,21 @@  bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
 		return false;
 
 #if HAVE_CRYPTO
-	/*
-	 * Create and initialize a public key algorithm context for signature
-	 * verification.
-	 */
-	EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pubkey_, nullptr);
+	EVP_MD_CTX *ctx = EVP_MD_CTX_new();
 	if (!ctx)
 		return false;
 
-	if (EVP_PKEY_verify_init(ctx) <= 0 ||
-	    EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0 ||
-	    EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0) {
-		EVP_PKEY_CTX_free(ctx);
+	utils::scope_exit ctxGuard([&] { EVP_MD_CTX_free(ctx); });
+
+	if (EVP_DigestVerifyInit(ctx, nullptr, nullptr, nullptr,
+				 pubkey_) <= 0)
 		return false;
-	}
 
-	/* Calculate the SHA256 digest of the data. */
-	uint8_t digest[SHA256_DIGEST_LENGTH];
-	SHA256(data.data(), data.size(), digest);
+	int ret = EVP_DigestVerify(ctx, sig.data(), sig.size(),
+				   data.data(), data.size());
 
-	/* Decrypt the signature and verify it matches the digest. */
-	int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
-				  SHA256_DIGEST_LENGTH);
-	EVP_PKEY_CTX_free(ctx);
 	return ret == 1;
+
 #elif HAVE_GNUTLS
 	const gnutls_datum_t gnuTlsData{
 		const_cast<unsigned char *>(data.data()),
@@ -129,8 +130,12 @@  bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
 		static_cast<unsigned int>(sig.size())
 	};
 
-	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_RSA_SHA256, 0,
+	constexpr gnutls_sign_algorithm_t algo =
+		std::string(IPA_MODULE_DIR_SIGNATURE_ALGO) == std::string("ml-dsa-65") ? GNUTLS_SIGN_MLDSA65 : GNUTLS_SIGN_RSA_SHA256;
+
+	int ret = gnutls_pubkey_verify_data2(pubkey_, algo, 0,
 					     &gnuTlsData, &gnuTlsSig);
+
 	return ret >= 0;
 #else
 	return false;