aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorKai Michaelis <[email protected]>2016-01-07 13:50:16 +0100
committerKai Michaelis <[email protected]>2016-03-10 11:29:29 +0100
commit205317fbc4b4cedcb5c1a0413d62ecdb64220570 (patch)
treeb88b1e0c51be7b1aceb67b80a7dec08534c78c35 /src/lib
parent0a9505a067313e0e1b9099873642e07ad9fee52f (diff)
X.509 Name Constraints
Diffstat (limited to 'src/lib')
-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
11 files changed, 722 insertions, 16 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";