botan: patch CVE-2026-32884

Details: https://nvd.nist.gov/vuln/detail/CVE-2026-32884

The backported patch was selected based on the security.rst[1]
file of the project, that mentions the date of the fix. When
looked through the commits from that date, picked the one that's
description matches the CVE description.

The included test passed successfully (along with the other tests).

[1]: https://github.com/randombit/botan/blob/master/doc/security.rst

Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Gyorgy Sarvari
2026-04-06 20:32:55 +02:00
committed by Anuj Mittal
parent 70a903c888
commit b35ad41144
2 changed files with 172 additions and 0 deletions
@@ -0,0 +1,171 @@
From cbc8b341ea4b38c388e89682b86da107a9fdc38c Mon Sep 17 00:00:00 2001
From: Jack Lloyd <jack@randombit.net>
Date: Sun, 15 Mar 2026 11:17:22 -0400
Subject: [PATCH] Fix name constraint DNS check when falling back to CN
When verifying name constraints which restrict the set of allowed DNS names, the
logic would first check the Subject Alternative Name if available, but if the
SAN is absent then it would check the CN field of the issued name since that is
a common fallback for DNS names when the SAN is missing.
However this check failed to properly canonicalize the potential DNS name in the
CN, allowing a mis-issued cert with a mixed-case CN field and absent SAN to
bypass any imposed excludedSubtrees rules.
CVE: CVE-2026-32884
Upstream-Status: Backport [https://github.com/randombit/botan/commit/db8baebd92bded036da6faf8bf3c324b67de9cf4]
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
src/lib/x509/name_constraint.cpp | 16 +++++++---
.../Invalid_DNS_Excluded_Mixed_Case_CN.crt | 19 ++++++++++++
.../Root_DNS_Excluded_Mixed_Case_CN.crt | 19 ++++++++++++
src/tests/test_name_constraint.cpp | 29 +++++++++++++++++++
4 files changed, 79 insertions(+), 4 deletions(-)
create mode 100644 src/tests/data/x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt
create mode 100644 src/tests/data/x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt
diff --git a/src/lib/x509/name_constraint.cpp b/src/lib/x509/name_constraint.cpp
index e767624..8708b95 100644
--- a/src/lib/x509/name_constraint.cpp
+++ b/src/lib/x509/name_constraint.cpp
@@ -19,6 +19,14 @@ namespace Botan {
class DER_Encoder;
+namespace {
+
+std::string canonicalize_dns_name(std::string_view name) {
+ return tolower_string(name);
+}
+
+} // namespace
+
std::string GeneralName::type() const {
switch(m_type) {
case NameType::Unknown:
@@ -75,7 +83,7 @@ void GeneralName::decode_from(BER_Decoder& ber) {
m_type = NameType::DNS;
// Store it in case insensitive form so we don't have to do it
// again while matching
- m_name.emplace<DNS_IDX>(tolower_string(ASN1::to_string(obj)));
+ m_name.emplace<DNS_IDX>(canonicalize_dns_name(ASN1::to_string(obj)));
} else if(obj.is_a(6, ASN1_Class::ContextSpecific)) {
m_type = NameType::URI;
m_name.emplace<URI_IDX>(ASN1::to_string(obj));
@@ -174,7 +182,7 @@ GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) cons
// Check CN instead...
for(const std::string& cn : dn.get_attribute("CN")) {
if(!string_to_ipv4(cn).has_value()) {
- score.add(matches_dns(cn, constraint));
+ score.add(matches_dns(canonicalize_dns_name(cn), constraint));
}
}
}
@@ -421,7 +429,7 @@ bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unk
return false;
}
} else {
- if(!is_permitted_dns_name(cn)) {
+ if(!is_permitted_dns_name(canonicalize_dns_name(cn))) {
return false;
}
}
@@ -540,7 +548,7 @@ bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unkn
return true;
}
} else {
- if(is_excluded_dns_name(cn)) {
+ if(is_excluded_dns_name(canonicalize_dns_name(cn))) {
return true;
}
}
diff --git a/src/tests/data/x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt b/src/tests/data/x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt
new file mode 100644
index 0000000..fe2c773
--- /dev/null
+++ b/src/tests/data/x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDCTCCAfGgAwIBAgIUAbjRT6B+WHVf1Wo9JdFHlHegF1cwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDMxNTE0NTM1MloXDTI3
+MDMxNTE0NTM1MlowFzEVMBMGA1UEAwwMU3ViLkVWSUwuQ09NMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsn1rve+kuZJd2udT4QEnjdws+YRT+lJI+rG/
+4OpljAcxQywCc6oHDY4ouaqOKdhT9+luvqRVSEnH3JfczT/D+wZD+gBOlgUqsmXp
+QasD8HSyHWdjyaLrkZuBGICoUZTh9a7ZOPuIQ2sblxOvBm3klndpSpXJC6sJsWAk
+tyU+5WdNz+ncTC82sFSY0YQLC2QvxckEP6Jxi4I+h9vANtWXCk6CRz/kT4dY3cD4
+VBFtAy+vUVYUZnLi48fEYlMt4rP64OerY7hjw8gE+66rmUQPml4+DAek+xJtL4Sl
+Q0SGytWE9tJb1zzICv4TNvj/+IKmbK4BCu7fVjOd2C64zNFTywIDAQABo00wSzAJ
+BgNVHRMEAjAAMB0GA1UdDgQWBBS6hC1DrVlXAq/GWDYl/UQbP4TmLTAfBgNVHSME
+GDAWgBQPZ2Gp3izLTRAUzQKCKRLbM5OA2DANBgkqhkiG9w0BAQsFAAOCAQEAcWBy
+f9+tJDDj7T7z4QziZnxIfKP7x7uP9wAlYGRpKcbbW6SHAg6DA1V5GBWHendkg1hb
+Nywy7pUFgJ0xlJ7KYr76Ylfa1BTUPT+gExJWvSmsg8WSQ4q3bOMVB1qRxWDv/8KY
+ZOT03KEzLyGL6ia9r/UFw1jibxS26Ff6qCE9EhDLA/4z0D/+9QzdLATuzSn/SLSR
+wtarLaWhCfOSpfRrekYhaSndG45BCKpUd99iWt1HA4LyjZe8/8cxsRkD8klaCalG
+8pRp+PolijzmYsrEXuG22Bq2idgxHTRvw8l4rBQrSdMWuWqqTdIpFIUrXNft0a5f
+d8RmhI6PZlPL43OCxw==
+-----END CERTIFICATE-----
diff --git a/src/tests/data/x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt b/src/tests/data/x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt
new file mode 100644
index 0000000..503206f
--- /dev/null
+++ b/src/tests/data/x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGjCCAgKgAwIBAgIUJj5ZhECZYOzt5qWnoN8DMcZzlzgwDQYJKoZIhvcNAQEL
+BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDMxNTE0NTM1MloXDTM2
+MDMxMjE0NTM1MlowFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtEqBgRHixpRodBHeqXrcttdKD3tpGG+S67tM
+WT1h+572k6EFIZ9RVfYokH32sNFNz8LNI5FwKKFTf/WaVhMdsqxaMrf/+06UzKdw
+7dKz+Q5uLGrygv6opSVqhojkjLZH78zmEER0Gba4Ac4FGq5pjafA2jtoIMDyQ2SV
+IpOy932WCajQL6IM20yHPQAsr0c0hoFP4RdbJPuiEVPfZv95VjEwwV0zMBFmiICk
+gItEDfiexBHEIvXAGNBModMHaBhUDzHZp0X7gNW/MD2ieiQGoLTyG/t7sKv0J3zV
+LnUpXVIrmHSIkGVfuc5EFOlq6OXZ/J29VjPEnVVeZARDhgyGRQIDAQABo14wXDAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQPZ2Gp3izLTRAUzQKCKRLbM5OA2DAO
+BgNVHQ8BAf8EBAMCAQYwGgYDVR0eAQH/BBAwDqEMMAqCCGV2aWwuY29tMA0GCSqG
+SIb3DQEBCwUAA4IBAQA5RLYaZWVxFIKpZRM5ZBk4fDDlAvRl7NVhMeQO3DMyyIzw
+Bp7IIEgH0I6PlL4/02SzSaqUuVVXYyO1LhbgBlFnE1h818zz+jN06i0lUemuXGAo
+wnHBwLW+V/r0JBm4BbnbneCYHUFS07sfR9IjQqV9Futp2WILwieZ7Ib96xGuX96L
+f6pxYEd82rYSXa/K14HvMKXkzC3qe72V+/E1GOPZqzlbZFriYfRUHUOY6P7LjcWl
+3Z+aUQcG8vEHbJjTd+ZZzP2PICjLKgaX1acpBOUR6pelHgcUmuR3z86V/DD8TWsH
+BHDLLBxTje/8fL0PR4qo4r4F+2Cj+hXnY/TFVQ+W
+-----END CERTIFICATE-----
diff --git a/src/tests/test_name_constraint.cpp b/src/tests/test_name_constraint.cpp
index 61aff53..5daa343 100644
--- a/src/tests/test_name_constraint.cpp
+++ b/src/tests/test_name_constraint.cpp
@@ -69,6 +69,35 @@ class Name_Constraint_Tests final : public Test {
BOTAN_REGISTER_TEST("x509", "x509_path_name_constraint", Name_Constraint_Tests);
+// Verify that DNS constraints are case-insensitive also when falling back to the CN
+class Name_Constraint_Excluded_CN_Case_Test final : public Test {
+ public:
+ std::vector<Test::Result> run() override {
+ Test::Result result("X509v3 Name Constraints: excluded DNS with mixed-case CN and no SAN");
+
+ const Botan::X509_Certificate root(
+ Test::data_file("x509/name_constraint/Root_DNS_Excluded_Mixed_Case_CN.crt"));
+ const Botan::X509_Certificate leaf(
+ Test::data_file("x509/name_constraint/Invalid_DNS_Excluded_Mixed_Case_CN.crt"));
+
+ Botan::Certificate_Store_In_Memory trusted;
+ trusted.add_certificate(root);
+
+ const Botan::Path_Validation_Restrictions restrictions(false, 80);
+ const auto validation_time = Botan::calendar_point(2026, 6, 1, 0, 0, 0).to_std_timepoint();
+
+ const auto path_result = Botan::x509_path_validate(
+ leaf, restrictions, trusted, "" /* hostname */, Botan::Usage_Type::UNSPECIFIED, validation_time);
+
+ result.test_eq(
+ "validation result", path_result.result_string(), "Certificate does not pass name constraint");
+
+ return {result};
+ }
+};
+
+BOTAN_REGISTER_TEST("x509", "x509_name_constraint_excluded_cn_case", Name_Constraint_Excluded_CN_Case_Test);
+
#endif
} // namespace
@@ -7,6 +7,7 @@ SECTION = "libs"
SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz \
file://CVE-2026-32877.patch \
file://CVE-2026-32883.patch \
file://CVE-2026-32884.patch \
"
SRC_URI[sha256sum] = "fde194236f6d5434f136ea0a0627f6cc9d26af8b96e9f1e1c7d8c82cd90f4f24"