[PATCH] crypto: CMS: add new Kconfig CONFIG_CMS_REQUIRE_HYBRID_PQC to require classic + PQC signatures

21 views
Skip to first unread message

Ayoub Zaki

unread,
Jul 29, 2026, 9:38:47 AM (7 days ago) Jul 29
to swup...@googlegroups.com, Ayoub Zaki
When a CMS update is signed with both a classic (RSA, ECDSA) and a
post-quantum (ML-DSA, SLH-DSA) signature the signers sit in an
unauthenticated set of SignerInfo. An attacker can strip one signer and
the rest still verify by downgrading the update to a single algorithm.

Add new Kconfig CONFIG_CMS_REQUIRE_HYBRID_PQC to rejects an update unless the
verified signers include at least one classic and one post-quantum
signature.

It only affects hybrid signing and is off by default. It needs every
signer to be trusted.

Signed-off-by: Ayoub Zaki <ayoub...@embetrix.com>
---
crypto/Kconfig | 13 ++++++
crypto/swupdate_cms_verify_openssl.c | 60 ++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)

diff --git a/crypto/Kconfig b/crypto/Kconfig
index c5bfc320..cb2e548a 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -86,6 +86,19 @@ config CMS_SKIP_UNKNOWN_SIGNERS
config CMS_IGNORE_ADDITIONAL_CERTS
bool "Use only direct signer certificates from CMS signature. This is implied for digest providers other than OpenSSL."
depends on SIGALG_CMS && SSL_IMPL_OPENSSL
+
+config CMS_REQUIRE_HYBRID_PQC
+ bool "Require a hybrid (classic + post-quantum) signature"
+ depends on SIGALG_CMS && SSL_IMPL_OPENSSL && !CMS_SKIP_UNKNOWN_SIGNERS
+ help
+ Reject a verified CMS update unless its signers include at least one
+ classic (RSA, ECDSA) and at least one post-quantum (ML-DSA, SLH-DSA)
+ signature. Without this, an attacker can strip one signer from the
+ unauthenticated SignerInfo set and downgrade a dual-signed update to a
+ single algorithm.
+
+ Only holds when every signer is trusted, so it is not compatible with
+ CMS_SKIP_UNKNOWN_SIGNERS. Requires OpenSSL >= 3.5.
endmenu

menu "Encryption"
diff --git a/crypto/swupdate_cms_verify_openssl.c b/crypto/swupdate_cms_verify_openssl.c
index e202817e..3d1ca718 100644
--- a/crypto/swupdate_cms_verify_openssl.c
+++ b/crypto/swupdate_cms_verify_openssl.c
@@ -455,6 +455,59 @@ dgst_init_error:
return ret;
}

+#if defined(CONFIG_CMS_REQUIRE_HYBRID_PQC)
+#if OPENSSL_VERSION_NUMBER < 0x30500000L
+#error "CONFIG_CMS_REQUIRE_HYBRID_PQC requires OpenSSL >= 3.5 (ML-DSA/SLH-DSA support)"
+#endif
+static bool is_pqc_signature_key(EVP_PKEY *pkey)
+{
+ const char *tn = pkey ? EVP_PKEY_get0_type_name(pkey) : NULL;
+
+ return tn && (strncmp(tn, "ML-DSA", 6) == 0 ||
+ strncmp(tn, "SLH-DSA", 7) == 0);
+}
+
+/*
+ * The signers of a CMS SignedData sit in an unauthenticated SET OF SignerInfo,
+ * so an attacker can strip one and the rest still verify. Require at least one
+ * classic and one post-quantum signer so a dual-signed update cannot be
+ * downgraded to a single algorithm.
+ *
+ * This assumes every signer is trusted, i.e. CMS_verify() runs without
+ * CMS_NO_SIGNER_CERT_VERIFY (CONFIG_CMS_SKIP_UNKNOWN_SIGNERS unset).
+ */
+static int check_hybrid_signers(CMS_ContentInfo *cms)
+{
+ STACK_OF(CMS_SignerInfo) *sinfos = CMS_get0_SignerInfos(cms);
+ int i, classic = 0, pqc = 0;
+
+ for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); ++i) {
+ CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sinfos, i);
+ X509 *signer = NULL;
+
+ /* the signer cert has been resolved by the preceding CMS_verify() */
+ CMS_SignerInfo_get0_algs(si, NULL, &signer, NULL, NULL);
+ if (!signer)
+ continue;
+
+ if (is_pqc_signature_key(X509_get0_pubkey(signer)))
+ pqc++;
+ else
+ classic++;
+ }
+
+ if (classic < 1 || pqc < 1) {
+ ERROR("hybrid policy not satisfied: %d classic + %d post-quantum "
+ "signer(s), require at least one of each", classic, pqc);
+ return -EBADMSG;
+ }
+
+ TRACE("hybrid policy OK: %d classic + %d post-quantum signer(s)",
+ classic, pqc);
+ return 0;
+}
+#endif
+
static int openssl_cms_verify_file(void *ctx, const char *sigfile,
const char *file, const char *signer_name)
{
@@ -511,6 +564,13 @@ static int openssl_cms_verify_file(void *ctx, const char *sigfile,
}
#endif

+#if defined(CONFIG_CMS_REQUIRE_HYBRID_PQC)
+ if (check_hybrid_signers(cms)) {
+ status = -EBADMSG;
+ goto out;
+ }
+#endif
+
TRACE("Verified OK");

/* Signature is valid */
--
2.43.0

Reply all
Reply to author
Forward
0 new messages