diff options
| author | Gyorgy Sarvari <skandigraun@gmail.com> | 2025-10-05 15:06:10 +0200 |
|---|---|---|
| committer | Gyorgy Sarvari <skandigraun@gmail.com> | 2025-10-12 13:08:46 +0200 |
| commit | a742bea9924133f7484a659afc0ba0ac391a67b8 (patch) | |
| tree | dca5c2aedf173c5591bc98da0b66578f74022701 | |
| parent | 6c5e7ee581567e6dc0abf0aab6f191a007bf10ef (diff) | |
| download | meta-openembedded-a742bea9924133f7484a659afc0ba0ac391a67b8.tar.gz | |
botan: patch CVE-2024-39312
Details: https://nvd.nist.gov/vuln/detail/CVE-2024-39312
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
| -rw-r--r-- | meta-oe/recipes-crypto/botan/botan/0001-Address-various-name-constraint-bugs.patch | 749 | ||||
| -rw-r--r-- | meta-oe/recipes-crypto/botan/botan_2.19.1.bb | 1 |
2 files changed, 750 insertions, 0 deletions
diff --git a/meta-oe/recipes-crypto/botan/botan/0001-Address-various-name-constraint-bugs.patch b/meta-oe/recipes-crypto/botan/botan/0001-Address-various-name-constraint-bugs.patch new file mode 100644 index 0000000000..e9abdaedb3 --- /dev/null +++ b/meta-oe/recipes-crypto/botan/botan/0001-Address-various-name-constraint-bugs.patch | |||
| @@ -0,0 +1,749 @@ | |||
| 1 | From 6c39cdf1aa7efccb5dbeaf859fddf06500b78aa8 Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Jack Lloyd <jack@randombit.net> | ||
| 3 | Date: Sun, 7 Jul 2024 05:02:48 -0400 | ||
| 4 | Subject: [PATCH] Address various name constraint bugs | ||
| 5 | |||
| 6 | CVE: CVE-2024-39312 | ||
| 7 | Upstream-Status: Backport [https://github.com/randombit/botan/commit/68338f5912534c74469f7f4e6e22b37aa5159952] | ||
| 8 | |||
| 9 | Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> | ||
| 10 | --- | ||
| 11 | src/lib/x509/asn1_alt_name.cpp | 4 + | ||
| 12 | src/lib/x509/name_constraint.cpp | 313 ++++++++++++++++++++++++++++- | ||
| 13 | src/lib/x509/pkix_types.h | 37 +++- | ||
| 14 | src/lib/x509/x509_ext.cpp | 80 +++----- | ||
| 15 | src/lib/x509/x509cert.cpp | 30 ++- | ||
| 16 | src/python/botan2.py | 2 + | ||
| 17 | src/scripts/test_python.py | 3 - | ||
| 18 | src/tests/test_name_constraint.cpp | 10 +- | ||
| 19 | 8 files changed, 401 insertions(+), 78 deletions(-) | ||
| 20 | |||
| 21 | diff --git a/src/lib/x509/asn1_alt_name.cpp b/src/lib/x509/asn1_alt_name.cpp | ||
| 22 | index a347a31..574b9fb 100644 | ||
| 23 | --- a/src/lib/x509/asn1_alt_name.cpp | ||
| 24 | +++ b/src/lib/x509/asn1_alt_name.cpp | ||
| 25 | @@ -257,6 +257,10 @@ void AlternativeName::decode_from(BER_Decoder& source) | ||
| 26 | const uint32_t ip = load_be<uint32_t>(obj.bits(), 0); | ||
| 27 | add_attribute("IP", ipv4_to_string(ip)); | ||
| 28 | } | ||
| 29 | + else if(obj.length() != 16) | ||
| 30 | + { | ||
| 31 | + throw Decoding_Error("Invalid length for IP address SAN"); | ||
| 32 | + } | ||
| 33 | } | ||
| 34 | |||
| 35 | } | ||
| 36 | diff --git a/src/lib/x509/name_constraint.cpp b/src/lib/x509/name_constraint.cpp | ||
| 37 | index c904572..632bc8c 100644 | ||
| 38 | --- a/src/lib/x509/name_constraint.cpp | ||
| 39 | +++ b/src/lib/x509/name_constraint.cpp | ||
| 40 | @@ -163,31 +163,48 @@ GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) cons | ||
| 41 | |||
| 42 | bool GeneralName::matches_dns(const std::string& nam) const | ||
| 43 | { | ||
| 44 | - if(nam.size() == name().size()) | ||
| 45 | + const std::string constraint = tolower_string(name()); | ||
| 46 | + const std::string issued = tolower_string(nam); | ||
| 47 | + | ||
| 48 | + if(nam.size() == constraint.size()) | ||
| 49 | { | ||
| 50 | - return tolower_string(nam) == tolower_string(name()); | ||
| 51 | + return issued == constraint; | ||
| 52 | } | ||
| 53 | - else if(name().size() > nam.size()) | ||
| 54 | + else if(constraint.size() > nam.size()) | ||
| 55 | { | ||
| 56 | // The constraint is longer than the issued name: not possibly a match | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | - else // name.size() < nam.size() | ||
| 60 | + else | ||
| 61 | { | ||
| 62 | - // constr is suffix of nam | ||
| 63 | - const std::string constr = name().front() == '.' ? name() : "." + name(); | ||
| 64 | - const std::string substr = nam.substr(nam.size() - constr.size(), constr.size()); | ||
| 65 | - return tolower_string(constr) == tolower_string(substr); | ||
| 66 | + if(constraint.empty()) { | ||
| 67 | + return true; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + std::string substr = issued.substr(nam.size() - constraint.size(), constraint.size()); | ||
| 71 | + | ||
| 72 | + if(constraint.front() == '.') { | ||
| 73 | + return substr == constraint; | ||
| 74 | + } else if(substr[0] == '.') { | ||
| 75 | + return substr.substr(1) == constraint; | ||
| 76 | + } else { | ||
| 77 | + return substr == constraint && issued[issued.size() - constraint.size() - 1] == '.'; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | +} | ||
| 81 | |||
| 82 | bool GeneralName::matches_dn(const std::string& nam) const | ||
| 83 | { | ||
| 84 | std::stringstream ss(nam); | ||
| 85 | - std::stringstream tt(name()); | ||
| 86 | - X509_DN nam_dn, my_dn; | ||
| 87 | - | ||
| 88 | + X509_DN nam_dn; | ||
| 89 | ss >> nam_dn; | ||
| 90 | + return matches_dn_obj(nam_dn); | ||
| 91 | + } | ||
| 92 | + | ||
| 93 | +bool GeneralName::matches_dn_obj(const X509_DN& nam_dn) const | ||
| 94 | + { | ||
| 95 | + std::stringstream tt(name()); | ||
| 96 | + X509_DN my_dn; | ||
| 97 | tt >> my_dn; | ||
| 98 | |||
| 99 | auto attr = nam_dn.get_attributes(); | ||
| 100 | @@ -270,4 +287,278 @@ std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) | ||
| 101 | os << gs.minimum() << "," << gs.maximum() << "," << gs.base(); | ||
| 102 | return os; | ||
| 103 | } | ||
| 104 | + | ||
| 105 | +NameConstraints::NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees, | ||
| 106 | + std::vector<GeneralSubtree>&& excluded_subtrees) : | ||
| 107 | + m_permitted_subtrees(permitted_subtrees), m_excluded_subtrees(excluded_subtrees) | ||
| 108 | + { | ||
| 109 | + for(const auto& c : m_permitted_subtrees) | ||
| 110 | + { | ||
| 111 | + m_permitted_name_types.insert(c.base().type()); | ||
| 112 | + } | ||
| 113 | + for(const auto& c : m_excluded_subtrees) | ||
| 114 | + { | ||
| 115 | + m_excluded_name_types.insert(c.base().type()); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | +namespace { | ||
| 120 | + | ||
| 121 | +bool looks_like_ipv4(const std::string& s) | ||
| 122 | + { | ||
| 123 | + try | ||
| 124 | + { | ||
| 125 | + // ignores return value | ||
| 126 | + string_to_ipv4(s); | ||
| 127 | + return true; | ||
| 128 | + } | ||
| 129 | + catch(...) | ||
| 130 | + { | ||
| 131 | + return false; | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +bool NameConstraints::is_permitted(const X509_Certificate& cert, bool reject_unknown) const { | ||
| 138 | + if(permitted().empty()) { | ||
| 139 | + return true; | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + const auto& alt_name = cert.subject_alt_name(); | ||
| 143 | + | ||
| 144 | + if(reject_unknown) { | ||
| 145 | + if(m_permitted_name_types.find("URI") != m_permitted_name_types.end() && !alt_name.get_attribute("URI").empty()) { | ||
| 146 | + return false; | ||
| 147 | + } | ||
| 148 | + if(m_permitted_name_types.find("RFC822") != m_permitted_name_types.end() && !alt_name.get_attribute("RFC822").empty()) { | ||
| 149 | + return false; | ||
| 150 | + } | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + auto is_permitted_dn = [&](const X509_DN& dn) { | ||
| 154 | + // If no restrictions, then immediate accept | ||
| 155 | + if(m_permitted_name_types.find("DN") == m_permitted_name_types.end()) { | ||
| 156 | + return true; | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + if(dn.empty()) { | ||
| 160 | + return true; | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + for(const auto& c : m_permitted_subtrees) { | ||
| 164 | + if(c.base().type() == "DN" && c.base().matches_dn_obj(dn)) { | ||
| 165 | + return true; | ||
| 166 | + } | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + // There is at least one permitted name and we didn't match | ||
| 170 | + return false; | ||
| 171 | + }; | ||
| 172 | + | ||
| 173 | + auto is_permitted_dns_name = [&](const std::string& name) { | ||
| 174 | + if(name.empty() || name[0] == '.') { | ||
| 175 | + return false; | ||
| 176 | + } | ||
| 177 | + | ||
| 178 | + // If no restrictions, then immediate accept | ||
| 179 | + if(m_permitted_name_types.find("DNS") == m_permitted_name_types.end()) { | ||
| 180 | + return true; | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + for(const auto& c : m_permitted_subtrees) { | ||
| 184 | + if(c.base().type() == "DNS" && c.base().matches_dns(name)) { | ||
| 185 | + return true; | ||
| 186 | + } | ||
| 187 | + } | ||
| 188 | + | ||
| 189 | + // There is at least one permitted name and we didn't match | ||
| 190 | + return false; | ||
| 191 | + }; | ||
| 192 | + | ||
| 193 | + auto is_permitted_ipv4 = [&](const std::string& ipv4) { | ||
| 194 | + // If no restrictions, then immediate accept | ||
| 195 | + if(m_permitted_name_types.find("IP") == m_permitted_name_types.end()) { | ||
| 196 | + return true; | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + for(const auto& c : m_permitted_subtrees) { | ||
| 200 | + if(c.base().type() == "IP" && c.base().matches_ip(ipv4)) { | ||
| 201 | + return true; | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + // There is at least one permitted name and we didn't match | ||
| 206 | + return false; | ||
| 207 | + }; | ||
| 208 | + | ||
| 209 | + if(!is_permitted_dn(cert.subject_dn())) { | ||
| 210 | + return false; | ||
| 211 | + } | ||
| 212 | + | ||
| 213 | + if(!is_permitted_dn(alt_name.dn())) | ||
| 214 | + { | ||
| 215 | + return false; | ||
| 216 | + } | ||
| 217 | + | ||
| 218 | + for(const auto& alt_dns : alt_name.get_attribute("DNS")) { | ||
| 219 | + if(!is_permitted_dns_name(alt_dns)) { | ||
| 220 | + return false; | ||
| 221 | + } | ||
| 222 | + } | ||
| 223 | + | ||
| 224 | + for(const auto& alt_ipv4 : alt_name.get_attribute("IP")) { | ||
| 225 | + if(!is_permitted_ipv4(alt_ipv4)) { | ||
| 226 | + return false; | ||
| 227 | + } | ||
| 228 | + } | ||
| 229 | + | ||
| 230 | + if(!alt_name.has_items()) | ||
| 231 | + { | ||
| 232 | + for(const auto& cn : cert.subject_info("Name")) | ||
| 233 | + { | ||
| 234 | + if(cn.find(".") != std::string::npos) | ||
| 235 | + { | ||
| 236 | + if(looks_like_ipv4(cn)) | ||
| 237 | + { | ||
| 238 | + if(!is_permitted_ipv4(cn)) | ||
| 239 | + { | ||
| 240 | + return false; | ||
| 241 | + } | ||
| 242 | + } | ||
| 243 | + else | ||
| 244 | + { | ||
| 245 | + if(!is_permitted_dns_name(cn)) | ||
| 246 | + { | ||
| 247 | + return false; | ||
| 248 | + } | ||
| 249 | + } | ||
| 250 | + } | ||
| 251 | + } | ||
| 252 | + } | ||
| 253 | + | ||
| 254 | + // We didn't encounter a name that doesn't have a matching constraint | ||
| 255 | + return true; | ||
| 256 | } | ||
| 257 | + | ||
| 258 | +bool NameConstraints::is_excluded(const X509_Certificate& cert, bool reject_unknown) const { | ||
| 259 | + if(excluded().empty()) { | ||
| 260 | + return false; | ||
| 261 | + } | ||
| 262 | + | ||
| 263 | + const auto& alt_name = cert.subject_alt_name(); | ||
| 264 | + | ||
| 265 | + if(reject_unknown) { | ||
| 266 | + if(m_excluded_name_types.find("URI") != m_excluded_name_types.end() && !alt_name.get_attribute("URI").empty()) { | ||
| 267 | + return false; | ||
| 268 | + } | ||
| 269 | + if(m_excluded_name_types.find("RFC822") != m_excluded_name_types.end() && !alt_name.get_attribute("RFC822").empty()) { | ||
| 270 | + return false; | ||
| 271 | + } | ||
| 272 | + } | ||
| 273 | + | ||
| 274 | + auto is_excluded_dn = [&](const X509_DN& dn) { | ||
| 275 | + // If no restrictions, then immediate accept | ||
| 276 | + if(m_excluded_name_types.find("DN") == m_excluded_name_types.end()) { | ||
| 277 | + return false; | ||
| 278 | + } | ||
| 279 | + | ||
| 280 | + if(dn.empty()) { | ||
| 281 | + return false; | ||
| 282 | + } | ||
| 283 | + | ||
| 284 | + for(const auto& c : m_excluded_subtrees) { | ||
| 285 | + if(c.base().type() == "DN" && c.base().matches_dn_obj(dn)) { | ||
| 286 | + return true; | ||
| 287 | + } | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + // There is at least one excluded name and we didn't match | ||
| 291 | + return false; | ||
| 292 | + }; | ||
| 293 | + | ||
| 294 | + auto is_excluded_dns_name = [&](const std::string& name) { | ||
| 295 | + if(name.empty() || name[0] == '.') { | ||
| 296 | + return true; | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + // If no restrictions, then immediate accept | ||
| 300 | + if(m_excluded_name_types.find("DNS") == m_excluded_name_types.end()) { | ||
| 301 | + return false; | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + for(const auto& c : m_excluded_subtrees) { | ||
| 305 | + if(c.base().type() == "DNS" && c.base().matches_dns(name)) { | ||
| 306 | + return true; | ||
| 307 | + } | ||
| 308 | + } | ||
| 309 | + | ||
| 310 | + // There is at least one excluded name and we didn't match | ||
| 311 | + return false; | ||
| 312 | + }; | ||
| 313 | + | ||
| 314 | + auto is_excluded_ipv4 = [&](const std::string& ipv4) { | ||
| 315 | + // If no restrictions, then immediate accept | ||
| 316 | + if(m_excluded_name_types.find("IP") == m_excluded_name_types.end()) { | ||
| 317 | + return false; | ||
| 318 | + } | ||
| 319 | + | ||
| 320 | + for(const auto& c : m_excluded_subtrees) { | ||
| 321 | + if(c.base().type() == "IP" && c.base().matches_ip(ipv4)) { | ||
| 322 | + return true; | ||
| 323 | + } | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + // There is at least one excluded name and we didn't match | ||
| 327 | + return false; | ||
| 328 | + }; | ||
| 329 | + | ||
| 330 | + if(is_excluded_dn(cert.subject_dn())) { | ||
| 331 | + return true; | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + if(is_excluded_dn(alt_name.dn())) { | ||
| 335 | + return true; | ||
| 336 | + } | ||
| 337 | + | ||
| 338 | + for(const auto& alt_dns : alt_name.get_attribute("DNS")) { | ||
| 339 | + if(is_excluded_dns_name(alt_dns)) { | ||
| 340 | + return true; | ||
| 341 | + } | ||
| 342 | + } | ||
| 343 | + | ||
| 344 | + for(const auto& alt_ipv4 : alt_name.get_attribute("IP")) { | ||
| 345 | + if(is_excluded_ipv4(alt_ipv4)) { | ||
| 346 | + return true; | ||
| 347 | + } | ||
| 348 | + } | ||
| 349 | + | ||
| 350 | + if(!alt_name.has_items()) | ||
| 351 | + { | ||
| 352 | + for(const auto& cn : cert.subject_info("Name")) | ||
| 353 | + { | ||
| 354 | + if(cn.find(".") != std::string::npos) | ||
| 355 | + { | ||
| 356 | + if(looks_like_ipv4(cn)) | ||
| 357 | + { | ||
| 358 | + if(is_excluded_ipv4(cn)) | ||
| 359 | + { | ||
| 360 | + return true; | ||
| 361 | + } | ||
| 362 | + } | ||
| 363 | + else | ||
| 364 | + { | ||
| 365 | + if(is_excluded_dns_name(cn)) | ||
| 366 | + { | ||
| 367 | + return true; | ||
| 368 | + } | ||
| 369 | + } | ||
| 370 | + } | ||
| 371 | + } | ||
| 372 | + } | ||
| 373 | + | ||
| 374 | + // We didn't encounter a name that matched any prohibited name | ||
| 375 | + return false; | ||
| 376 | +} | ||
| 377 | + | ||
| 378 | +} // namespace Botan | ||
| 379 | diff --git a/src/lib/x509/pkix_types.h b/src/lib/x509/pkix_types.h | ||
| 380 | index 9831219..0b85887 100644 | ||
| 381 | --- a/src/lib/x509/pkix_types.h | ||
| 382 | +++ b/src/lib/x509/pkix_types.h | ||
| 383 | @@ -191,6 +191,9 @@ class BOTAN_PUBLIC_API(2,0) Attribute final : public ASN1_Object | ||
| 384 | * Handles parsing GeneralName types in their BER and canonical string | ||
| 385 | * encoding. Allows matching GeneralNames against each other using | ||
| 386 | * the rules laid out in the RFC 5280, sec. 4.2.1.10 (Name Contraints). | ||
| 387 | +* | ||
| 388 | +* This entire class is deprecated and will be removed in a future | ||
| 389 | +* major release | ||
| 390 | */ | ||
| 391 | class BOTAN_PUBLIC_API(2,0) GeneralName final : public ASN1_Object | ||
| 392 | { | ||
| 393 | @@ -213,6 +216,7 @@ class BOTAN_PUBLIC_API(2,0) GeneralName final : public ASN1_Object | ||
| 394 | * Creates a new GeneralName for its string format. | ||
| 395 | * @param str type and name, colon-separated, e.g., "DNS:google.com" | ||
| 396 | */ | ||
| 397 | + BOTAN_DEPRECATED("Deprecated no replacement") | ||
| 398 | GeneralName(const std::string& str); | ||
| 399 | |||
| 400 | void encode_into(DER_Encoder&) const override; | ||
| 401 | @@ -234,15 +238,17 @@ class BOTAN_PUBLIC_API(2,0) GeneralName final : public ASN1_Object | ||
| 402 | * @param cert certificate to be matched | ||
| 403 | * @return the match result | ||
| 404 | */ | ||
| 405 | + BOTAN_DEPRECATED("Deprecated no replacement") | ||
| 406 | MatchResult matches(const X509_Certificate& cert) const; | ||
| 407 | |||
| 408 | - private: | ||
| 409 | - std::string m_type; | ||
| 410 | - std::string m_name; | ||
| 411 | - | ||
| 412 | bool matches_dns(const std::string&) const; | ||
| 413 | bool matches_dn(const std::string&) const; | ||
| 414 | + bool matches_dn_obj(const X509_DN& dn) const; | ||
| 415 | bool matches_ip(const std::string&) const; | ||
| 416 | + | ||
| 417 | + private: | ||
| 418 | + std::string m_type; | ||
| 419 | + std::string m_name; | ||
| 420 | }; | ||
| 421 | |||
| 422 | std::ostream& operator<<(std::ostream& os, const GeneralName& gn); | ||
| 423 | @@ -253,6 +259,9 @@ std::ostream& operator<<(std::ostream& os, const GeneralName& gn); | ||
| 424 | * The Name Constraint extension adds a minimum and maximum path | ||
| 425 | * length to a GeneralName to form a constraint. The length limits | ||
| 426 | * are currently unused. | ||
| 427 | +* | ||
| 428 | +* This entire class is deprecated and will be removed in a future | ||
| 429 | +* major release | ||
| 430 | */ | ||
| 431 | class BOTAN_PUBLIC_API(2,0) GeneralSubtree final : public ASN1_Object | ||
| 432 | { | ||
| 433 | @@ -260,6 +269,7 @@ class BOTAN_PUBLIC_API(2,0) GeneralSubtree final : public ASN1_Object | ||
| 434 | /** | ||
| 435 | * Creates an empty name constraint. | ||
| 436 | */ | ||
| 437 | + BOTAN_DEPRECATED("Deprecated no replacement") | ||
| 438 | GeneralSubtree() : m_base(), m_minimum(0), m_maximum(std::numeric_limits<std::size_t>::max()) | ||
| 439 | {} | ||
| 440 | |||
| 441 | @@ -269,6 +279,7 @@ class BOTAN_PUBLIC_API(2,0) GeneralSubtree final : public ASN1_Object | ||
| 442 | * @param min minimum path length | ||
| 443 | * @param max maximum path length | ||
| 444 | */ | ||
| 445 | + BOTAN_DEPRECATED("Deprecated no replacement") | ||
| 446 | GeneralSubtree(const GeneralName& base, size_t min, size_t max) | ||
| 447 | : m_base(base), m_minimum(min), m_maximum(max) | ||
| 448 | {} | ||
| 449 | @@ -277,6 +288,7 @@ class BOTAN_PUBLIC_API(2,0) GeneralSubtree final : public ASN1_Object | ||
| 450 | * Creates a new name constraint for its string format. | ||
| 451 | * @param str name constraint | ||
| 452 | */ | ||
| 453 | + BOTAN_DEPRECATED("Deprecated no replacement") | ||
| 454 | GeneralSubtree(const std::string& str); | ||
| 455 | |||
| 456 | void encode_into(DER_Encoder&) const override; | ||
| 457 | @@ -325,9 +337,7 @@ class BOTAN_PUBLIC_API(2,0) NameConstraints final | ||
| 458 | * @param excluded_subtrees names for which the certificate is not permitted | ||
| 459 | */ | ||
| 460 | NameConstraints(std::vector<GeneralSubtree>&& permitted_subtrees, | ||
| 461 | - std::vector<GeneralSubtree>&& excluded_subtrees) | ||
| 462 | - : m_permitted_subtrees(permitted_subtrees), m_excluded_subtrees(excluded_subtrees) | ||
| 463 | - {} | ||
| 464 | + std::vector<GeneralSubtree>&& excluded_subtrees); | ||
| 465 | |||
| 466 | /** | ||
| 467 | * @return permitted names | ||
| 468 | @@ -339,9 +349,22 @@ class BOTAN_PUBLIC_API(2,0) NameConstraints final | ||
| 469 | */ | ||
| 470 | const std::vector<GeneralSubtree>& excluded() const { return m_excluded_subtrees; } | ||
| 471 | |||
| 472 | + /** | ||
| 473 | + * Return true if all of the names in the certificate are permitted | ||
| 474 | + */ | ||
| 475 | + bool is_permitted(const X509_Certificate& cert, bool reject_unknown) const; | ||
| 476 | + | ||
| 477 | + /** | ||
| 478 | + * Return true if any of the names in the certificate are excluded | ||
| 479 | + */ | ||
| 480 | + bool is_excluded(const X509_Certificate& cert, bool reject_unknown) const; | ||
| 481 | + | ||
| 482 | private: | ||
| 483 | std::vector<GeneralSubtree> m_permitted_subtrees; | ||
| 484 | std::vector<GeneralSubtree> m_excluded_subtrees; | ||
| 485 | + | ||
| 486 | + std::set<std::string> m_permitted_name_types; | ||
| 487 | + std::set<std::string> m_excluded_name_types; | ||
| 488 | }; | ||
| 489 | |||
| 490 | /** | ||
| 491 | diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp | ||
| 492 | index e81e15c..51bc517 100644 | ||
| 493 | --- a/src/lib/x509/x509_ext.cpp | ||
| 494 | +++ b/src/lib/x509/x509_ext.cpp | ||
| 495 | @@ -601,27 +601,27 @@ void Name_Constraints::decode_inner(const std::vector<uint8_t>& in) | ||
| 496 | { | ||
| 497 | std::vector<GeneralSubtree> permit, exclude; | ||
| 498 | BER_Decoder ber(in); | ||
| 499 | - BER_Decoder ext = ber.start_cons(SEQUENCE); | ||
| 500 | - BER_Object per = ext.get_next_object(); | ||
| 501 | + BER_Decoder inner = ber.start_cons(SEQUENCE); | ||
| 502 | + BER_Object per = inner.get_next_object(); | ||
| 503 | |||
| 504 | - ext.push_back(per); | ||
| 505 | + inner.push_back(per); | ||
| 506 | if(per.is_a(0, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) | ||
| 507 | { | ||
| 508 | - ext.decode_list(permit,ASN1_Tag(0),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); | ||
| 509 | + inner.decode_list(permit,ASN1_Tag(0),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); | ||
| 510 | if(permit.empty()) | ||
| 511 | throw Encoding_Error("Empty Name Contraint list"); | ||
| 512 | } | ||
| 513 | |||
| 514 | - BER_Object exc = ext.get_next_object(); | ||
| 515 | - ext.push_back(exc); | ||
| 516 | - if(per.is_a(1, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) | ||
| 517 | + BER_Object exc = inner.get_next_object(); | ||
| 518 | + inner.push_back(exc); | ||
| 519 | + if(exc.is_a(1, ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC))) | ||
| 520 | { | ||
| 521 | - ext.decode_list(exclude,ASN1_Tag(1),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); | ||
| 522 | + inner.decode_list(exclude,ASN1_Tag(1),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); | ||
| 523 | if(exclude.empty()) | ||
| 524 | throw Encoding_Error("Empty Name Contraint list"); | ||
| 525 | } | ||
| 526 | |||
| 527 | - ext.end_cons(); | ||
| 528 | + inner.end_cons(); | ||
| 529 | |||
| 530 | if(permit.empty() && exclude.empty()) | ||
| 531 | throw Encoding_Error("Empty Name Contraint extension"); | ||
| 532 | @@ -650,11 +650,17 @@ void Name_Constraints::contents_to(Data_Store& subject, Data_Store&) const | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | -void Name_Constraints::validate(const X509_Certificate& subject, const X509_Certificate& issuer, | ||
| 537 | +void Name_Constraints::validate(const X509_Certificate& subject, const X509_Certificate& /*issuer*/, | ||
| 538 | const std::vector<std::shared_ptr<const X509_Certificate>>& cert_path, | ||
| 539 | std::vector<std::set<Certificate_Status_Code>>& cert_status, | ||
| 540 | size_t pos) | ||
| 541 | { | ||
| 542 | + // This is much smaller limit than in Botan3 because here name constraint checks | ||
| 543 | + // are much more expensive due to optimizations which would be difficult to | ||
| 544 | + // backport here. | ||
| 545 | + const size_t MAX_NC_COMPARES = (1 << 12); | ||
| 546 | + const size_t total_constraints = m_name_constraints.permitted().size() + m_name_constraints.excluded().size(); | ||
| 547 | + | ||
| 548 | if(!m_name_constraints.permitted().empty() || !m_name_constraints.excluded().empty()) | ||
| 549 | { | ||
| 550 | if(!subject.is_CA_cert()) | ||
| 551 | @@ -663,54 +669,34 @@ void Name_Constraints::validate(const X509_Certificate& subject, const X509_Cert | ||
| 552 | } | ||
| 553 | |||
| 554 | const bool issuer_name_constraint_critical = | ||
| 555 | - issuer.is_critical("X509v3.NameConstraints"); | ||
| 556 | + subject.is_critical("X509v3.NameConstraints"); | ||
| 557 | |||
| 558 | // Check that all subordinate certs pass the name constraint | ||
| 559 | for(size_t j = 0; j < pos; ++j) | ||
| 560 | { | ||
| 561 | - bool permitted = m_name_constraints.permitted().empty(); | ||
| 562 | - bool failed = false; | ||
| 563 | + const auto& cert = cert_path.at(j); | ||
| 564 | |||
| 565 | - for(auto c: m_name_constraints.permitted()) | ||
| 566 | - { | ||
| 567 | - switch(c.base().matches(*cert_path.at(j))) | ||
| 568 | - { | ||
| 569 | - case GeneralName::MatchResult::NotFound: | ||
| 570 | - case GeneralName::MatchResult::All: | ||
| 571 | - permitted = true; | ||
| 572 | - break; | ||
| 573 | - case GeneralName::MatchResult::UnknownType: | ||
| 574 | - failed = issuer_name_constraint_critical; | ||
| 575 | - permitted = true; | ||
| 576 | - break; | ||
| 577 | - default: | ||
| 578 | - break; | ||
| 579 | - } | ||
| 580 | - } | ||
| 581 | + const size_t total_names = | ||
| 582 | + cert->subject_dn().dn_info().size() + | ||
| 583 | + cert->subject_alt_name().get_attributes().size(); | ||
| 584 | |||
| 585 | - for(auto c: m_name_constraints.excluded()) | ||
| 586 | - { | ||
| 587 | - switch(c.base().matches(*cert_path.at(j))) | ||
| 588 | - { | ||
| 589 | - case GeneralName::MatchResult::All: | ||
| 590 | - case GeneralName::MatchResult::Some: | ||
| 591 | - failed = true; | ||
| 592 | - break; | ||
| 593 | - case GeneralName::MatchResult::UnknownType: | ||
| 594 | - failed = issuer_name_constraint_critical; | ||
| 595 | - break; | ||
| 596 | - default: | ||
| 597 | - break; | ||
| 598 | - } | ||
| 599 | - } | ||
| 600 | + if(total_names * total_constraints >= MAX_NC_COMPARES) { | ||
| 601 | + cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); | ||
| 602 | + continue; | ||
| 603 | + } | ||
| 604 | |||
| 605 | - if(failed || !permitted) | ||
| 606 | - { | ||
| 607 | + if(!m_name_constraints.is_permitted(*cert, issuer_name_constraint_critical)) { | ||
| 608 | cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); | ||
| 609 | - } | ||
| 610 | + continue; | ||
| 611 | + } | ||
| 612 | + | ||
| 613 | + if(m_name_constraints.is_excluded(*cert, issuer_name_constraint_critical)) { | ||
| 614 | + cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); | ||
| 615 | + continue; | ||
| 616 | } | ||
| 617 | } | ||
| 618 | } | ||
| 619 | +} | ||
| 620 | |||
| 621 | namespace { | ||
| 622 | |||
| 623 | diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp | ||
| 624 | index 55f279c..fd1bb4c 100644 | ||
| 625 | --- a/src/lib/x509/x509cert.cpp | ||
| 626 | +++ b/src/lib/x509/x509cert.cpp | ||
| 627 | @@ -17,6 +17,7 @@ | ||
| 628 | #include <botan/oids.h> | ||
| 629 | #include <botan/hash.h> | ||
| 630 | #include <botan/hex.h> | ||
| 631 | +#include <botan/internal/stl_util.h> | ||
| 632 | #include <algorithm> | ||
| 633 | #include <sstream> | ||
| 634 | |||
| 635 | @@ -788,16 +789,35 @@ bool X509_Certificate::matches_dns_name(const std::string& name) const | ||
| 636 | if(name.empty()) | ||
| 637 | return false; | ||
| 638 | |||
| 639 | - std::vector<std::string> issued_names = subject_info("DNS"); | ||
| 640 | + bool is_ipv4 = false; | ||
| 641 | |||
| 642 | - // Fall back to CN only if no DNS names are set (RFC 6125 sec 6.4.4) | ||
| 643 | - if(issued_names.empty()) | ||
| 644 | + try { | ||
| 645 | + string_to_ipv4(name); | ||
| 646 | + is_ipv4 = true; | ||
| 647 | + } | ||
| 648 | + catch(...) {} | ||
| 649 | + | ||
| 650 | + std::vector<std::string> issued_names; | ||
| 651 | + | ||
| 652 | + if(subject_alt_name().has_items()) { | ||
| 653 | + issued_names = subject_alt_name().get_attribute(is_ipv4 ? "IP" : "DNS"); | ||
| 654 | + } else if(is_ipv4 == false) { | ||
| 655 | + // Use CN only if no SAN is included | ||
| 656 | issued_names = subject_info("Name"); | ||
| 657 | + } | ||
| 658 | |||
| 659 | for(size_t i = 0; i != issued_names.size(); ++i) | ||
| 660 | { | ||
| 661 | - if(host_wildcard_match(issued_names[i], name)) | ||
| 662 | - return true; | ||
| 663 | + if(is_ipv4) | ||
| 664 | + { | ||
| 665 | + if(issued_names[i] == name) | ||
| 666 | + return true; | ||
| 667 | + } | ||
| 668 | + else | ||
| 669 | + { | ||
| 670 | + if(host_wildcard_match(issued_names[i], name)) | ||
| 671 | + return true; | ||
| 672 | + } | ||
| 673 | } | ||
| 674 | |||
| 675 | return false; | ||
| 676 | diff --git a/src/python/botan2.py b/src/python/botan2.py | ||
| 677 | index 6ae1bd6..f424303 100755 | ||
| 678 | --- a/src/python/botan2.py | ||
| 679 | +++ b/src/python/botan2.py | ||
| 680 | @@ -1285,6 +1285,7 @@ def _load_buf_or_file(filename, buf, file_fn, buf_fn): | ||
| 681 | # | ||
| 682 | class X509Cert(object): # pylint: disable=invalid-name | ||
| 683 | def __init__(self, filename=None, buf=None): | ||
| 684 | + self.__obj = c_void_p(0) | ||
| 685 | self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load) | ||
| 686 | |||
| 687 | def __del__(self): | ||
| 688 | @@ -1464,6 +1465,7 @@ class X509Cert(object): # pylint: disable=invalid-name | ||
| 689 | # | ||
| 690 | class X509CRL(object): | ||
| 691 | def __init__(self, filename=None, buf=None): | ||
| 692 | + self.__obj = c_void_p(0) | ||
| 693 | self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load) | ||
| 694 | |||
| 695 | def __del__(self): | ||
| 696 | diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py | ||
| 697 | index 2202c0e..c45b9f1 100644 | ||
| 698 | --- a/src/scripts/test_python.py | ||
| 699 | +++ b/src/scripts/test_python.py | ||
| 700 | @@ -474,9 +474,6 @@ ofvkP1EDmpx50fHLawIDAQAB | ||
| 701 | self.assertEqual(cert.issuer_dn('Organizational Unit', 0), 'bsi') | ||
| 702 | self.assertEqual(cert.issuer_dn('Country', 0), 'DE') | ||
| 703 | |||
| 704 | - self.assertTrue(cert.hostname_match('csca-germany')) | ||
| 705 | - self.assertFalse(cert.hostname_match('csca-slovakia')) | ||
| 706 | - | ||
| 707 | self.assertEqual(cert.not_before(), 1184858838) | ||
| 708 | self.assertEqual(cert.not_after(), 1831907880) | ||
| 709 | |||
| 710 | diff --git a/src/tests/test_name_constraint.cpp b/src/tests/test_name_constraint.cpp | ||
| 711 | index d99e967..7e08641 100644 | ||
| 712 | --- a/src/tests/test_name_constraint.cpp | ||
| 713 | +++ b/src/tests/test_name_constraint.cpp | ||
| 714 | @@ -29,17 +29,17 @@ class Name_Constraint_Tests final : public Test | ||
| 715 | std::make_tuple( | ||
| 716 | "Root_Email_Name_Constraint.crt", | ||
| 717 | "Invalid_Email_Name_Constraint.crt", | ||
| 718 | - "Invalid Email Name Constraint", | ||
| 719 | + "", | ||
| 720 | "Certificate does not pass name constraint"), | ||
| 721 | std::make_tuple( | ||
| 722 | "Root_DN_Name_Constraint.crt", | ||
| 723 | "Invalid_DN_Name_Constraint.crt", | ||
| 724 | - "Invalid DN Name Constraint", | ||
| 725 | + "", | ||
| 726 | "Certificate does not pass name constraint"), | ||
| 727 | std::make_tuple( | ||
| 728 | "Root_DN_Name_Constraint.crt", | ||
| 729 | "Valid_DN_Name_Constraint.crt", | ||
| 730 | - "Valid DN Name Constraint", | ||
| 731 | + "", | ||
| 732 | "Verified"), | ||
| 733 | std::make_tuple( | ||
| 734 | "Root_DNS_Name_Constraint.crt", | ||
| 735 | @@ -49,12 +49,12 @@ class Name_Constraint_Tests final : public Test | ||
| 736 | std::make_tuple( | ||
| 737 | "Root_IP_Name_Constraint.crt", | ||
| 738 | "Valid_IP_Name_Constraint.crt", | ||
| 739 | - "Valid IP Name Constraint", | ||
| 740 | + "", | ||
| 741 | "Verified"), | ||
| 742 | std::make_tuple( | ||
| 743 | "Root_IP_Name_Constraint.crt", | ||
| 744 | "Invalid_IP_Name_Constraint.crt", | ||
| 745 | - "Invalid IP Name Constraint", | ||
| 746 | + "", | ||
| 747 | "Certificate does not pass name constraint"), | ||
| 748 | }; | ||
| 749 | std::vector<Test::Result> results; | ||
diff --git a/meta-oe/recipes-crypto/botan/botan_2.19.1.bb b/meta-oe/recipes-crypto/botan/botan_2.19.1.bb index 6477da4dbf..71e805b655 100644 --- a/meta-oe/recipes-crypto/botan/botan_2.19.1.bb +++ b/meta-oe/recipes-crypto/botan/botan_2.19.1.bb | |||
| @@ -9,6 +9,7 @@ SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz \ | |||
| 9 | file://0002-FIX-intermediates-can-sign-their-own-OCSP-responses.patch \ | 9 | file://0002-FIX-intermediates-can-sign-their-own-OCSP-responses.patch \ |
| 10 | file://0003-FIX-missing-validation-of-authority-of-delegation-re.patch \ | 10 | file://0003-FIX-missing-validation-of-authority-of-delegation-re.patch \ |
| 11 | file://0004-review-comments.patch \ | 11 | file://0004-review-comments.patch \ |
| 12 | file://0001-Address-various-name-constraint-bugs.patch \ | ||
| 12 | " | 13 | " |
| 13 | SRC_URI[sha256sum] = "e26e00cfefda64082afdd540d3c537924f645d6a674afed2cd171005deff5560" | 14 | SRC_URI[sha256sum] = "e26e00cfefda64082afdd540d3c537924f645d6a674afed2cd171005deff5560" |
| 14 | 15 | ||
