Botan 3.12.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 431 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 395 of file name_constraint.cpp.

396 :
397 m_permitted_subtrees(std::move(permitted_subtrees)), m_excluded_subtrees(std::move(excluded_subtrees)) {
398 for(const auto& c : m_permitted_subtrees) {
399 m_permitted_name_types.insert(c.base().type_code());
400 }
401 for(const auto& c : m_excluded_subtrees) {
402 m_excluded_name_types.insert(c.base().type_code());
403 }
404}

Member Function Documentation

◆ excluded()

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

Definition at line 456 of file pkix_types.h.

456 {
457 return m_excluded_subtrees;
458 }

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 591 of file name_constraint.cpp.

591 {
592 if(excluded().empty()) {
593 return false;
594 }
595
596 const auto& alt_name = cert.subject_alt_name();
597
598 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), excluded().size())) {
599 return true;
600 }
601
602 if(reject_unknown) {
603 // This is one is overly broad: we should just reject if there is a name constraint
604 // with the same OID as one of the other names
605 if(m_excluded_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
606 return true;
607 }
608 if(m_excluded_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
609 return true;
610 }
611 if(m_excluded_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
612 return true;
613 }
614 }
615
616 auto is_excluded_dn = [&](const X509_DN& dn) {
617 // If no restrictions, then immediate accept
618 if(!m_excluded_name_types.contains(GeneralName::NameType::DN)) {
619 return false;
620 }
621
622 for(const auto& c : m_excluded_subtrees) {
623 if(c.base().matches_dn(dn)) {
624 return true;
625 }
626 }
627
628 // There is at least one excluded name and we didn't match
629 return false;
630 };
631
632 auto is_excluded_dns_name = [&](const std::string& name) {
633 if(name.empty() || name.starts_with(".")) {
634 return true;
635 }
636
637 // If no restrictions, then immediate accept
638 if(!m_excluded_name_types.contains(GeneralName::NameType::DNS)) {
639 return false;
640 }
641
642 const bool name_has_wildcard = (name.find('*') != std::string::npos);
643
644 for(const auto& c : m_excluded_subtrees) {
645 if(c.base().matches_dns(name)) {
646 return true;
647 }
648
649 /*
650 RFC 5280 4.2.1.10 - "any name matching a restriction in the
651 excludedSubtrees field is invalid".
652
653 If the cert has a wildcard SAN (*.example.com), and that wildcard
654 could be matched against an excluded name, it must be rejected.
655 */
656 if(name_has_wildcard && c.base().m_type == GeneralName::NameType::DNS) {
657 const auto& constraint = std::get<GeneralName::DNS_IDX>(c.base().m_name);
658 if(host_wildcard_match(name, constraint)) {
659 return true;
660 }
661 }
662 }
663
664 // There is at least one excluded name and we didn't match
665 return false;
666 };
667
668 auto is_excluded_ipv4 = [&](uint32_t ipv4) {
669 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
670 for(const auto& c : m_excluded_subtrees) {
671 if(c.base().matches_ipv4(ipv4)) {
672 return true;
673 }
674 }
675 }
676
677 // This name did not match any of the excluded names
678 return false;
679 };
680
681 auto is_excluded_ipv6 = [&](const IPv6Address& ipv6) {
682 if(m_excluded_name_types.contains(GeneralName::NameType::IPv6)) {
683 for(const auto& c : m_excluded_subtrees) {
684 if(c.base().matches_ipv6(ipv6)) {
685 return true;
686 }
687 }
688 }
689
690 // An IPv4-mapped IPv6 address names an IPv4 address so verify that
691 // address is not restricted by an IPv4 excludes rule
692 if(m_excluded_name_types.contains(GeneralName::NameType::IPv4)) {
693 if(auto embedded_v4 = ipv6.as_ipv4()) {
694 for(const auto& c : m_excluded_subtrees) {
695 if(c.base().matches_ipv4(*embedded_v4)) {
696 return true;
697 }
698 }
699 }
700 }
701
702 // This name did not match any of the excluded names
703 return false;
704 };
705
706 if(is_excluded_dn(cert.subject_dn())) {
707 return true;
708 }
709
710 for(const auto& alt_dn : alt_name.directory_names()) {
711 if(is_excluded_dn(alt_dn)) {
712 return true;
713 }
714 }
715
716 for(const auto& alt_dns : alt_name.dns()) {
717 if(is_excluded_dns_name(alt_dns)) {
718 return true;
719 }
720 }
721
722 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
723 if(is_excluded_ipv4(alt_ipv4)) {
724 return true;
725 }
726 }
727
728 for(const auto& alt_ipv6 : alt_name.ipv6_address()) {
729 if(is_excluded_ipv6(alt_ipv6)) {
730 return true;
731 }
732 }
733
734 if(alt_name.count() == 0) {
735 for(const auto& cn : cert.subject_info("Name")) {
736 if(cn.find(".") != std::string::npos) {
737 if(auto ipv4 = string_to_ipv4(cn)) {
738 if(is_excluded_ipv4(ipv4.value())) {
739 return true;
740 }
741 } else {
742 if(is_excluded_dns_name(canonicalize_dns_name(cn))) {
743 return true;
744 }
745 }
746 }
747 }
748 }
749
750 // We didn't encounter a name that matched any prohibited name
751 return false;
752}
const std::vector< GeneralSubtree > & excluded() const
Definition pkix_types.h:456
std::optional< uint32_t > string_to_ipv4(std::string_view str)
Definition parsing.cpp:155
bool host_wildcard_match(std::string_view issued, std::string_view host)
Definition parsing.cpp:389

References Botan::X509_DN::count(), Botan::GeneralName::DN, Botan::GeneralName::DNS, excluded(), Botan::host_wildcard_match(), Botan::GeneralName::IPv4, Botan::GeneralName::IPv6, Botan::GeneralName::Other, Botan::GeneralName::RFC822, Botan::string_to_ipv4(), Botan::X509_Certificate::subject_alt_name(), Botan::X509_Certificate::subject_dn(), Botan::X509_Certificate::subject_info(), and Botan::GeneralName::URI.

◆ 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 427 of file name_constraint.cpp.

427 {
428 if(permitted().empty()) {
429 return true;
430 }
431
432 const auto& alt_name = cert.subject_alt_name();
433
434 if(exceeds_limit(cert.subject_dn().count(), alt_name.count(), permitted().size())) {
435 return false;
436 }
437
438 if(reject_unknown) {
439 if(m_permitted_name_types.contains(GeneralName::NameType::Other) && !alt_name.other_names().empty()) {
440 return false;
441 }
442 if(m_permitted_name_types.contains(GeneralName::NameType::URI) && !alt_name.uris().empty()) {
443 return false;
444 }
445 if(m_permitted_name_types.contains(GeneralName::NameType::RFC822) && !alt_name.email().empty()) {
446 return false;
447 }
448 }
449
450 auto is_permitted_dn = [&](const X509_DN& dn) {
451 // If no restrictions, then immediate accept
452 if(!m_permitted_name_types.contains(GeneralName::NameType::DN)) {
453 return true;
454 }
455
456 for(const auto& c : m_permitted_subtrees) {
457 if(c.base().matches_dn(dn)) {
458 return true;
459 }
460 }
461
462 // There is at least one permitted name and we didn't match
463 return false;
464 };
465
466 auto is_permitted_dns_name = [&](const std::string& name) {
467 if(name.empty() || name.starts_with(".")) {
468 return false;
469 }
470
471 // If no restrictions, then immediate accept
472 if(!m_permitted_name_types.contains(GeneralName::NameType::DNS)) {
473 return true;
474 }
475
476 for(const auto& c : m_permitted_subtrees) {
477 if(c.base().matches_dns(name)) {
478 return true;
479 }
480 }
481
482 // There is at least one permitted name and we didn't match
483 return false;
484 };
485
486 /*
487 RFC 5280 4.2.1.10: iPAddress is a single GeneralName element where
488 IPv4 and IPv6 are distinguished only by the length.
489
490 An iPAddress subtree of either version therefore restricts the iPAddress name
491 form for both versions.
492 */
493 const bool ip_form_restricted = m_permitted_name_types.contains(GeneralName::NameType::IPv4) ||
494 m_permitted_name_types.contains(GeneralName::NameType::IPv6);
495
496 auto is_permitted_ipv4 = [&](uint32_t ipv4) {
497 if(!ip_form_restricted) {
498 return true;
499 }
500
501 for(const auto& c : m_permitted_subtrees) {
502 if(c.base().matches_ipv4(ipv4)) {
503 return true;
504 }
505 }
506
507 // We might here check if there are any IPv6 permitted names which are
508 // mapped IPv4 addresses, and if so check if any of those apply. It's not
509 // clear if this is desirable, and RFC 5280 is completely silent on the issue.
510
511 // There is at least one permitted iPAddress name and we didn't match
512 return false;
513 };
514
515 auto is_permitted_ipv6 = [&](const IPv6Address& ipv6) {
516 if(!ip_form_restricted) {
517 return true;
518 }
519
520 for(const auto& c : m_permitted_subtrees) {
521 if(c.base().matches_ipv6(ipv6)) {
522 return true;
523 }
524 }
525
526 // There is at least one permitted iPAddress name and we didn't match
527 return false;
528 };
529
530 /*
531 RFC 5280 4.1.2.6:
532 If subject naming information is present only in the
533 subjectAltName extension (e.g., a key bound only to an email
534 address or URI), then the subject name MUST be an empty
535 sequence and the subjectAltName extension MUST be critical.
536
537 RFC 5280 4.2.1.10:
538 Restrictions of the form directoryName MUST be applied to the subject
539 field in the certificate (when the certificate includes a non-empty
540 subject field) and to any names of type directoryName in the
541 subjectAltName extension.
542 */
543 if(!cert.subject_dn().empty() && !is_permitted_dn(cert.subject_dn())) {
544 return false;
545 }
546
547 for(const auto& alt_dn : alt_name.directory_names()) {
548 if(!is_permitted_dn(alt_dn)) {
549 return false;
550 }
551 }
552
553 for(const auto& alt_dns : alt_name.dns()) {
554 if(!is_permitted_dns_name(alt_dns)) {
555 return false;
556 }
557 }
558
559 for(const auto& alt_ipv4 : alt_name.ipv4_address()) {
560 if(!is_permitted_ipv4(alt_ipv4)) {
561 return false;
562 }
563 }
564
565 for(const auto& alt_ipv6 : alt_name.ipv6_address()) {
566 if(!is_permitted_ipv6(alt_ipv6)) {
567 return false;
568 }
569 }
570
571 if(alt_name.count() == 0) {
572 for(const auto& cn : cert.subject_info("Name")) {
573 if(cn.find(".") != std::string::npos) {
574 if(auto ipv4 = string_to_ipv4(cn)) {
575 if(!is_permitted_ipv4(ipv4.value())) {
576 return false;
577 }
578 } else {
579 if(!is_permitted_dns_name(canonicalize_dns_name(cn))) {
580 return false;
581 }
582 }
583 }
584 }
585 }
586
587 // We didn't encounter a name that doesn't have a matching constraint
588 return true;
589}
const std::vector< GeneralSubtree > & permitted() const
Definition pkix_types.h:449

References Botan::X509_DN::count(), Botan::GeneralName::DN, Botan::GeneralName::DNS, Botan::X509_DN::empty(), Botan::GeneralName::IPv4, Botan::GeneralName::IPv6, Botan::GeneralName::Other, permitted(), Botan::GeneralName::RFC822, Botan::string_to_ipv4(), Botan::X509_Certificate::subject_alt_name(), Botan::X509_Certificate::subject_dn(), Botan::X509_Certificate::subject_info(), and Botan::GeneralName::URI.

◆ permitted()

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

Definition at line 449 of file pkix_types.h.

449 {
450 return m_permitted_subtrees;
451 }

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: