Botan 3.13.0
Crypto and TLS for C&
Botan::NameConstraints Class Referencefinal

Name Constraints. More...

#include <pkix_types.h>

Public Member Functions

const std::vector< GeneralSubtree > & excluded () const
bool is_excluded (const X509_Certificate &cert, bool reject_unknown) const
bool is_permitted (const X509_Certificate &cert, bool reject_unknown) const
 NameConstraints ()=default
 NameConstraints (std::vector< GeneralSubtree > &&permitted_subtrees, std::vector< GeneralSubtree > &&excluded_subtrees)
const std::vector< GeneralSubtree > & permitted () const

Detailed Description

Name Constraints.

Wraps the Name Constraints associated with a certificate.

Definition at line 750 of file pkix_types.h.

Constructor & Destructor Documentation

◆ NameConstraints() [1/2]

Botan::NameConstraints::NameConstraints ( )
default

Creates an empty name NameConstraints.

References NameConstraints().

Referenced by NameConstraints().

◆ NameConstraints() [2/2]

Botan::NameConstraints::NameConstraints ( std::vector< GeneralSubtree > && permitted_subtrees,
std::vector< GeneralSubtree > && excluded_subtrees )

Creates NameConstraints from a list of permitted and excluded subtrees.

Parameters
permitted_subtreesnames for which the certificate is permitted
excluded_subtreesnames for which the certificate is not permitted

Definition at line 832 of file name_constraint.cpp.

833 :
834 m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
835 for(const auto& c : m_permitted_subtrees) {
836 m_permitted_name_types.insert(c.base().type_code());
837 }
838 for(const auto& c : m_excluded_subtrees) {
839 m_excluded_name_types.insert(c.base().type_code());
840 }
841}

Member Function Documentation

◆ excluded()

const std::vector< GeneralSubtree > & Botan::NameConstraints::excluded ( ) const
inline
Returns
excluded names

Definition at line 775 of file pkix_types.h.

775 {
776 return m_excluded_subtrees;
777 }

References excluded().

Referenced by botan_x509_cert_excluded_name_constraints(), excluded(), and is_excluded().

◆ is_excluded()

bool Botan::NameConstraints::is_excluded ( const X509_Certificate & cert,
bool reject_unknown ) const

Return true if any of the names in the certificate are excluded

Definition at line 1135 of file name_constraint.cpp.

