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

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

Commit Message

Kate Hsuan April 8, 2026, 7:55 a.m. UTC
As quantum computing advances, traditional signature algorithms are
becoming vulnerable. To ensure long-term data security, this change
implements ML-DSA-65, the primary Post-Quantum Cryptography (PQC)
standard finalized by NIST. This addition prepares for the transition
away from RSA, which is slated for deprecation by 2035.

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 | 55 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 52 insertions(+), 3 deletions(-)

Comments

Barnabás Pőcze April 13, 2026, 9:18 a.m. UTC | #1
Hi

2026. 04. 08. 9:55 keltezéssel, Kate Hsuan írta:
> As quantum computing advances, traditional signature algorithms are
> becoming vulnerable. To ensure long-term data security, this change
> implements ML-DSA-65, the primary Post-Quantum Cryptography (PQC)
> standard finalized by NIST. This addition prepares for the transition
> away from RSA, which is slated for deprecation by 2035.

Is there any point in keeping the RSA path in libcamera?


Regards,
Barnabás Pőcze

> 
> 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 | 55 ++++++++++++++++++++++++++++++++++++---
>   1 file changed, 52 insertions(+), 3 deletions(-)
> 
> diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
> index f1d73a5c..20f13600 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/internal/pub_key.h"
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/utils.h>
> +
>   /**
>    * \file pub_key.h
>    * \brief Public key signature verification
> @@ -23,17 +28,24 @@
>   
>   namespace libcamera {
>   
> +LOG_DEFINE_CATEGORY(PubKey);
>   /**
>    * \class PubKey
>    * \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 in
> + * compile time.
>    */
>   
>   /**
>    * \brief Construct a PubKey from key data
>    * \param[in] key Key data encoded in DER format
> + *
> + * The signature algorithm is determined in the compile
> + * 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 +95,8 @@ 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. The signature algorithm is determined in compile time. RSA keys use
> + * RSA-SHA256, while ML-DSA keys use ML-DSA-65 mentioned in FIPS 204.
>    *
>    * \return True if the signature is valid, false otherwise
>    */
> @@ -94,6 +107,30 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
>   		return false;
>   
>   #if HAVE_CRYPTO
> +
> +#if WITH_FIPS
> +	/* ML-DSA */
> +	EVP_MD_CTX *ctx_dsa = EVP_MD_CTX_new();
> +	if (!ctx_dsa) {
> +		LOG(PubKey, Error) << "Initialize context for ML-DSA failed";
> +		return false;
> +	}
> +
> +	if (EVP_DigestVerifyInit(ctx_dsa, nullptr, nullptr, nullptr,
> +				 pubkey_) <= 0) {
> +		EVP_MD_CTX_free(ctx_dsa);
> +		LOG(PubKey, Error) << "Initialize ML-DSA verification failed";
> +		return false;
> +	}
> +
> +	int ret = EVP_DigestVerify(ctx_dsa, sig.data(), sig.size(),
> +				   data.data(), data.size());
> +	EVP_MD_CTX_free(ctx_dsa);
> +	LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
> +	return ret == 1;
> +#else
> +	/* RSA with SHA-256 */
> +
>   	/*
>   	 * Create and initialize a public key algorithm context for signature
>   	 * verification.
> @@ -117,7 +154,11 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
>   	int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
>   				  SHA256_DIGEST_LENGTH);
>   	EVP_PKEY_CTX_free(ctx);
> +
> +	LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
>   	return ret == 1;
> +#endif
> +
>   #elif HAVE_GNUTLS
>   	const gnutls_datum_t gnuTlsData{
>   		const_cast<unsigned char *>(data.data()),
> @@ -129,9 +170,17 @@ 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,
> +#if WITH_FIPS
> +	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_MLDSA65, 0,
>   					     &gnuTlsData, &gnuTlsSig);
> +	LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
> +	return ret >= 0;
> +#else
> +	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_RSA_SHA256,
> +					     0, &gnuTlsData, &gnuTlsSig);
> +	LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
>   	return ret >= 0;
> +#endif
>   #else
>   	return false;
>   #endif
Kate Hsuan April 15, 2026, 2:27 a.m. UTC | #2
Hi Barnabás,

On Mon, Apr 13, 2026 at 5:18 PM Barnabás Pőcze
<barnabas.pocze@ideasonboard.com> wrote:
>
> Hi
>
> 2026. 04. 08. 9:55 keltezéssel, Kate Hsuan írta:
> > As quantum computing advances, traditional signature algorithms are
> > becoming vulnerable. To ensure long-term data security, this change
> > implements ML-DSA-65, the primary Post-Quantum Cryptography (PQC)
> > standard finalized by NIST. This addition prepares for the transition
> > away from RSA, which is slated for deprecation by 2035.
>
> Is there any point in keeping the RSA path in libcamera?

Thank you for reviewing this work.

RSA is expected to be deprecated by 2035, so I think keeping both
algorithms for now is better.
Keeping RSA gives more room to the Linux distributor to prepare the
PQC migration.
After 2035, RSA can be entirely dropped :)

>
>
> Regards,
> Barnabás Pőcze
>
> >
> > 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 | 55 ++++++++++++++++++++++++++++++++++++---
> >   1 file changed, 52 insertions(+), 3 deletions(-)
> >
> > diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
> > index f1d73a5c..20f13600 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/internal/pub_key.h"
> > +#include <libcamera/base/log.h>
> > +#include <libcamera/base/utils.h>
> > +
> >   /**
> >    * \file pub_key.h
> >    * \brief Public key signature verification
> > @@ -23,17 +28,24 @@
> >
> >   namespace libcamera {
> >
> > +LOG_DEFINE_CATEGORY(PubKey);
> >   /**
> >    * \class PubKey
> >    * \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 in
> > + * compile time.
> >    */
> >
> >   /**
> >    * \brief Construct a PubKey from key data
> >    * \param[in] key Key data encoded in DER format
> > + *
> > + * The signature algorithm is determined in the compile
> > + * 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 +95,8 @@ 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. The signature algorithm is determined in compile time. RSA keys use
> > + * RSA-SHA256, while ML-DSA keys use ML-DSA-65 mentioned in FIPS 204.
> >    *
> >    * \return True if the signature is valid, false otherwise
> >    */
> > @@ -94,6 +107,30 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
> >               return false;
> >
> >   #if HAVE_CRYPTO
> > +
> > +#if WITH_FIPS
> > +     /* ML-DSA */
> > +     EVP_MD_CTX *ctx_dsa = EVP_MD_CTX_new();
> > +     if (!ctx_dsa) {
> > +             LOG(PubKey, Error) << "Initialize context for ML-DSA failed";
> > +             return false;
> > +     }
> > +
> > +     if (EVP_DigestVerifyInit(ctx_dsa, nullptr, nullptr, nullptr,
> > +                              pubkey_) <= 0) {
> > +             EVP_MD_CTX_free(ctx_dsa);
> > +             LOG(PubKey, Error) << "Initialize ML-DSA verification failed";
> > +             return false;
> > +     }
> > +
> > +     int ret = EVP_DigestVerify(ctx_dsa, sig.data(), sig.size(),
> > +                                data.data(), data.size());
> > +     EVP_MD_CTX_free(ctx_dsa);
> > +     LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
> > +     return ret == 1;
> > +#else
> > +     /* RSA with SHA-256 */
> > +
> >       /*
> >        * Create and initialize a public key algorithm context for signature
> >        * verification.
> > @@ -117,7 +154,11 @@ bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
> >       int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
> >                                 SHA256_DIGEST_LENGTH);
> >       EVP_PKEY_CTX_free(ctx);
> > +
> > +     LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
> >       return ret == 1;
> > +#endif
> > +
> >   #elif HAVE_GNUTLS
> >       const gnutls_datum_t gnuTlsData{
> >               const_cast<unsigned char *>(data.data()),
> > @@ -129,9 +170,17 @@ 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,
> > +#if WITH_FIPS
> > +     int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_MLDSA65, 0,
> >                                            &gnuTlsData, &gnuTlsSig);
> > +     LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
> > +     return ret >= 0;
> > +#else
> > +     int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_RSA_SHA256,
> > +                                          0, &gnuTlsData, &gnuTlsSig);
> > +     LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
> >       return ret >= 0;
> > +#endif
> >   #else
> >       return false;
> >   #endif
>

Patch
diff mbox series

diff --git a/src/libcamera/pub_key.cpp b/src/libcamera/pub_key.cpp
index f1d73a5c..20f13600 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/internal/pub_key.h"
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
 /**
  * \file pub_key.h
  * \brief Public key signature verification
@@ -23,17 +28,24 @@ 
 
 namespace libcamera {
 
+LOG_DEFINE_CATEGORY(PubKey);
 /**
  * \class PubKey
  * \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 in
+ * compile time.
  */
 
 /**
  * \brief Construct a PubKey from key data
  * \param[in] key Key data encoded in DER format
+ *
+ * The signature algorithm is determined in the compile
+ * 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 +95,8 @@  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. The signature algorithm is determined in compile time. RSA keys use
+ * RSA-SHA256, while ML-DSA keys use ML-DSA-65 mentioned in FIPS 204.
  *
  * \return True if the signature is valid, false otherwise
  */
@@ -94,6 +107,30 @@  bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
 		return false;
 
 #if HAVE_CRYPTO
+
+#if WITH_FIPS
+	/* ML-DSA */
+	EVP_MD_CTX *ctx_dsa = EVP_MD_CTX_new();
+	if (!ctx_dsa) {
+		LOG(PubKey, Error) << "Initialize context for ML-DSA failed";
+		return false;
+	}
+
+	if (EVP_DigestVerifyInit(ctx_dsa, nullptr, nullptr, nullptr,
+				 pubkey_) <= 0) {
+		EVP_MD_CTX_free(ctx_dsa);
+		LOG(PubKey, Error) << "Initialize ML-DSA verification failed";
+		return false;
+	}
+
+	int ret = EVP_DigestVerify(ctx_dsa, sig.data(), sig.size(),
+				   data.data(), data.size());
+	EVP_MD_CTX_free(ctx_dsa);
+	LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
+	return ret == 1;
+#else
+	/* RSA with SHA-256 */
+
 	/*
 	 * Create and initialize a public key algorithm context for signature
 	 * verification.
@@ -117,7 +154,11 @@  bool PubKey::verify([[maybe_unused]] Span<const uint8_t> data,
 	int ret = EVP_PKEY_verify(ctx, sig.data(), sig.size(), digest,
 				  SHA256_DIGEST_LENGTH);
 	EVP_PKEY_CTX_free(ctx);
+
+	LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
 	return ret == 1;
+#endif
+
 #elif HAVE_GNUTLS
 	const gnutls_datum_t gnuTlsData{
 		const_cast<unsigned char *>(data.data()),
@@ -129,9 +170,17 @@  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,
+#if WITH_FIPS
+	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_MLDSA65, 0,
 					     &gnuTlsData, &gnuTlsSig);
+	LOG(PubKey, Error) << "Verify with ML-DSA-65: " << ret;
+	return ret >= 0;
+#else
+	int ret = gnutls_pubkey_verify_data2(pubkey_, GNUTLS_SIGN_RSA_SHA256,
+					     0, &gnuTlsData, &gnuTlsSig);
+	LOG(PubKey, Error) << "Verify with RSA-SHA256: " << ret;
 	return ret >= 0;
+#endif
 #else
 	return false;
 #endif