aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/asn1/oid_lookup/default.cpp1
-rw-r--r--src/lib/asn1/x509_dn.cpp103
-rw-r--r--src/lib/asn1/x509_dn.h1
-rw-r--r--src/lib/cert/x509/cert_status.h1
-rw-r--r--src/lib/cert/x509/name_constraint.cpp269
-rw-r--r--src/lib/cert/x509/name_constraint.h132
-rw-r--r--src/lib/cert/x509/x509_ext.cpp70
-rw-r--r--src/lib/cert/x509/x509_ext.h23
-rw-r--r--src/lib/cert/x509/x509cert.cpp55
-rw-r--r--src/lib/cert/x509/x509cert.h23
-rw-r--r--src/lib/cert/x509/x509path.cpp60
-rw-r--r--src/tests/data/name_constraint/Invalid_DN_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/Invalid_Email_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/Invalid_IP_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/Root_DNS_Name_Constraint.crt20
-rw-r--r--src/tests/data/name_constraint/Root_DN_Name_Constraint.crt20
-rw-r--r--src/tests/data/name_constraint/Root_Email_Name_Constraint.crt20
-rw-r--r--src/tests/data/name_constraint/Root_IP_Name_Constraint.crt20
-rw-r--r--src/tests/data/name_constraint/Valid_DNS_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/Valid_DN_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/Valid_IP_Name_Constraint.crt18
-rw-r--r--src/tests/data/name_constraint/testcases.xdbbin0 -> 26476 bytes
-rw-r--r--src/tests/data/x509test/expected.txt12
-rw-r--r--src/tests/test_name_constraint.cpp96
24 files changed, 1011 insertions, 23 deletions
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 a86cc6417..dafe72f51 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..d763076cb
--- /dev/null
+++ b/src/lib/cert/x509/name_constraint.cpp
@@ -0,0 +1,269 @@
+/*
+* 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 std::runtime_error("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
+ {
+ throw Decoding_Error("Found unknown GeneralName type");
+ }
+ }
+
+GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) const
+ {
+ if(type() == "DNS" || type() == "DN" || type() == "IP")
+ {
+ 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")
+ {
+ std::stringstream ss;
+
+ match_fn = std::mem_fn(&GeneralName::matches_dn);
+ 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
+ {
+ throw Unsupported_Argument("Unsupported Name Constraint");
+ }
+
+ 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;
+ }
+ }
+ else
+ {
+ return MatchResult::UnknownType;
+ }
+ }
+
+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 // nam.size() > name().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 = stoull(v,&p0,10);
+ size_t max = 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 b4b918529..765ad1786 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);
@@ -160,7 +162,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));
+ }
}
/*
@@ -436,6 +441,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 e9e718014..64d290fc2 100644
--- a/src/lib/cert/x509/x509_ext.h
+++ b/src/lib/cert/x509/x509_ext.h
@@ -256,6 +256,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 cb24a7a03..73aa02cf3 100644
--- a/src/lib/cert/x509/x509cert.cpp
+++ b/src/lib/cert/x509/x509cert.cpp
@@ -279,6 +279,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
@@ -296,6 +304,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
@@ -508,6 +536,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 d5784f427..aa910f665 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..df286cfd7 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(int j = i; j >= 0; 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/name_constraint/testcases.xdb b/src/tests/data/name_constraint/testcases.xdb
new file mode 100644
index 000000000..03604785d
--- /dev/null
+++ b/src/tests/data/name_constraint/testcases.xdb
Binary files differ
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
+
+}
+
+}