1135 {
1136 if(excluded().empty()) {
1137 return false;
1138 }
1139
1140 const auto& alt_name = cert.subject_alt_name();
1141
1142 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
1143 return true;
1144 }
1145
1146 if(reject_unknown) {
1147 // This is one is overly broad: we should just reject if there is a name constraint
1148 // with the same OID as one of the other names
1149 if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_name_values().empty()) {
1150 return true;
1151 }
1152 // As in is_permitted: a critical NC restricting an unrecognized
1153 // GeneralName form cannot be evaluated; reject conservatively.
1154 if(m_excluded_name_types.contains(GeneralName::NameType::Unknown)) {
1155 return true;
1156 }
1157 }
1158
1159 auto is_excluded_dn = [&](const X509_DN& dn) {
1160 // If no restrictions, then immediate accept
1161 if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
1162 return false;
1163 }
1164
1165 for(const auto& c : m_excluded_subtrees) {
1166 if(c.base().matches_dn(dn)) {
1167 return true;
1168 }
1169 }
1170
1171 // There is at least one excluded name and we didn't match
1172 return false;
1173 };
1174
1175 auto is_excluded_dns_name = [&](const DNSName& name) {
1176 // If no restrictions, then immediate accept
1177 if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
1178 return false;
1179 }
1180
1181 for(const auto& c : m_excluded_subtrees) {
1182 if(c.base().matches_dns(name)) {
1183 return true;
1184 }
1185
1186 /*
1187 RFC 5280 4.2.1.10:
1188 Any name matching a restriction in the excludedSubtrees
1189 field is invalid regardless of information appearing in
1190 the permittedSubtrees.
1191
1192 If the cert has a wildcard SAN (*.example.com), and that wildcard
1193 could be matched against an excluded name, it must be rejected.
1194 */
1195 if(c.base().m_type == GeneralName::NameType::DNS && name.is_wildcard()) {
1196 const auto& constraint = std::get<GeneralName::DNSConstraint>(c.base().m_name).value();
1197 if(wildcard_intersects_excluded_dns_subtree(name.to_string(), constraint)) {
1198 return true;
1199 }
1200 }
1201 }
1202
1203 // There is at least one excluded name and we didn't match
1204 return false;
1205 };
1206
1207 auto is_excluded_ipv4 = [&](const IPv4Address& ipv4) {
1208 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
1209 for(const auto& c : m_excluded_subtrees) {
1210 if(c.base().matches_ipv4(ipv4)) {
1211 return true;
1212 }
1213 }
1214 }
1215
1216 // This name did not match any of the excluded names
1217 return false;
1218 };
1219
1220 auto is_excluded_ipv6 = [&](const IPv6Address& ipv6) {
1221 if(m_excluded_name_types.contains(GeneralName::NameType::IPv6)) {
1222 for(const auto& c : m_excluded_subtrees) {
1223 if(c.base().matches_ipv6(ipv6)) {
1224 return true;
1225 }
1226 }
1227 }
1228
1229 // An IPv4-mapped IPv6 address names an IPv4 address so verify that
1230 // address is not restricted by an IPv4 excludes rule
1231 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
1232 if(auto embedded_v4 = ipv6.as_ipv4()) {
1233 for(const auto& c : m_excluded_subtrees) {
1234 if(c.base().matches_ipv4(*embedded_v4)) {
1235 return true;
1236 }
1237 }
1238 }
1239 }
1240
1241 // This name did not match any of the excluded names
1242 return false;
1243 };
1244
1245 auto is_excluded_uri = [&](const URI& uri) {
1246 if(!m_excluded_name_types.contains(GeneralName::NameType::URI)) {
1247 return false;
1248 }
1249 /*
1250 RFC 5280 4.2.1.10:
1251 If a constraint is applied to the uniformResourceIdentifier
1252 name form and a subsequent certificate includes a
1253 subjectAltName extension with a uniformResourceIdentifier that
1254 does not include an authority component with a host name
1255 specified as a fully qualified domain name (e.g., if the URI
1256 either does not include an authority component or includes an
1257 authority component in which the host name is specified as an
1258 IP address), then the application MUST reject the certificate.
1259 */
1260 const auto host = uri.host();
1261 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
1262 return true;
1263 }
1264 if(std::get<DNSName>(host->get()).to_string().find('.') == std::string::npos) {
1265 return true;
1266 }
1267 for(const auto& c : m_excluded_subtrees) {
1268 if(c.base().matches_uri(uri)) {
1269 return true;
1270 }
1271 }
1272 return false;
1273 };
1274
1275 /*
1276 * The email matching logic on the exclude side is intentionally stricter
1277 * (more expansive) than the permit side logic.
1278 *
1279 * RFC 9549 updates RFC 5280 and among other things completely removes mailbox
1280 * form constraints (ones with a '@', rather than just a domain constraint)
1281 * claiming "This capability was not used".
1282 *
1283 * This prohibition is reiterated in RFC 9598 Section 6 with "rfc822Name
1284 * constraints with a Local-part SHOULD NOT be used."
1285 *
1286 * Here we lean very conservative in our interpretation: if there is a
1287 * mailbox-form exclude constraint, we reject any mailbox at that domain. That
1288 * is, if excludedSubtrees includes "user@example.com", we treat that
1289 * constraint identically to an exclusion of "example.com".
1290 *
1291 * This might be overly cautious, but generally a rejects-valid bug gets you a
1292 * prompt bug report with testcase, while an accepts-invalid eventually gets
1293 * you a surprise CVE.
1294 */
1295 auto mailbox_form_constraint_covers_domain = [](const GeneralName& gn, const DNSName& san_domain) {
1296 if(gn.type_code() != GeneralName::NameType::RFC822) {
1297 return false;
1298 }
1299 const auto& constraint = std::get<GeneralName::EmailConstraint>(gn.m_name).value();
1300 const auto at = constraint.find('@');
1301 return at != std::string::npos && san_domain.to_string() == constraint.substr(at + 1);
1302 };
1303
1304 auto is_excluded_email = [&](const EmailAddress& addr) {
1305 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1306 for(const auto& c : m_excluded_subtrees) {
1307 if(c.base().matches_email(addr)) {
1308 return true;
1309 }
1310 /*
1311 If we were strictly following RFC 9549 we would here want to call
1312 mailbox_form_constraint_covers_domain, but this breaks chains which
1313 are in conformance to the specifications prior to 9549.
1314 */
1315 }
1316 }
1317 return false;
1318 };
1319
1320 // RFC 9598 Section 6: rfc822Name name constraints also apply to
1321 // SmtpUTF8Mailbox SAN entries. See is_permitted_smtp_utf8.
1322 auto is_excluded_smtp_utf8 = [&](const SmtpUtf8Mailbox& mailbox) {
1323 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1324 for(const auto& c : m_excluded_subtrees) {
1325 if(c.base().matches_email(mailbox)) {
1326 return true;
1327 }
1328 if(mailbox_form_constraint_covers_domain(c.base(), mailbox.domain())) {
1329 return true;
1330 }
1331 }
1332 }
1333 return false;
1334 };
1335
1336 if(is_excluded_dn(cert.subject_dn())) {
1337 return true;
1338 }
1339
1340 for(const auto& alt_dn : alt_name.directory_names()) {
1341 if(is_excluded_dn(alt_dn)) {
1342 return true;
1343 }
1344 }
1345
1346 for(const auto& alt_dns : alt_name.dns_names()) {
1347 if(is_excluded_dns_name(alt_dns)) {
1348 return true;
1349 }
1350 }
1351
1352 for(const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1353 if(is_excluded_ipv4(alt_ipv4)) {
1354 return true;
1355 }
1356 }
1357
1358 for(const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1359 if(is_excluded_ipv6(alt_ipv6)) {
1360 return true;
1361 }
1362 }
1363
1364 for(const auto& uri : alt_name.uri_names()) {
1365 if(is_excluded_uri(uri)) {
1366 return true;
1367 }
1368 }
1369
1370 for(const auto& addr : alt_name.email_addresses()) {
1371 if(is_excluded_email(addr)) {
1372 return true;
1373 }
1374 }
1375
1376 for(const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1377 if(is_excluded_smtp_utf8(mailbox)) {
1378 return true;
1379 }
1380 }
1381
1382 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
1383 if(alt_name.is_empty()) {
1384 for(const auto& cn : cert.subject_info("Name")) {
1385 if(auto ipv4 = IPv4Address::from_string(cn)) {
1386 if(is_excluded_ipv4(*ipv4)) {
1387 return true;
1388 }
1389 } else if(cn.find('.') != std::string::npos) {
1390 if(auto dns_form = DNSName::from_san_string(cn)) {
1391 if(is_excluded_dns_name(*dns_form)) {
1392 return true;
1393 }
1394 }
1395 }
1396 }
1397
1398 // RFC 5280 4.2.1.10 fallback to subject DN emailAddress when the cert has no SAN
1399 for(const auto& email_str : cert.subject_dn().get_attribute("PKCS9.EmailAddress")) {
1400 if(auto addr = EmailAddress::from_string(email_str)) {
1401 if(is_excluded_email(*addr)) {
1402 return true;
1403 }
1404 } else if(m_excluded_name_types.contains(GeneralName::NameType::RFC822)) {
1405 return true;
1406 }
1407 }
1408 }
1409
1410 // We didn't encounter a name that matched any prohibited name
1411 return false;
1412}
static std::optional< DNSName > from_san_string(std::string_view name)
Definition dns_name.cpp:149
static std::optional< EmailAddress > from_string(std::string_view addr)
Definition email.cpp:78
static std::optional< IPv4Address > from_string(std::string_view str)
const std::vector< GeneralSubtree > & excluded() const
Definition pkix_types.h:775
bool wildcard_intersects_excluded_dns_subtree(std::string_view pattern, std::string_view constraint)

