diff options
author | Jack Lloyd <[email protected]> | 2016-03-16 01:27:29 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-03-16 01:27:29 -0400 |
commit | eba8e2e0f1baf64637acda3f049fa14f79283201 (patch) | |
tree | a9f5311413629259f8169b80eef87312c8760ee2 | |
parent | 93966abb3c51a77edf867abe7d7388ec542411bb (diff) | |
parent | efe8e7d46683ceab23889fda7fcbc68303f23d62 (diff) |
Merge GH #454 X.509 name constraints
25 files changed, 1016 insertions, 24 deletions
diff --git a/doc/license.txt b/doc/license.txt index ba4381c1b..ef0b97ac1 100644 --- a/doc/license.txt +++ b/doc/license.txt @@ -28,6 +28,7 @@ Copyright (C) 1999-2013,2014,2015,2016 Jack Lloyd 2015 René Korthaus 2015,2016 Daniel Neus 2015 Uri Blumenthal + 2015,2016 Kai Michaelis All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/doc/todo.rst b/doc/todo.rst index 32927e6d7..0045b18be 100644 --- a/doc/todo.rst +++ b/doc/todo.rst @@ -32,7 +32,6 @@ PKIX ---------------------------------------- * Support multiple DNS names in certificates -* X.509 name constraints * X.509 policy constraints * OCSP responder logic * X.509 attribute certificates (RFC 5755) diff --git a/src/lib/asn1/oid_lookup/default.cpp b/src/lib/asn1/oid_lookup/default.cpp index 5bd268e5b..fe7a42748 100644 --- a/src/lib/asn1/oid_lookup/default.cpp +++ b/src/lib/asn1/oid_lookup/default.cpp @@ -177,6 +177,7 @@ const char* default_oid_list() "2.5.29.21 = X509v3.ReasonCode" "\n" "2.5.29.23 = X509v3.HoldInstructionCode" "\n" "2.5.29.24 = X509v3.InvalidityDate" "\n" + "2.5.29.30 = X509v3.NameConstraints" "\n" "2.5.29.31 = X509v3.CRLDistributionPoints" "\n" "2.5.29.32 = X509v3.CertificatePolicies" "\n" "2.5.29.35 = X509v3.AuthorityKeyIdentifier" "\n" diff --git a/src/lib/asn1/x509_dn.cpp b/src/lib/asn1/x509_dn.cpp index 9c36cd695..e9a4731b3 100644 --- a/src/lib/asn1/x509_dn.cpp +++ b/src/lib/asn1/x509_dn.cpp @@ -12,6 +12,7 @@ #include <botan/internal/stl_util.h> #include <botan/oids.h> #include <ostream> +#include <cctype> namespace Botan { @@ -117,15 +118,15 @@ std::vector<byte> X509_DN::get_bits() const */ std::string X509_DN::deref_info_field(const std::string& info) { - if(info == "Name" || info == "CommonName") return "X520.CommonName"; - if(info == "SerialNumber") return "X520.SerialNumber"; - if(info == "Country") return "X520.Country"; - if(info == "Organization") return "X520.Organization"; - if(info == "Organizational Unit" || info == "OrgUnit") + if(info == "Name" || info == "CommonName" || info == "CN") return "X520.CommonName"; + if(info == "SerialNumber" || info == "SN") return "X520.SerialNumber"; + if(info == "Country" || info == "C") return "X520.Country"; + if(info == "Organization" || info == "O") return "X520.Organization"; + if(info == "Organizational Unit" || info == "OrgUnit" || info == "OU") return "X520.OrganizationalUnit"; - if(info == "Locality") return "X520.Locality"; - if(info == "State" || info == "Province") return "X520.State"; - if(info == "Email") return "RFC822"; + if(info == "Locality" || info == "L") return "X520.Locality"; + if(info == "State" || info == "Province" || info == "ST") return "X520.State"; + if(info == "Email") return "RFC822"; return info; } @@ -303,9 +304,93 @@ std::ostream& operator<<(std::ostream& out, const X509_DN& dn) for(std::multimap<std::string, std::string>::const_iterator i = contents.begin(); i != contents.end(); ++i) { - out << to_short_form(i->first) << "=" << i->second << ' '; + out << to_short_form(i->first) << "=\""; + for(char c: i->second) + { + if(c == '\\' || c == '\"') + { + out << "\\"; + } + out << c; + } + out << "\""; + + if(std::next(i) != contents.end()) + { + out << ","; + } } return out; } +std::istream& operator>>(std::istream& in, X509_DN& dn) + { + in >> std::noskipws; + do + { + std::string key; + std::string val; + char c; + + while(in.good()) + { + in >> c; + + if(std::isspace(c) && key.empty()) + continue; + else if(!std::isspace(c)) + { + key.push_back(c); + break; + } + else + break; + } + + while(in.good()) + { + in >> c; + + if(!std::isspace(c) && c != '=') + key.push_back(c); + else if(c == '=') + break; + else + throw Invalid_Argument("Ill-formed X.509 DN"); + } + + bool in_quotes = false; + while(in.good()) + { + in >> c; + + if(std::isspace(c)) + { + if(!in_quotes && !val.empty()) + break; + else if(in_quotes) + val.push_back(' '); + } + else if(c == '"') + in_quotes = !in_quotes; + else if(c == '\\') + { + if(in.good()) + in >> c; + val.push_back(c); + } + else if(c == ',' && !in_quotes) + break; + else + val.push_back(c); + } + + if(!key.empty() && !val.empty()) + dn.add_attribute(X509_DN::deref_info_field(key),val); + else + break; + } + while(in.good()); + return in; + } } diff --git a/src/lib/asn1/x509_dn.h b/src/lib/asn1/x509_dn.h index 12553a1a0..2b841feb6 100644 --- a/src/lib/asn1/x509_dn.h +++ b/src/lib/asn1/x509_dn.h @@ -50,6 +50,7 @@ bool BOTAN_DLL operator!=(const X509_DN&, const X509_DN&); bool BOTAN_DLL operator<(const X509_DN&, const X509_DN&); BOTAN_DLL std::ostream& operator<<(std::ostream& out, const X509_DN& dn); +BOTAN_DLL std::istream& operator>>(std::istream& in, X509_DN& dn); } diff --git a/src/lib/cert/x509/cert_status.h b/src/lib/cert/x509/cert_status.h index 5028af384..6e8635237 100644 --- a/src/lib/cert/x509/cert_status.h +++ b/src/lib/cert/x509/cert_status.h @@ -38,6 +38,7 @@ enum class Certificate_Status_Code { INVALID_USAGE, CERT_CHAIN_TOO_LONG, CA_CERT_NOT_FOR_CERT_ISSUER, + NAME_CONSTRAINT_ERROR, // Revocation errors CA_CERT_NOT_FOR_CRL_ISSUER, diff --git a/src/lib/cert/x509/name_constraint.cpp b/src/lib/cert/x509/name_constraint.cpp new file mode 100644 index 000000000..a1ed19856 --- /dev/null +++ b/src/lib/cert/x509/name_constraint.cpp @@ -0,0 +1,273 @@ +/* +* X.509 Name Constraint +* (C) 2015 Kai Michaelis +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/name_constraint.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> +#include <botan/charset.h> +#include <botan/loadstor.h> +#include <botan/x509_dn.h> +#include <botan/x509cert.h> +#include <sstream> + +namespace Botan { + +GeneralName::GeneralName(const std::string& v) : GeneralName() + { + size_t p = v.find(':'); + + if(p != std::string::npos) + { + m_type = v.substr(0,p); + m_name = v.substr(p + 1,std::string::npos); + } + else + { + throw Invalid_Argument("Failed to decode Name Constraint"); + } + } + +void GeneralName::encode_into(class DER_Encoder&) const + { + throw Exception("General Name encoding not implemented"); + } + +void GeneralName::decode_from(class BER_Decoder& ber) + { + BER_Object obj = ber.get_next_object(); + if((obj.class_tag != CONTEXT_SPECIFIC) && + (obj.class_tag != (CONTEXT_SPECIFIC | CONSTRUCTED))) + throw Decoding_Error("Invalid class tag while decoding GeneralName"); + + const ASN1_Tag tag = obj.type_tag; + + if(tag == 1 || tag == 2 || tag == 6) + { + m_name = Charset::transcode(ASN1::to_string(obj),LATIN1_CHARSET,LOCAL_CHARSET); + + if(tag == 1) + { + m_type = "RFC822"; + } + else if(tag == 2) + { + m_type = "DNS"; + } + else if(tag == 6) + { + m_type = "URI"; + } + } + else if(tag == 4) + { + X509_DN dn; + std::multimap<std::string, std::string> nam; + BER_Decoder dec(obj.value); + std::stringstream ss; + + dn.decode_from(dec); + ss << dn; + + m_name = ss.str(); + m_type = "DN"; + } + else if(tag == 7) + { + if(obj.value.size() == 8) + { + const std::vector<byte> ip(obj.value.begin(),obj.value.begin() + 4); + const std::vector<byte> net(obj.value.begin() + 4,obj.value.end()); + m_type = "IP"; + m_name = ipv4_to_string(load_be<u32bit>(ip.data(),0)) + "/" + ipv4_to_string(load_be<u32bit>(net.data(),0)); + } + else if(obj.value.size() == 32) + { + throw Decoding_Error("Unsupported IPv6 name constraint"); + } + else + { + throw Decoding_Error("Invalid IP name constraint size " + + std::to_string(obj.value.size())); + } + } + else + { + throw Decoding_Error("Found unknown GeneralName type"); + } + } + +GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) const + { + std::vector<std::string> nam; + std::function<bool(const GeneralName*,const std::string&)> match_fn; + + if(type() == "DNS") + { + match_fn = std::mem_fn(&GeneralName::matches_dns); + nam = cert.subject_info("DNS"); + + if(nam.empty()) + { + nam = cert.subject_info("CN"); + } + } + else if(type() == "DN") + { + match_fn = std::mem_fn(&GeneralName::matches_dn); + + std::stringstream ss; + ss << cert.subject_dn(); + nam.push_back(ss.str()); + } + else if(type() == "IP") + { + match_fn = std::mem_fn(&GeneralName::matches_ip); + nam = cert.subject_info("IP"); + } + else + { + return MatchResult::UnknownType; + } + + if(nam.empty()) + { + return MatchResult::NotFound; + } + + bool some = false; + bool all = true; + + for(const std::string& n: nam) + { + bool m = match_fn(this,n); + + some |= m; + all &= m; + } + + if(all) + { + return MatchResult::All; + } + else if(some) + { + return MatchResult::Some; + } + else + { + return MatchResult::None; + } + } + +bool GeneralName::matches_dns(const std::string& nam) const + { + if(nam.size() == name().size()) + { + return nam == name(); + } + else if(name().size() > nam.size()) + { + return false; + } + else // name.size() < nam.size() + { + std::string constr = name().front() == '.' ? name() : "." + name(); + // constr is suffix of nam + return constr == nam.substr(nam.size() - constr.size(),constr.size()); + } + } + +bool GeneralName::matches_dn(const std::string& nam) const + { + std::stringstream ss(nam); + std::stringstream tt(name()); + X509_DN nam_dn, my_dn; + + ss >> nam_dn; + tt >> my_dn; + + auto attr = nam_dn.get_attributes(); + bool ret = true; + int trys = 0; + + for(const std::pair<OID,std::string>& c: my_dn.get_attributes()) + { + auto i = attr.equal_range(c.first); + + if(i.first != i.second) + { + trys += 1; + ret &= i.first->second == c.second; + } + } + + return trys > 0 && ret; + } + +bool GeneralName::matches_ip(const std::string& nam) const + { + u32bit ip = string_to_ipv4(nam); + std::vector<std::string> p = split_on(name(),'/'); + + if(p.size() != 2) + throw Decoding_Error("failed to parse IPv4 address"); + + u32bit net = string_to_ipv4(p.at(0)); + u32bit mask = string_to_ipv4(p.at(1)); + + return (ip & mask) == net; + } + +std::ostream& operator<<(std::ostream& os, const GeneralName& gn) + { + os << gn.type() << ":" << gn.name(); + return os; + } + +GeneralSubtree::GeneralSubtree(const std::string& v) : GeneralSubtree() + { + size_t p0, p1; + size_t min = std::stoull(v, &p0, 10); + size_t max = std::stoull(v.substr(p0 + 1), &p1, 10); + GeneralName gn(v.substr(p0 + p1 + 2)); + + if(p0 > 0 && p1 > 0) + { + m_minimum = min; + m_maximum = max; + m_base = gn; + } + else + { + throw Invalid_Argument("Failed to decode Name Constraint"); + } + } + +void GeneralSubtree::encode_into(class DER_Encoder&) const + { + throw std::runtime_error("General Subtree encoding not implemented"); + } + +void GeneralSubtree::decode_from(class BER_Decoder& ber) + { + ber.start_cons(SEQUENCE) + .decode(m_base) + .decode_optional(m_minimum,ASN1_Tag(0),CONTEXT_SPECIFIC,size_t(0)) + .end_cons(); + + if(m_minimum != 0) + throw Decoding_Error("GeneralSubtree minimum must be 0"); + + m_maximum = std::numeric_limits<std::size_t>::max(); + } + +std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs) + { + os << gs.minimum() << "," << gs.maximum() << "," << gs.base(); + return os; + } +} diff --git a/src/lib/cert/x509/name_constraint.h b/src/lib/cert/x509/name_constraint.h new file mode 100644 index 000000000..345e64ff5 --- /dev/null +++ b/src/lib/cert/x509/name_constraint.h @@ -0,0 +1,132 @@ +/* +* X.509 Name Constraint +* (C) 2015 Kai Michaelis +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_NAME_CONSTRAINT_H__ +#define BOTAN_NAME_CONSTRAINT_H__ + +#include <botan/asn1_obj.h> +#include <ostream> + +namespace Botan { + + class X509_Certificate; + + /** + * @brief X.509 GeneralName Type + * + * Handles parsing GeneralName types in their BER and canonical string + * encoding. Allows matching GeneralNames against each other using + * the rules laid out in the X.509 4.2.1.10 (Name Contraints). + */ + class BOTAN_DLL GeneralName : public ASN1_Object + { + public: + enum MatchResult : int + { + All, + Some, + None, + NotFound, + UnknownType, + }; + + GeneralName() : m_type(), m_name() {} + + /// Constructs a new GeneralName for its string format. + GeneralName(const std::string& s); + + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + /// Type of the name. Can be DN, DNS, IP, RFC822, URI. + const std::string& type() const { return m_type; } + + /// The name as string. Format depends on type. + const std::string& name() const { return m_name; } + + /// Checks whenever a given certificate (partially) matches this name. + MatchResult matches(const X509_Certificate&) const; + + private: + std::string m_type; + std::string m_name; + + bool matches_dns(const std::string&) const; + bool matches_dn(const std::string&) const; + bool matches_ip(const std::string&) const; + }; + + std::ostream& operator<<(std::ostream& os, const GeneralName& gn); + + /** + * @brief A single Name Constraints + * + * THe Name Constraint extension adds a minimum and maximum path + * length to a GeneralName to form a constraint. The length limits + * are currently unused. + */ + class BOTAN_DLL GeneralSubtree : public ASN1_Object + { + public: + GeneralSubtree() : m_base(), m_minimum(0), m_maximum(std::numeric_limits<std::size_t>::max()) + {} + + /// Constructs a new Name Constraint + GeneralSubtree(GeneralName b,size_t min,size_t max) + : m_base(b), m_minimum(min), m_maximum(max) + {} + + /// Constructs a new GeneralSubtree for its string format. + GeneralSubtree(const std::string&); + + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + /// Name + GeneralName base() const { return m_base; } + + // Minimum path length + size_t minimum() const { return m_minimum; } + + // Maximum path length + size_t maximum() const { return m_maximum; } + + private: + GeneralName m_base; + size_t m_minimum; + size_t m_maximum; + }; + + std::ostream& operator<<(std::ostream& os, const GeneralSubtree& gs); + + /** + * @brief Name Constraints + * + * Wraps the Name Constraints associated with a certificate. + */ + class BOTAN_DLL NameConstraints + { + public: + NameConstraints() : m_permitted_subtrees(), m_excluded_subtrees() {} + + NameConstraints(std::vector<GeneralSubtree>&& ps, std::vector<GeneralSubtree>&& es) + : m_permitted_subtrees(ps), m_excluded_subtrees(es) + {} + + /// Permitted names + const std::vector<GeneralSubtree>& permitted() const { return m_permitted_subtrees; } + + /// Excluded names + const std::vector<GeneralSubtree>& excluded() const { return m_excluded_subtrees; } + + private: + std::vector<GeneralSubtree> m_permitted_subtrees; + std::vector<GeneralSubtree> m_excluded_subtrees; + }; +} + +#endif diff --git a/src/lib/cert/x509/x509_ext.cpp b/src/lib/cert/x509/x509_ext.cpp index f3a9a7f1c..47fd909eb 100644 --- a/src/lib/cert/x509/x509_ext.cpp +++ b/src/lib/cert/x509/x509_ext.cpp @@ -13,6 +13,7 @@ #include <botan/charset.h> #include <botan/internal/bit_ops.h> #include <algorithm> +#include <sstream> namespace Botan { @@ -32,6 +33,7 @@ Certificate_Extension* Extensions::get_extension(const OID& oid) X509_EXTENSION("X509v3.ExtendedKeyUsage", Extended_Key_Usage); X509_EXTENSION("X509v3.IssuerAlternativeName", Issuer_Alternative_Name); X509_EXTENSION("X509v3.SubjectAlternativeName", Subject_Alternative_Name); + X509_EXTENSION("X509v3.NameConstraints", Name_Constraints); X509_EXTENSION("X509v3.CertificatePolicies", Certificate_Policies); X509_EXTENSION("X509v3.CRLDistributionPoints", CRL_Distribution_Points); X509_EXTENSION("PKIX.AuthorityInformationAccess", Authority_Information_Access); @@ -166,7 +168,10 @@ void Extensions::contents_to(Data_Store& subject_info, Data_Store& issuer_info) const { for(size_t i = 0; i != m_extensions.size(); ++i) + { m_extensions[i].first->contents_to(subject_info, issuer_info); + subject_info.add(m_extensions[i].first->oid_name() + ".is_critical", (m_extensions[i].second ? 1 : 0)); + } } @@ -430,6 +435,71 @@ void Extended_Key_Usage::contents_to(Data_Store& subject, Data_Store&) const subject.add("X509v3.ExtendedKeyUsage", m_oids[i].as_string()); } +/* +* Encode the extension +*/ +std::vector<byte> Name_Constraints::encode_inner() const + { + throw std::runtime_error("Name_Constraints encoding not implemented"); + } + + +/* +* Decode the extension +*/ +void Name_Constraints::decode_inner(const std::vector<byte>& in) + { + std::vector<GeneralSubtree> permit, exclude; + BER_Decoder ber(in); + BER_Decoder ext = ber.start_cons(SEQUENCE); + BER_Object per = ext.get_next_object(); + + ext.push_back(per); + if(per.type_tag == 0 && per.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + { + ext.decode_list(permit,ASN1_Tag(0),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + if(permit.empty()) + throw Encoding_Error("Empty Name Contraint list"); + } + + BER_Object exc = ext.get_next_object(); + ext.push_back(exc); + if(per.type_tag == 1 && per.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) + { + ext.decode_list(exclude,ASN1_Tag(1),ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)); + if(exclude.empty()) + throw Encoding_Error("Empty Name Contraint list"); + } + + ext.end_cons(); + + if(permit.empty() && exclude.empty()) + throw Encoding_Error("Empty Name Contraint extension"); + + m_name_constraints = NameConstraints(std::move(permit),std::move(exclude)); + } + +/* +* Return a textual representation +*/ +void Name_Constraints::contents_to(Data_Store& subject, Data_Store&) const + { + std::stringstream ss; + + for(const GeneralSubtree& gs: m_name_constraints.permitted()) + { + ss << gs; + subject.add("X509v3.NameConstraints.permitted", ss.str()); + ss.str(std::string()); + } + for(const GeneralSubtree& gs: m_name_constraints.excluded()) + { + ss << gs; + subject.add("X509v3.NameConstraints.excluded", ss.str()); + ss.str(std::string()); + } + } + namespace { /* diff --git a/src/lib/cert/x509/x509_ext.h b/src/lib/cert/x509/x509_ext.h index 8d2dcb52b..ac456b998 100644 --- a/src/lib/cert/x509/x509_ext.h +++ b/src/lib/cert/x509/x509_ext.h @@ -260,6 +260,29 @@ class BOTAN_DLL Extended_Key_Usage final : public Certificate_Extension }; /** +* Name Constraints +*/ +class BOTAN_DLL Name_Constraints : public Certificate_Extension + { + public: + Name_Constraints* copy() const override + { return new Name_Constraints(m_name_constraints); } + + Name_Constraints() {} + Name_Constraints(const NameConstraints &nc) : m_name_constraints(nc) {} + private: + std::string oid_name() const override + { return "X509v3.NameConstraints"; } + + bool should_encode() const override { return true; } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + NameConstraints m_name_constraints; + }; + +/** * Certificate Policies Extension */ class BOTAN_DLL Certificate_Policies final : public Certificate_Extension diff --git a/src/lib/cert/x509/x509cert.cpp b/src/lib/cert/x509/x509cert.cpp index 8d6d9a70a..959cddb53 100644 --- a/src/lib/cert/x509/x509cert.cpp +++ b/src/lib/cert/x509/x509cert.cpp @@ -280,6 +280,14 @@ u32bit X509_Certificate::path_limit() const } /* +* Return if a certificate extension is marked critical +*/ +bool X509_Certificate::is_critical(const std::string& ex_name) const + { + return !!m_subject.get1_u32bit(ex_name + ".is_critical",0); + } + +/* * Return the key usage constraints */ Key_Constraints X509_Certificate::constraints() const @@ -297,6 +305,26 @@ std::vector<std::string> X509_Certificate::ex_constraints() const } /* +* Return the name constraints +*/ +NameConstraints X509_Certificate::name_constraints() const + { + std::vector<GeneralSubtree> permit, exclude; + + for(const std::string& v: m_subject.get("X509v3.NameConstraints.permitted")) + { + permit.push_back(GeneralSubtree(v)); + } + + for(const std::string& v: m_subject.get("X509v3.NameConstraints.excluded")) + { + exclude.push_back(GeneralSubtree(v)); + } + + return NameConstraints(std::move(permit),std::move(exclude)); + } + +/* * Return the list of certificate policies */ std::vector<std::string> X509_Certificate::policies() const @@ -514,6 +542,33 @@ std::string X509_Certificate::to_string() const out << " " << ex_constraints[i] << "\n"; } + NameConstraints name_constraints = this->name_constraints(); + if(!name_constraints.permitted().empty() || + !name_constraints.excluded().empty()) + { + out << "Name Constraints:\n"; + + if(!name_constraints.permitted().empty()) + { + out << " Permit"; + for(auto st: name_constraints.permitted()) + { + out << " " << st.base(); + } + out << "\n"; + } + + if(!name_constraints.excluded().empty()) + { + out << " Exclude"; + for(auto st: name_constraints.excluded()) + { + out << " " << st.base(); + } + out << "\n"; + } + } + if(!ocsp_responder().empty()) out << "OCSP responder " << ocsp_responder() << "\n"; if(!crl_distribution_point().empty()) diff --git a/src/lib/cert/x509/x509cert.h b/src/lib/cert/x509/x509cert.h index 32f2bba9f..54d82b1b4 100644 --- a/src/lib/cert/x509/x509cert.h +++ b/src/lib/cert/x509/x509cert.h @@ -14,6 +14,7 @@ #include <botan/asn1_alt_name.h> #include <botan/datastor.h> #include <botan/key_constraint.h> +#include <botan/name_constraint.h> #include <map> namespace Botan { @@ -64,9 +65,9 @@ class BOTAN_DLL X509_Certificate final : public X509_Object * "X509.Certificate.start", "X509.Certificate.end", * "X509.Certificate.v2.key_id", "X509.Certificate.public_key", * "X509v3.BasicConstraints.path_constraint", - * "X509v3.BasicConstraints.is_ca", "X509v3.ExtendedKeyUsage", - * "X509v3.CertificatePolicies", "X509v3.SubjectKeyIdentifier" or - * "X509.Certificate.serial". + * "X509v3.BasicConstraints.is_ca", "X509v3.NameConstraints", + * "X509v3.ExtendedKeyUsage", "X509v3.CertificatePolicies", + * "X509v3.SubjectKeyIdentifier" or "X509.Certificate.serial". * @return value(s) of the specified parameter */ std::vector<std::string> subject_info(const std::string& name) const; @@ -156,6 +157,12 @@ class BOTAN_DLL X509_Certificate final : public X509_Object u32bit path_limit() const; /** + * Check whenever a given X509 Extension is marked critical in this + * certificate. + */ + bool is_critical(const std::string& ex_name) const; + + /** * Get the key constraints as defined in the KeyUsage extension of this * certificate. * @return key constraints @@ -164,13 +171,19 @@ class BOTAN_DLL X509_Certificate final : public X509_Object /** * Get the key constraints as defined in the ExtendedKeyUsage - * extension of this - * certificate. + * extension of this certificate. * @return key constraints */ std::vector<std::string> ex_constraints() const; /** + * Get the name constraints as defined in the NameConstraints + * extension of this certificate. + * @return name constraints + */ + NameConstraints name_constraints() const; + + /** * Get the policies as defined in the CertificatePolicies extension * of this certificate. * @return certificate policies diff --git a/src/lib/cert/x509/x509path.cpp b/src/lib/cert/x509/x509path.cpp index 71c025280..dd9df6f51 100644 --- a/src/lib/cert/x509/x509path.cpp +++ b/src/lib/cert/x509/x509path.cpp @@ -16,8 +16,6 @@ #include <vector> #include <set> -#include <iostream> - namespace Botan { namespace { @@ -143,6 +141,62 @@ check_chain(const std::vector<X509_Certificate>& cert_path, if(!trusted_hashes.count(subject.hash_used_for_signature())) status.insert(Certificate_Status_Code::UNTRUSTED_HASH); } + + const NameConstraints& name_constr = issuer.name_constraints(); + + if(!name_constr.permitted().empty() || !name_constr.excluded().empty()) + { + if(!issuer.is_CA_cert() || !issuer.is_critical("X509v3.NameConstraints")) + cert_status.at(i).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + + // Check that all subordinate certs pass the name constraint + for(size_t j = 0; j <= i; ++j) + { + if(i == j && at_self_signed_root) + continue; + + bool permitted = name_constr.permitted().empty(); + bool failed = false; + + for(auto c: name_constr.permitted()) + { + switch(c.base().matches(cert_path.at(j))) + { + case GeneralName::MatchResult::NotFound: + case GeneralName::MatchResult::All: + permitted = true; + break; + case GeneralName::MatchResult::UnknownType: + failed = issuer.is_critical("X509v3.NameConstraints"); + permitted = true; + break; + default: + break; + } + } + + for(auto c: name_constr.excluded()) + { + switch(c.base().matches(cert_path.at(j))) + { + case GeneralName::MatchResult::All: + case GeneralName::MatchResult::Some: + failed = true; + break; + case GeneralName::MatchResult::UnknownType: + failed = issuer.is_critical("X509v3.NameConstraints"); + break; + default: + break; + } + } + + if(failed || !permitted) + { + cert_status.at(j).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + } + } + } } for(size_t i = 0; i != cert_path.size() - 1; ++i) @@ -416,6 +470,8 @@ const char* Path_Validation_Result::status_string(Certificate_Status_Code code) return "OCSP bad status"; case Certificate_Status_Code::CERT_NAME_NOMATCH: return "Certificate does not match provided name"; + case Certificate_Status_Code::NAME_CONSTRAINT_ERROR: + return "Certificate does not pass name constraint"; case Certificate_Status_Code::CERT_IS_REVOKED: return "Certificate is revoked"; diff --git a/src/tests/data/name_constraint/Invalid_DN_Name_Constraint.crt b/src/tests/data/name_constraint/Invalid_DN_Name_Constraint.crt new file mode 100644 index 000000000..7c8c0aabc --- /dev/null +++ b/src/tests/data/name_constraint/Invalid_DN_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7DCCAdSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IEROIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDMxNjI1MDBaFw0xNzAzMDMxNjI1 +MDBaMFExCzAJBgNVBAYTAkRFMQwwCgYDVQQIEwNOUlcxDzANBgNVBAcTBkJvY2h1 +bTEjMCEGA1UEAxMaSW52YWxpZCBETiBOYW1lIENvbnN0cmFpbnQwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsHLRRWXgXFv0rpmrqjELvS8jrCjObZkML +uWHpgJIev59IEXcf/WXQyMNSzSudFB2JKNatyEUxKshD5eGSHYTb5qNPfEBYEMC3 +GCp+yXA3xKd1K7hnKOpdApTKN305K1ubZqkrY9SH2EdtMMfTqPIqTmG8VWtCtlOp +svK6v8uwI17QdlC0pi39bkR/z2EZfZPkEHgB0rqK37FaWBgLoTsTEb0PL1aZkEYO +Q8Wyvz7VieakIhDk/QMX22AEp8ig1LI99FvS8o4VOAYgjjCzIKWEos+p/hCYKrCe +EpZ5GMI0O/13PCDaXRNywo20fhrV3Byzg57WSfsewnd/SztiY5t1AgMBAAEwDQYJ +KoZIhvcNAQEFBQADggEBABZCqDTZdTy8KgOvpZCUaST52iAHIGFkqhi3XYF1gaj2 +ADgMzonuttj+DAiYzS2wMts+TdrHFVuytmMsbIoWNXtRq/CAoQIg/tmpeb7AB5iS +LGc5nxf+9nnCW276XmmA2cA8GCfbL0WDPZrfHRsw8jEAtyOP4bQEO3iqNcnQBK63 +nP0fdCfqM9ImN0eVhxA04IkP8d6utC1CoIlDyqqike7+2o+PXCrWlmb/WeZD+Hym +4eJe8y5Q5YJ6G5F03Z1zuU5SVLKYJImdx1qiTXqUX2qBh4NKmVFgAImJOrbtj1qk +//pH1Fb3w5xK9akaGcXYTTDUDZu1HM06LbAd0pwlRBI= +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Invalid_Email_Name_Constraint.crt b/src/tests/data/name_constraint/Invalid_Email_Name_Constraint.crt new file mode 100644 index 000000000..c7083a3dd --- /dev/null +++ b/src/tests/data/name_constraint/Invalid_Email_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBAjANBgkqhkiG9w0BAQUFADAlMSMwIQYDVQQDExpSb290 +IEVtYWlsIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDMxNjIxMDBaFw0xNzAzMDMx +NjIxMDBaMCgxJjAkBgNVBAMTHUludmFsaWQgRW1haWwgTmFtZSBDb25zdHJhaW50 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApLQNwVt6i6aYP67ZyaAH +u/uqKg7P6K9xocb/aSlnfj3A4ee5nmxi3xoCghuXKV4lSXIE68Rppak81dCG9Bou +x8S5K2DZ20AX11Pk5ulMMZGaQdOPZEBbPIxSqPdWKHwSLtkLxw7F+KN1MdB5lCf0 +YbXn4niqTF8eGrGEcfatYDFOYI5NVwlRHMqPoyv9JaJC68a/njhFV33DM/J1cTvM +gIMbF8nnUKqfzyGwP1q5A2ep9DmH6LpOzpEzcp3d7DPCTAK7q64vSr/uOALt6Vsg +LAHAOmberdVB/GUBCqa3F/eDo8s4lw+kdq5ow72hM1jSP7LeRb2OHcUJa3bl1cKj +cQIDAQABox8wHTAbBgNVHREEFDASgRB0ZXN0QGV4YW1wbGUubmV0MA0GCSqGSIb3 +DQEBBQUAA4IBAQBit9P5NiMg8jjBtoXTBwUHBFA/B/KdxXx2AFsS6rx2xb5m09WG +joFvv2le+GixwKUHAyseTtYshO+s1HiCSHcnp7j5RfcjMdraJWrACKzmEqA6J+KM +mSa4opop91JFEY7ydnNqGf9biJ1dxiAs8XQ+ldbMuFxYc5CrNG8uoNvWGFZRegGS +rR3pLEHeG5waGVExBMzMSIA3k4qIeh1JsiXIorQGwLfSQEPkCVIbRw9dpUOWLmDK +SIlnL86z+OZDQDScOuLdh9j5mNWNIS9GH+VH7H+f7l8F0y96IbFumzby8DdTZiXf +MuLW1n9aD7h787KRc6ExRx4qw4uWMl0tXSmd +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Invalid_IP_Name_Constraint.crt b/src/tests/data/name_constraint/Invalid_IP_Name_Constraint.crt new file mode 100644 index 000000000..fad54841e --- /dev/null +++ b/src/tests/data/name_constraint/Invalid_IP_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC1TCCAb2gAwIBAgIBAzANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IElQIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDkxMDUzMDBaFw0xNzAzMDkxMDUz +MDBaMCUxIzAhBgNVBAMTGkludmFsaWQgSVAgTmFtZSBDb25zdHJhaW50MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqPSMstg2PL5m4NlT2sVweNGmpRl1 +2qZaV8VF01WweYdvE0DRw1aNzBzkbeIpbrVdvgxZIPeEOpusQQJPUtBuemMuKoNt +O2zCEheILKwsESq3YhrXvpgs2679nK62Dfr4PbT1KUfsP4Kruf2cAuHsRO0hhT4i +tZUcU6JCw5KDPihk+joALcZ4qdeKuAZHSopPlg5LFoRKb2mXDfm6j25bngMiysew +XdCXeYCoL9+ROthrgz4H3t5mEcSAVS8dwqfjUIZznVlUwYfw5ThqIHJYWZHOOozr +sgTicfiFKSje/XIGoHmgdrT6HI5ZOufKtXMgpco1vjdnnRuxG99ilYboPQIDAQAB +oxMwETAPBgNVHREECDAGhwQKAAEDMA0GCSqGSIb3DQEBBQUAA4IBAQAi6yVuppWf +kPQNWRjW45/6AB/yH9HPN46saVHHuGREaH4kxhmzrEBjS1CdbinvbtHm5SfN3qau +eUWwabGvgvSBqxRxhV7HyagZTP1rfMDqjkQSICjOM24NJRBn+OxC87kxwtbi8z4d +zcL4bULErtzZS70A8xuEemEt6LEBSOkrsDfN3sN2UJWQ5ifAgLELXr05FVHyt6Rf +8xzbu5uvO8nUfRNCYEkrzSAU6oREwYpbJkimpoLiykERRNd6hJmd2xxupBKL6Cf0 +vXk/uaVeiHjchk4cFwpDPgK7PQJlWUb9HVEGjJQY/7STcVMl0Rk9NDmta6kW57q1 +Y6gSmXgGZBt1 +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Root_DNS_Name_Constraint.crt b/src/tests/data/name_constraint/Root_DNS_Name_Constraint.crt new file mode 100644 index 000000000..dd57ded87 --- /dev/null +++ b/src/tests/data/name_constraint/Root_DNS_Name_Constraint.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIBATANBgkqhkiG9w0BAQUFADAjMSEwHwYDVQQDExhSb290 +IEROUyBOYW1lIENvbnN0cmFpbnQwHhcNMTYwMzAzMTYzMzAwWhcNMjYwMzAzMTYz +MzAwWjAjMSEwHwYDVQQDExhSb290IEROUyBOYW1lIENvbnN0cmFpbnQwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCnc8QujqwM6w/QoC8fAEIXrWIf6+IV +WznKMPwCqogoq1dw+9Url/4yhUqFNWZiPf7h8Cxm2oqLesq0LJ4IQcTrqwDfflZw +dHiw9Tj6woks5YEq8k4cxDOVjJPftPOL+drVCMDQnpRctEtcNcbOmNFsCrWSGl7t +bBvhWjARAfQvCfMTILkhJj6Bh3wHdxbxzy5m4rqQuG+gyAzEQBIPbhIYkrjhaFdx +FUnPmk2uhYXDmpOuln2zuE1BKi/HqG1iytRgm0DfuayrqPKHustUhdcOQdJnxy/q +3wthcsP6i8YX5eeV332BDXPVijWHJ9AHilGITYfRssUwyoI+sxEZB035AgMBAAGj +gZIwgY8wDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUcvuTsCu3ovZJQRVo6494 +7kehuVUwCwYDVR0PBAQDAgEGMB0GA1UdHgEB/wQTMBGhDzANggtleGFtcGxlLmNv +bTARBglghkgBhvhCAQEEBAMCAAcwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZp +Y2F0ZTANBgkqhkiG9w0BAQUFAAOCAQEAB7JEZgAGO3jLaXWFUdV9k1nXvngKR/yV +AKvr1KIl8f7azR6khnnIY/UpbYQJHSNCKt3J+DEmWzrI8/ayfDW1Ty7/2u+IT0iw +P44TOFIFSN7q4x1nLiHN1PFZvNc8ENHpqSubqF2ooGWIakSbO1LrmHqVgPMkcMJk +5tUIcwmlCMOdFvy6ejVjw/l7aawAG+sOLTzjheYeKIngilejPthBhMxsniqVlzCY +5dTV+jplLzOqOANSyhzhlu0cywJbhifG+Vzq59raPzzk9tXEXKsi3qO0B7J+5Y9f +fwIHNf8ZZ/4ODDBYS7BHAemgXcXrVMtJfwHQCjracE6RYx5NpzRU1g== +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Root_DN_Name_Constraint.crt b/src/tests/data/name_constraint/Root_DN_Name_Constraint.crt new file mode 100644 index 000000000..7dc1c4c72 --- /dev/null +++ b/src/tests/data/name_constraint/Root_DN_Name_Constraint.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDVjCCAj6gAwIBAgIBATANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IEROIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDMxNjIzMDBaFw0yNjAzMDMxNjIz +MDBaMCIxIDAeBgNVBAMTF1Jvb3QgRE4gTmFtZSBDb25zdHJhaW50MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbIHfxTBz1AVs6kMHeeOeiNy+RC4EDuT +ArRrD0NpATiYOM+O0MTwk+pJtlCge77dTEnXEhHfXpYV5MLDvPx4B95v49BGshLn +2GOUcMuP4rextDb12hr/5oBKksFiWgBuuKc+XwDD8yh7i8KbOtJelWiRg7ge97sr +Hw6eiPmKlDDmTN39aN5O68YIJfkpDIr08ncLWCC6WRpjMU/eRdYMce6LIaB+bHEx +P5uwQD10e6XCnOVHaOc3kTmhbivIugrrE7VS4lq+t42amSDf9V4NWZ7d65dvIzGK +DEics4ZGaizeN3BYMMNfPUfjJ0iqTbPYi+29gkE5/AQKVs1b4b4G/QIDAQABo4GW +MIGTMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOYXX2m7K9gJL3EE2CtZdlut +kC6cMAsGA1UdDwQEAwIBBjAhBgNVHR4BAf8EFzAVoRMwEaQPMA0xCzAJBgNVBAYT +AkRFMBEGCWCGSAGG+EIBAQQEAwIABzAeBglghkgBhvhCAQ0EERYPeGNhIGNlcnRp +ZmljYXRlMA0GCSqGSIb3DQEBBQUAA4IBAQALCFfBog6/kTwd1gGBnVhxJ9eKzzVS +NyOl0T1SVjVHRul9AK8kP/8pyw7GtZE/hdAwSjyYbO6VaOT6muVtiAy9TQrGPSpA +LPbk9RVLEn0vqnUBUkE3kX2T9WVM4jJqh7CsvO6OPTczCf1EqiJmmNhp91jCAPgX +C375wkZEEI5thOZnblD5zDWpM+tp3RiIiFUZiZ1IT8ALgT3elFnqNePuOYZ/daaK ++ehnF0gpr0KoLkgZ+HRoXoBIK0rz1TNCDzIfnc2Lx2G/SftBtuoVSp3TgPJO/7SP +X9zsKBv3St40ZQ8ZUi3DqTwOCFYm6ODbESJPR3uJQhil1XVCr1GlGBYl +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Root_Email_Name_Constraint.crt b/src/tests/data/name_constraint/Root_Email_Name_Constraint.crt new file mode 100644 index 000000000..d1181a837 --- /dev/null +++ b/src/tests/data/name_constraint/Root_Email_Name_Constraint.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUTCCAjmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAlMSMwIQYDVQQDExpSb290 +IEVtYWlsIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDMxNjIxMDBaFw0yNjAzMDMx +NjIxMDBaMCUxIzAhBgNVBAMTGlJvb3QgRW1haWwgTmFtZSBDb25zdHJhaW50MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fLWMKDgTjWtsla75PAa4yvB +4LMkiaYfXdKSAE3MNgoY64o1LhajQ0/LrJ8USY/2GLv9vVIufBqFFHwHTiUYiyAM +odLGphQ/YitGE/ZAkef0NKkzRh5InCQSopP7SntZ//QoqsaJqKgdZl6UN+5eP9SA +o9AbTDhpZxYmEWpT8Sk+5igBvepr7mBQ7ZAnJnTeGJE/IjrjCx7C2pInV2FxJNph +3Ou5LpqH3SBFOMnzootP6AWcPIfVgIY9CxJdITReQ7o3vFs/pn08DONV08eH4fDd +8xJWjNk7GfhhYEKfFP1Fk5CXAwlurPoydVuyU53SA3ICTpnstbUnJGAedUhBnwID +AQABo4GLMIGIMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFL5lAY1YFiV6918j +DM0v+NcRNuV/MAsGA1UdDwQEAwIBBjAWBgNVHR4BAf8EDDAKoAgwBoEELmNvbTAR +BglghkgBhvhCAQEEBAMCAAcwHgYJYIZIAYb4QgENBBEWD3hjYSBjZXJ0aWZpY2F0 +ZTANBgkqhkiG9w0BAQUFAAOCAQEA2QfSxzwYMYdSzs4Ntsda3mhroewzjf//LYbg +DxXYw6QocnMkQnp3JIokaoTlcvh0FnqlxwVeXI7DYssNFBeEM9tkl9/KTqNWzZxq +R3Ui5jpW3wuEnIJLN3Z3xnhH1dTXKlnuYc28aQuwmMySemJ6PD06HbntJRcu8qCY +XbEHlwRoueGtFsRyHylOcdFrSS4XWKcSZ0O4dd2GdDYy1oSo5B/7uad/OBV7lAX9 +iww0t64k+grM/mM+OgBJTj9cQCR0CP0cAVsVdSCw0y1asvoc7ainpEuYWT7H/3/d +uG1lewFoWdDXkSkppwLO1h2wvkffBmzPgnjfkZ/ByLGeBLk2og== +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Root_IP_Name_Constraint.crt b/src/tests/data/name_constraint/Root_IP_Name_Constraint.crt new file mode 100644 index 000000000..580ce508c --- /dev/null +++ b/src/tests/data/name_constraint/Root_IP_Name_Constraint.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIBATANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IElQIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDkxMDQ5MDBaFw0yNjAzMDkxMDQ5 +MDBaMCIxIDAeBgNVBAMTF1Jvb3QgSVAgTmFtZSBDb25zdHJhaW50MIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0qbHmORvktt3Z3/wu+RgO3bSqIb1tIKy +QVOCAFz52gpFoI1PL7Sqs+O6rz9iQN57ntIpBw9WfydMk98UWFOsM5ICLci4J5jz +gm0Go8clJRe/gL1q3ORRgM8CPAdt8eZrvZzO3SM1rhUC5QLjzzdCs+xzBmiJRzq0 +hyiQZl6FSlQEwrGuBfPKFuRA56zYyXISLftm2wHwXK+9sF/sErghaFUUDIGfalfs +6TnsdvghrTlkcTfHg1ftsXq8YnxuCS+yWuKhbiMcoj7eNaGmc4/qY4oyxMkciprN +Jir4eowwSklG7RR6tEz32K2yfUaOlx206KtT9r4AAwaNX7VX8RZ5qwIDAQABo4GP +MIGMMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHJ0ggZC8ZUlgwPyCQZHM7sQ +DcE9MAsGA1UdDwQEAwIBBjAaBgNVHR4BAf8EEDAOoAwwCocIwKgAAP//AAAwEQYJ +YIZIAYb4QgEBBAQDAgAHMB4GCWCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUw +DQYJKoZIhvcNAQEFBQADggEBAL2zm2nBuKk/OH32bdzAy8TILh+b2ZiiGCWy+7QQ +CCfRyKpCb6zoMq6uTqlFmXoQ5iUFih51fleP3qeQ4H3mMqIqoThPA1suQzgha/O8 +jO6TIFYIo3+XTSfleGNpNUxfm8SqsZc0K6huerZZJW8e89dMddHxFa43T/RLKGpY +P6VIu0JIweavOZTsUcd0JAqCSEnlyTJF3o5hP3thfbZMUZxgXM9sV4ucVBUE/o+U +q3JMWLkE5OxrRG37z8+5yIOZi7Y8uOKncueUvyTzyHPp9S5SUombIOg/K8NoaCEt +HkqILLcDJAihb7/odRS35Zw8ZPDVHCL0LtS1c2zEVnXbETc= +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Valid_DNS_Name_Constraint.crt b/src/tests/data/name_constraint/Valid_DNS_Name_Constraint.crt new file mode 100644 index 000000000..77d30879a --- /dev/null +++ b/src/tests/data/name_constraint/Valid_DNS_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3TCCAcWgAwIBAgIBAzANBgkqhkiG9w0BAQUFADAjMSEwHwYDVQQDExhSb290 +IEROUyBOYW1lIENvbnN0cmFpbnQwHhcNMTYwMzAzMTY0MzAwWhcNMTcwMzAzMTY0 +MzAwWjAkMSIwIAYDVQQDExlWYWxpZCBETlMgTmFtZSBDb25zdHJhaW50MIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxPaa6o2mZoq85YKSUYWFP9Ttl8Ux +X+hDEKzvVS9+V0dbdAE0WHLxZnq22UAVsQ/a2RlYcXGMGOyztJ+zTKqjyeSadNat +gIyh0BD3B2xhKxz1Zf2ZixVWZcwu5t/ZcboIF4Q8IKgiEzPUjcWRErk88ldMh7Zt +9vIZMcGNnlzCWeuk7I91WoS9qs5mLXRecL/SrGm2gS+ByhirNNpSlPMC+4hvFShE +/Z82BEM2gqR6YOsfGjlz65DBqAfME8Pd/IWuHA9sb1t6s0/dTCYQ5RWoCkKBHe9Q +CWtBK7MezgYcJqFFzPlMjMS1K/z51RXBHOetxqsommSJiKg189NKX0xYhwIDAQAB +oxswGTAXBgNVHREEEDAOggxhZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADggEB +AErxPj6k5vksK5msdV+0dCFr3j7L/qCBU5vAwuGQF+qW7P/3tG2GKbsTtDW64fDn +coWDA3P/LU9Rat4qh36VGVOlAOfGLxfA6QbFeGpIj9oQ+LLQrcWovELGaQoXMJly +r4VRpCzoe4B2xDp1ivJo5tprwmskRiL1kRkVauQ9tlCn1b0EyDfr2iX4CEZESlDm +my7BVAM6zOGBMs76R8mobP8YtB7zRsC5EVuvDz0j0YDfPKTedMKtP1Po+sYfNmHy +4EBgYjdh83zOzUXhG4qxaAn7LlnEjzrI+b22ouKXucXShNeEtQdBa6QSAWlAyqyS +MxdsIT7d9oSqYMIBvWHx89I= +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Valid_DN_Name_Constraint.crt b/src/tests/data/name_constraint/Valid_DN_Name_Constraint.crt new file mode 100644 index 000000000..c3575b376 --- /dev/null +++ b/src/tests/data/name_constraint/Valid_DN_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdKgAwIBAgIBAjANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IEROIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDMxNjI0MDBaFw0xNzAzMDMxNjI0 +MDBaME8xCzAJBgNVBAYTAlVLMQwwCgYDVQQIEwNYWFgxDzANBgNVBAcTBkxvbmRv +bjEhMB8GA1UEAxMYVmFsaWQgRE4gTmFtZSBDb25zdHJhaW50MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEApu0vAiLdowRssVHCzK31e3A3vso8wECeBY/1 +esASJqMjusWjxPq9tp002KT+1CAYD0Du6I6KPjAXUp38AXglQTcA/JjL3LTQrGXw +DCwL1vzK6WzJew6beQnyskscbAQ+iPxzsWn7Nb9fCUQF5fSoZBVP06KEh4Q3dgxb +feYbGQC3cZIR93YHUm2wiO27mCE9xx7xwqIGux+V/Kzt4+tsUpduJn/tPGJVUq9n +oCmSb8rW9B0pbtwXt1QmxjShBGodXefExY8JAmkNmOLxARCgddyK0Xmoyl7Teo+L +BBtosdV23VNe3L+oQi/OAb1pn82u1hOgbQhttUyzlungnsWjfwIDAQABMA0GCSqG +SIb3DQEBBQUAA4IBAQBwWrY+5e+tjYokgNpWZHV3buxqOt2CAjN7FvPcd6adJeDV +GFcBjCGX2qmh1AvqYXliBZTl9rh406Wfz7ssBAzPrxlgyAPInSCfrAbPIH+wpx2G +DR2xNp+uybtIPXMH8LRSGuRZIkaWAvFTKtJMDq96xXUt0iPZJ7gUDS26QQnTFKqz +/ctGxQgno7R+0/8OT/FjwRV2zesB9PI1vJA2Vo082cPyLrSnc4B1/awJy1GGnSyr +XwCyrwYVU17fjhyjYRpIWF4W9WGRbzSOCCRZvxtxPvTpMeC83hDr0i5ZzgjNrxjg +gwheK0rKj14494bf3S3WHQBsFKuoQ/2/kNbzr/OW +-----END CERTIFICATE----- diff --git a/src/tests/data/name_constraint/Valid_IP_Name_Constraint.crt b/src/tests/data/name_constraint/Valid_IP_Name_Constraint.crt new file mode 100644 index 000000000..76a461ec4 --- /dev/null +++ b/src/tests/data/name_constraint/Valid_IP_Name_Constraint.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC0zCCAbugAwIBAgIBAjANBgkqhkiG9w0BAQUFADAiMSAwHgYDVQQDExdSb290 +IElQIE5hbWUgQ29uc3RyYWludDAeFw0xNjAzMDkxMDUyMDBaFw0xNzAzMDkxMDUy +MDBaMCMxITAfBgNVBAMTGFZhbGlkIElQIE5hbWUgQ29uc3RyYWludDCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBALYPp5KI+Bhr+jJwg33UWg1Pdr8hbBCg +AWWyulQW++LlmjnlhGNGOTBW8L4p4rCBbBJ5EHgsLdOpm24fFw4G/yj4cakQKZyf +1eIgvJ5nHmFSTJH2TNWZppeRCK2zbFa3ZaBuiOoyNPVsFii35NLVNiHy8xjRDerw +SjkB9PIxwdB5/jZiAYU5FPE8Y1zsaIXOIC+RaPygdt/qwnsDrqqJLQDCuN7WLeYO +zxZfs306z1FT7uZOfOEzfFrqS9ZiQA5ZhWc38IhmkGDdjJlZtKb6abLdDHR/QjWH +upnm/wJ+w2AtBfhIU1aLHSduX8MVvJRRSkAl68o6HJUKRayWA+5t8ksCAwEAAaMT +MBEwDwYDVR0RBAgwBocEwKgBATANBgkqhkiG9w0BAQUFAAOCAQEAa4ZrteRtAghe +Lvwr1hgg+MKG3U3+1kq9VWbM6TbjcB6YuWWbWD/pPeMeq3k8+5xDw7eTWLkYyyAL +nyFSKUHhYppabmkvtxmYpwIzqdhrm7a3Ej+3FDwAjvjgB2DoT60oQ6TB6P1do43O +LcnAIDOmuknAml5wz8jndNHGcYPy2oJLq0lzWjVxmdhF3KbfSTa50yj9CeXkLD1C +Dvf53AVpcsQXxI92omp+OFvx5d7uc8iIE2KD2d0gKGw0vZPQsdA0VMDwTxcSNbeZ +KlMIV0lhRVLX41vjU9J+Ax7Izt4EymoMD8UWqI/w1Hv2RwHvy3IGNOjPsMVCs06A +2dWXjqwbWg== +-----END CERTIFICATE----- diff --git a/src/tests/data/x509test/expected.txt b/src/tests/data/x509test/expected.txt index 67e2937eb..23cc9daf1 100644 --- a/src/tests/data/x509test/expected.txt +++ b/src/tests/data/x509test/expected.txt @@ -10,6 +10,10 @@ InvalidKeyUsage.pem:Invalid usage InvalidName.pem:Certificate does not match provided name InvalidNameAltName.pem:Certificate does not match provided name InvalidNameAltNameWithSubj.pem:Certificate does not match provided name +InvalidNameConstraintExclude.pem:Certificate does not pass name constraint +InvalidNameConstraintPermit.pem:Certificate does not pass name constraint +InvalidNameConstraintPermitRight.pem:Certificate does not pass name constraint +InvalidNameConstraintPermitThenExclude.pem:Certificate does not pass name constraint InvalidNotAfter.pem:Certificate has expired InvalidNotAfterChained.pem:Certificate has expired InvalidSelfSign.pem:Cannot establish trust @@ -28,16 +32,10 @@ MissingIntCAExtensions.pem:CA certificate not allowed to issue certs ValidAltName.pem:Verified ValidCert.pem:Verified ValidChained.pem:Verified +ValidNameConstraint.pem:Verified ValidIntCALen.pem:Verified ValidWildcard.pem:Verified # Need to fix date settings in x509test and regen #InvalidNotBefore.pem:Certificate is not yet valid #InvalidNotBeforeChained.pem:Certificate is not yet valid - -# Missing name constraints -InvalidNameConstraintExclude.pem:Certificate issuer not found -InvalidNameConstraintPermit.pem:Certificate issuer not found -InvalidNameConstraintPermitRight.pem:Certificate issuer not found -InvalidNameConstraintPermitThenExclude.pem:Certificate issuer not found -ValidNameConstraint.pem:Certificate issuer not found diff --git a/src/tests/test_name_constraint.cpp b/src/tests/test_name_constraint.cpp new file mode 100644 index 000000000..01bdfc3ef --- /dev/null +++ b/src/tests/test_name_constraint.cpp @@ -0,0 +1,96 @@ +/* +* (C) 2015,2016 Kai Michaelis +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "tests.h" + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + #include <botan/x509path.h> + #include <botan/internal/filesystem.h> +#endif + +#include <algorithm> +#include <fstream> +#include <iomanip> +#include <string> +#include <vector> +#include <map> +#include <cstdlib> + +namespace Botan_Tests { + +namespace { + +#if defined(BOTAN_HAS_X509_CERTIFICATES) + +class Name_Constraint_Tests : public Test + { + public: + std::vector<Test::Result> run() override + { + const std::vector<std::tuple<std::string,std::string,std::string,std::string>> test_cases = { + std::make_tuple( + "Root_Email_Name_Constraint.crt", + "Invalid_Email_Name_Constraint.crt", + "Invalid Email Name Constraint", + "Certificate does not pass name constraint"), + std::make_tuple( + "Root_DN_Name_Constraint.crt", + "Invalid_DN_Name_Constraint.crt", + "Invalid DN Name Constraint", + "Certificate does not pass name constraint"), + std::make_tuple( + "Root_DN_Name_Constraint.crt", + "Valid_DN_Name_Constraint.crt", + "Valid DN Name Constraint", + "Verified"), + std::make_tuple( + "Root_DNS_Name_Constraint.crt", + "Valid_DNS_Name_Constraint.crt", + "aexample.com", + "Verified"), + std::make_tuple( + "Root_IP_Name_Constraint.crt", + "Valid_IP_Name_Constraint.crt", + "Valid IP Name Constraint", + "Verified"), + std::make_tuple( + "Root_IP_Name_Constraint.crt", + "Invalid_IP_Name_Constraint.crt", + "Invalid IP Name Constraint", + "Certificate does not pass name constraint"), + }; + std::vector<Test::Result> results; + const Botan::Path_Validation_Restrictions default_restrictions; + + for(const auto& t: test_cases) + { + Botan::X509_Certificate root(Test::data_file("name_constraint/" + std::get<0>(t))); + Botan::X509_Certificate sub(Test::data_file("name_constraint/" + std::get<1>(t))); + Botan::Certificate_Store_In_Memory trusted; + Test::Result result("X509v3 Name Constraints: " + std::get<1>(t)); + + trusted.add_certificate(root); + Botan::Path_Validation_Result path_result = Botan::x509_path_validate( + sub, default_restrictions, trusted, std::get<2>(t), Botan::Usage_Type::TLS_SERVER_AUTH); + + if(path_result.successful_validation() && path_result.trust_root() != root) + path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST); + + result.test_eq("validation result", path_result.result_string(), std::get<3>(t)); + results.push_back(result); + } + + return results; + } + }; + +BOTAN_REGISTER_TEST("x509_path_name_constraint", Name_Constraint_Tests); + +#endif + +} + +} |