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