References Botan::X509_DN::count(), Botan::GeneralName::DN, Botan::GeneralName::DNS, excluded(), Botan::DNSName::from_san_string(), Botan::EmailAddress::from_string(), Botan::IPv4Address::from_string(), Botan::X509_DN::get_attribute(), Botan::GeneralName::IPv4, Botan::GeneralName::IPv6, Botan::GeneralName::Other, Botan::GeneralName::RFC822, Botan::X509_Certificate::subject_alt_name(), Botan::X509_Certificate::subject_dn(), Botan::X509_Certificate::subject_info(), Botan::GeneralName::type_code(), Botan::GeneralName::Unknown, Botan::GeneralName::URI, and Botan::wildcard_intersects_excluded_dns_subtree().

◆ is_permitted()

bool Botan::NameConstraints::is_permitted ( const X509_Certificate & cert,
bool reject_unknown ) const

Return true if all of the names in the certificate are permitted

Definition at line 864 of file name_constraint.cpp.

864 {
865 if(permitted().empty()) {
866 return true;
867 }
868
869 const auto& alt_name = cert.subject_alt_name();
870
871 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
872 return false;
873 }
874
875 if(reject_unknown) {
876 /* A critical NC restricting an unrecognized GeneralName form (e.g. x400Address)
877 * causes immediate rejection.
878 *
879 * RFC 5280 4.2.1.10 leaves this both unspecified
880 * The syntax and semantics for name constraints for otherName, ediPartyName, and
881 * registeredID are not defined by this specification
882 * and discouraged
883 * Conforming CAs [...] SHOULD NOT impose name constraints on the x400Address,
884 * ediPartyName, or registeredID name forms.
885 *
886 * In principle we should only reject when the constrained form appears in the
887 * certificate. But this situation in general seems to be a minefield, with no help
888 * from specs, test suites, etc. Lacking any obvious use case, just fail closed.
889 *
890 * If you happen to hit this with a real chain, open an issue.
891 */
892 if(m_permitted_name_types.contains(GeneralName::NameType::Unknown)) {
893 return false;
894 }
895 if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_name_values().empty()) {
896 return false;
897 }
898 }
899
900 auto is_permitted_dn = [&](const X509_DN& dn) {
901 // If no restrictions, then immediate accept
902 if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
903 return true;
904 }
905
906 for(const auto& c : m_permitted_subtrees) {
907 if(c.base().matches_dn(dn)) {
908 return true;
909 }
910 }
911
912 // There is at least one permitted name and we didn't match
913 return false;
914 };
915
916 auto is_permitted_dns_name = [&](const DNSName& name) {
917 // If no restrictions, then immediate accept
918 if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
919 return true;
920 }
921
922 for(const auto& c : m_permitted_subtrees) {
923 if(c.base().matches_dns(name)) {
924 return true;
925 }
926 }
927
928 // There is at least one permitted name and we didn't match
929 return false;
930 };
931
932 /*
933 RFC 5280 4.2.1.10: iPAddress is a single GeneralName element where
934 IPv4 and IPv6 are distinguished only by the length.
935
936 An iPAddress subtree of either version therefore restricts the iPAddress name
937 form for both versions.
938 */
939 const bool ip_form_restricted = m_permitted_name_types.contains(GeneralName::NameType::IPv4) ||
940 m_permitted_name_types.contains(GeneralName::NameType::IPv6);
941
942 auto is_permitted_ipv4 = [&](const IPv4Address& ipv4) {
943 if(!ip_form_restricted) {
944 return true;
945 }
946
947 for(const auto& c : m_permitted_subtrees) {
948 if(c.base().matches_ipv4(ipv4)) {
949 return true;
950 }
951 }
952
953 // We might here check if there are any IPv6 permitted names which are
954 // mapped IPv4 addresses, and if so check if any of those apply. It's not
955 // clear if this is desirable, and RFC 5280 is completely silent on the issue.
956
957 // There is at least one permitted iPAddress name and we didn't match
958 return false;
959 };
960
961 auto is_permitted_ipv6 = [&](const IPv6Address& ipv6) {
962 if(!ip_form_restricted) {
963 return true;
964 }
965
966 for(const auto& c : m_permitted_subtrees) {
967 if(c.base().matches_ipv6(ipv6)) {
968 return true;
969 }
970 }
971
972 // There is at least one permitted iPAddress name and we didn't match
973 return false;
974 };
975
976 auto is_permitted_uri = [&](const URI& uri) {
977 // If no URI restrictions, accept.
978 if(!m_permitted_name_types.contains(GeneralName::NameType::URI)) {
979 return true;
980 }
981 /*
982 RFC 5280 4.2.1.10:
983 If a constraint is applied to the uniformResourceIdentifier
984 name form and a subsequent certificate includes a
985 subjectAltName extension with a uniformResourceIdentifier that
986 does not include an authority component with a host name
987 specified as a fully qualified domain name (e.g., if the URI
988 either does not include an authority component or includes an
989 authority component in which the host name is specified as an
990 IP address), then the application MUST reject the certificate.
991 */
992 const auto host = uri.host();
993 if(!host.has_value() || !std::holds_alternative<DNSName>(host->get())) {
994 return false;
995 }
996 if(std::get<DNSName>(host->get()).to_string().find('.') == std::string::npos) {
997 return false;
998 }
999 for(const auto& c : m_permitted_subtrees) {
1000 if(c.base().matches_uri(uri)) {
1001 return true;
1002 }
1003 }
1004 return false;
1005 };
1006
1007 auto is_permitted_email = [&](const EmailAddress& addr) {
1008 // If no email restrictions, accept.
1009 if(!m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1010 return true;
1011 }
1012 for(const auto& c : m_permitted_subtrees) {
1013 if(c.base().matches_email(addr)) {
1014 return true;
1015 }
1016 }
1017 return false;
1018 };
1019
1020 // RFC 9598 Section 6 extends rfc822Name name constraints to SmtpUTF8Mailbox
1021 // SAN entries (id-on-SmtpUTF8Mailbox otherNames). When rfc822Name
1022 // constraints are in effect, every SmtpUTF8Mailbox SAN must match
1023 // at least one permitted entry.
1024 auto is_permitted_smtp_utf8 = [&](const SmtpUtf8Mailbox& mailbox) {
1025 if(!m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1026 return true;
1027 }
1028 for(const auto& c : m_permitted_subtrees) {
1029 if(c.base().matches_email(mailbox)) {
1030 return true;
1031 }
1032 }
1033 return false;
1034 };
1035
1036 /*
1037 RFC 5280 4.1.2.6:
1038 If subject naming information is present only in the
1039 subjectAltName extension (e.g., a key bound only to an email
1040 address or URI), then the subject name MUST be an empty
1041 sequence and the subjectAltName extension MUST be critical.
1042
1043 RFC 5280 4.2.1.10:
1044 Restrictions of the form directoryName MUST be applied to the subject
1045 field in the certificate (when the certificate includes a non-empty
1046 subject field) and to any names of type directoryName in the
1047 subjectAltName extension.
1048 */
1049 if(!cert.subject_dn().empty() && !is_permitted_dn(cert.subject_dn())) {
1050 return false;
1051 }
1052
1053 for(const auto& alt_dn : alt_name.directory_names()) {
1054 if(!is_permitted_dn(alt_dn)) {
1055 return false;
1056 }
1057 }
1058
1059 for(const auto& alt_dns : alt_name.dns_names()) {
1060 if(!is_permitted_dns_name(alt_dns)) {
1061 return false;
1062 }
1063 }
1064
1065 for(const auto& alt_ipv4 : alt_name.ipv4_addresses()) {
1066 if(!is_permitted_ipv4(alt_ipv4)) {
1067 return false;
1068 }
1069 }
1070
1071 for(const auto& alt_ipv6 : alt_name.ipv6_addresses()) {
1072 if(!is_permitted_ipv6(alt_ipv6)) {
1073 return false;
1074 }
1075 }
1076
1077 for(const auto& uri : alt_name.uri_names()) {
1078 if(!is_permitted_uri(uri)) {
1079 return false;
1080 }
1081 }
1082
1083 for(const auto& addr : alt_name.email_addresses()) {
1084 if(!is_permitted_email(addr)) {
1085 return false;
1086 }
1087 }
1088
1089 for(const auto& mailbox : alt_name.smtp_utf8_mailboxes()) {
1090 if(!is_permitted_smtp_utf8(mailbox)) {
1091 return false;
1092 }
1093 }
1094
1095 // TODO(Botan4): CN fallback is deprecated for removal in Botan4.
1096 if(alt_name.is_empty()) {
1097 for(const auto& cn : cert.subject_info("CN")) {
1098 if(auto ipv4 = IPv4Address::from_string(cn)) {
1099 if(!is_permitted_ipv4(*ipv4)) {
1100 return false;
1101 }
1102 } else if(cn.find('.') != std::string::npos) {
1103 if(auto dns_form = DNSName::from_san_string(cn)) {
1104 if(!is_permitted_dns_name(*dns_form)) {
1105 return false;
1106 }
1107 }
1108 }
1109 }
1110
1111 /*
1112 RFC 5280 4.2.1.10:
1113 When constraints are imposed on the rfc822Name name form, but the
1114 certificate does not include a subject alternative name, the
1115 rfc822Name constraint MUST be applied to the attribute of type
1116 emailAddress in the subject distinguished name.
1117 */
1118 for(const auto& email_str : cert.subject_dn().get_attribute("PKCS9.EmailAddress")) {
1119 if(auto addr = EmailAddress::from_string(email_str)) {
1120 if(!is_permitted_email(*addr)) {
1121 return false;
1122 }
1123 } else if(m_permitted_name_types.contains(GeneralName::NameType::RFC822)) {
1124 // emailAddress is present but unparsable and an rfc822Name
1125 // constraint is in effect; treat as not permitted.
1126 return false;
1127 }
1128 }
1129 }
1130
1131 // We didn't encounter a name that doesn't have a matching constraint
1132 return true;
1133}
const std::vector< GeneralSubtree > & permitted() const
Definition pkix_types.h:768

References Botan::X509_DN::count(), Botan::GeneralName::DN, Botan::GeneralName::DNS, Botan::X509_DN::empty(), Botan::DNSName::from_san_string(), Botan::EmailAddress::from_string(), Botan::IPv4Address::from_string(), Botan::X509_DN::get_attribute(), Botan::GeneralName::IPv4, Botan::GeneralName::IPv6, Botan::GeneralName::Other, permitted(), Botan::GeneralName::RFC822, Botan::X509_Certificate::subject_alt_name(), Botan::X509_Certificate::subject_dn(), Botan::X509_Certificate::subject_info(), Botan::GeneralName::Unknown, and Botan::GeneralName::URI.

◆ permitted()

const std::vector< GeneralSubtree > & Botan::NameConstraints::permitted ( ) const
inline
Returns
permitted names

Definition at line 768 of file pkix_types.h.

768 {
769 return m_permitted_subtrees;
770 }

References permitted().

Referenced by botan_x509_cert_permitted_name_constraints(), is_permitted(), permitted(), and Botan::X509_Certificate::to_string().


The documentation for this class was generated from the following files: