diff options
-rw-r--r-- | src/lib/cert/x509/cert_status.h | 2 | ||||
-rw-r--r-- | src/lib/cert/x509/crl_ent.cpp | 1 | ||||
-rw-r--r-- | src/lib/cert/x509/crl_ent.h | 3 | ||||
-rw-r--r-- | src/lib/cert/x509/x509_crl.h | 2 | ||||
-rw-r--r-- | src/lib/cert/x509/x509_ext.cpp | 105 | ||||
-rw-r--r-- | src/lib/cert/x509/x509_ext.h | 81 | ||||
-rw-r--r-- | src/lib/cert/x509/x509cert.cpp | 51 | ||||
-rw-r--r-- | src/lib/cert/x509/x509cert.h | 14 | ||||
-rw-r--r-- | src/lib/cert/x509/x509path.cpp | 60 |
9 files changed, 241 insertions, 78 deletions
diff --git a/src/lib/cert/x509/cert_status.h b/src/lib/cert/x509/cert_status.h index 6e8635237..52b65fb57 100644 --- a/src/lib/cert/x509/cert_status.h +++ b/src/lib/cert/x509/cert_status.h @@ -47,6 +47,8 @@ enum class Certificate_Status_Code { CERT_NAME_NOMATCH, + UNKNOWN_CRITICAL_EXTENSION, + // Hard failures CERT_IS_REVOKED = 5000, CRL_BAD_SIGNATURE, diff --git a/src/lib/cert/x509/crl_ent.cpp b/src/lib/cert/x509/crl_ent.cpp index d6923f714..7074f0609 100644 --- a/src/lib/cert/x509/crl_ent.cpp +++ b/src/lib/cert/x509/crl_ent.cpp @@ -6,6 +6,7 @@ */ #include <botan/crl_ent.h> +#include <botan/x509cert.h> #include <botan/x509_ext.h> #include <botan/der_enc.h> #include <botan/ber_dec.h> diff --git a/src/lib/cert/x509/crl_ent.h b/src/lib/cert/x509/crl_ent.h index 11ab34365..4be508812 100644 --- a/src/lib/cert/x509/crl_ent.h +++ b/src/lib/cert/x509/crl_ent.h @@ -8,11 +8,12 @@ #ifndef BOTAN_CRL_ENTRY_H__ #define BOTAN_CRL_ENTRY_H__ -#include <botan/x509cert.h> #include <botan/asn1_time.h> namespace Botan { +class X509_Certificate; + /** * X.509v2 CRL Reason Code. */ diff --git a/src/lib/cert/x509/x509_crl.h b/src/lib/cert/x509/x509_crl.h index 29057e944..dab4d5153 100644 --- a/src/lib/cert/x509/x509_crl.h +++ b/src/lib/cert/x509/x509_crl.h @@ -9,7 +9,9 @@ #define BOTAN_X509_CRL_H__ #include <botan/x509_obj.h> +#include <botan/x509_dn.h> #include <botan/crl_ent.h> +#include <botan/datastor.h> #include <vector> namespace Botan { diff --git a/src/lib/cert/x509/x509_ext.cpp b/src/lib/cert/x509/x509_ext.cpp index 47fd909eb..b54c82b87 100644 --- a/src/lib/cert/x509/x509_ext.cpp +++ b/src/lib/cert/x509/x509_ext.cpp @@ -6,6 +6,7 @@ */ #include <botan/x509_ext.h> +#include <botan/x509cert.h> #include <botan/sha160.h> #include <botan/der_enc.h> #include <botan/ber_dec.h> @@ -20,7 +21,7 @@ namespace Botan { /* * List of X.509 Certificate Extensions */ -Certificate_Extension* Extensions::get_extension(const OID& oid) +Certificate_Extension* Extensions::get_extension(const OID& oid, bool critical) { #define X509_EXTENSION(NAME, TYPE) \ if(OIDS::name_of(oid, NAME)) \ @@ -40,7 +41,7 @@ Certificate_Extension* Extensions::get_extension(const OID& oid) X509_EXTENSION("X509v3.CRLNumber", CRL_Number); X509_EXTENSION("X509v3.ReasonCode", CRL_ReasonCode); - return nullptr; + return critical ? new Cert_Extension::Unknown_Critical_Extension(oid) : nullptr; } /* @@ -55,7 +56,7 @@ Extensions::Extensions(const Extensions& extensions) : ASN1_Object() * Extensions Assignment Operator */ Extensions& Extensions::operator=(const Extensions& other) - { + { m_extensions.clear(); for(size_t i = 0; i != other.m_extensions.size(); ++i) @@ -63,6 +64,7 @@ Extensions& Extensions::operator=(const Extensions& other) std::make_pair(std::unique_ptr<Certificate_Extension>(other.m_extensions[i].first->copy()), other.m_extensions[i].second)); + m_extensions_raw = other.m_extensions_raw; m_throw_on_unknown_critical = other.m_throw_on_unknown_critical; return (*this); @@ -76,12 +78,31 @@ OID Certificate_Extension::oid_of() const return OIDS::lookup(oid_name()); } +/* +* Validate the extension (the default implementation is a NOP) +*/ +void Certificate_Extension::validate(const X509_Certificate&, const X509_Certificate&, + const std::vector<X509_Certificate>&, + std::vector<std::set<Certificate_Status_Code>>&, + size_t) + { + } + void Extensions::add(Certificate_Extension* extn, bool critical) { m_extensions.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(extn), critical)); m_extensions_raw.emplace(extn->oid_of(), std::make_pair(extn->encode_inner(), critical)); } +std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> Extensions::extensions() const + { + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> exts; + for(auto& ext : m_extensions) + { + exts.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(ext.first->copy()), ext.second)); + } + return exts; + } std::map<OID, std::pair<std::vector<byte>, bool>> Extensions::extensions_raw() const { @@ -136,7 +157,7 @@ void Extensions::decode_from(BER_Decoder& from_source) m_extensions_raw.emplace(oid, std::make_pair(value, critical)); - std::unique_ptr<Certificate_Extension> ext(get_extension(oid)); + std::unique_ptr<Certificate_Extension> ext(get_extension(oid, critical)); if(!ext && critical && m_throw_on_unknown_critical) throw Decoding_Error("Encountered unknown X.509 extension marked " @@ -500,6 +521,68 @@ void Name_Constraints::contents_to(Data_Store& subject, Data_Store&) const } } +void Name_Constraints::validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<X509_Certificate>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) + { + if(!m_name_constraints.permitted().empty() || !m_name_constraints.excluded().empty()) + { + if(!subject.is_CA_cert() || !subject.is_critical("X509v3.NameConstraints")) + cert_status.at(pos).insert(Certificate_Status_Code::NAME_CONSTRAINT_ERROR); + + const bool at_self_signed_root = (pos == cert_path.size() - 1); + + // Check that all subordinate certs pass the name constraint + for(size_t j = 0; j <= pos; ++j) + { + if(pos == j && at_self_signed_root) + continue; + + bool permitted = m_name_constraints.permitted().empty(); + bool failed = false; + + for(auto c: m_name_constraints.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: m_name_constraints.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); + } + } + } + } + namespace { /* @@ -730,6 +813,20 @@ void CRL_Distribution_Points::Distribution_Point::decode_from(class BER_Decoder& .end_cons().end_cons(); } +std::vector<byte> Unknown_Critical_Extension::encode_inner() const + { + throw Exception("Unknown_Critical_Extension encoding not implemented"); + } + +void Unknown_Critical_Extension::decode_inner(const std::vector<byte>& buf) + { + } + +void Unknown_Critical_Extension::contents_to(Data_Store& info, Data_Store&) const + { + // TODO: textual representation? + } + } } diff --git a/src/lib/cert/x509/x509_ext.h b/src/lib/cert/x509/x509_ext.h index ac456b998..caefcb855 100644 --- a/src/lib/cert/x509/x509_ext.h +++ b/src/lib/cert/x509/x509_ext.h @@ -10,11 +10,17 @@ #include <botan/asn1_obj.h> #include <botan/asn1_oid.h> +#include <botan/asn1_alt_name.h> +#include <botan/cert_status.h> #include <botan/datastor.h> +#include <botan/name_constraint.h> +#include <botan/key_constraint.h> #include <botan/crl_ent.h> namespace Botan { +class X509_Certificate; + /** * X.509 Certificate Extension */ @@ -24,7 +30,7 @@ class BOTAN_DLL Certificate_Extension /** * @return OID representing this extension */ - OID oid_of() const; + virtual OID oid_of() const; /** * Make a copy of this extension @@ -46,6 +52,26 @@ class BOTAN_DLL Certificate_Extension */ virtual std::string oid_name() const = 0; + /* + * Callback visited during path validation. + * + * An extension can implement this callback to inspect + * the path during path validation. + * + * If an error occurs during validation of this extension, + * an appropriate status code shall be added to cert_status. + * + * @param subject Subject certificate that contains this extension + * @param issuer Issuer certificate + * @param status Certificate validation status codes for subject certificate + * @param cert_path Certificate path which is currently validated + * @param pos Position of subject certificate in cert_path + */ + virtual void validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<X509_Certificate>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos); + virtual ~Certificate_Extension() {} protected: friend class Extensions; @@ -67,6 +93,8 @@ class BOTAN_DLL Extensions : public ASN1_Object void add(Certificate_Extension* extn, bool critical = false); + std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const; + std::map<OID, std::pair<std::vector<byte>, bool>> extensions_raw() const; Extensions& operator=(const Extensions&); @@ -76,7 +104,7 @@ class BOTAN_DLL Extensions : public ASN1_Object explicit Extensions(bool st = true) : m_throw_on_unknown_critical(st) {} private: - static Certificate_Extension* get_extension(const OID&); + static Certificate_Extension* get_extension(const OID&, bool); std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> m_extensions; bool m_throw_on_unknown_critical; @@ -101,6 +129,7 @@ class BOTAN_DLL Basic_Constraints final : public Certificate_Extension bool get_is_ca() const { return m_is_ca; } size_t get_path_limit() const; + private: std::string oid_name() const override { return "X509v3.BasicConstraints"; } @@ -124,6 +153,7 @@ class BOTAN_DLL Key_Usage final : public Certificate_Extension explicit Key_Usage(Key_Constraints c = NO_CONSTRAINTS) : m_constraints(c) {} Key_Constraints get_constraints() const { return m_constraints; } + private: std::string oid_name() const override { return "X509v3.KeyUsage"; } @@ -174,6 +204,7 @@ class BOTAN_DLL Authority_Key_ID final : public Certificate_Extension explicit Authority_Key_ID(const std::vector<byte>& k) : m_key_id(k) {} std::vector<byte> get_key_id() const { return m_key_id; } + private: std::string oid_name() const override { return "X509v3.AuthorityKeyIdentifier"; } @@ -198,6 +229,7 @@ class BOTAN_DLL Alternative_Name : public Certificate_Extension Alternative_Name(const AlternativeName&, const std::string& oid_name); Alternative_Name(const std::string&, const std::string&); + private: std::string oid_name() const override { return m_oid_name_str; } @@ -247,6 +279,7 @@ class BOTAN_DLL Extended_Key_Usage final : public Certificate_Extension explicit Extended_Key_Usage(const std::vector<OID>& o) : m_oids(o) {} std::vector<OID> get_oids() const { return m_oids; } + private: std::string oid_name() const override { return "X509v3.ExtendedKeyUsage"; } @@ -270,6 +303,12 @@ class BOTAN_DLL Name_Constraints : public Certificate_Extension Name_Constraints() {} Name_Constraints(const NameConstraints &nc) : m_name_constraints(nc) {} + + void validate(const X509_Certificate& subject, const X509_Certificate& issuer, + const std::vector<X509_Certificate>& cert_path, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) override; + private: std::string oid_name() const override { return "X509v3.NameConstraints"; } @@ -295,6 +334,7 @@ class BOTAN_DLL Certificate_Policies final : public Certificate_Extension explicit Certificate_Policies(const std::vector<OID>& o) : m_oids(o) {} std::vector<OID> get_oids() const { return m_oids; } + private: std::string oid_name() const override { return "X509v3.CertificatePolicies"; } @@ -344,6 +384,7 @@ class BOTAN_DLL CRL_Number final : public Certificate_Extension CRL_Number(size_t n) : m_has_value(true), m_crl_number(n) {} size_t get_crl_number() const; + private: std::string oid_name() const override { return "X509v3.CRLNumber"; } @@ -368,6 +409,7 @@ class BOTAN_DLL CRL_ReasonCode final : public Certificate_Extension explicit CRL_ReasonCode(CRL_Code r = UNSPECIFIED) : m_reason(r) {} CRL_Code get_reason() const { return m_reason; } + private: std::string oid_name() const override { return "X509v3.ReasonCode"; } @@ -421,6 +463,41 @@ class BOTAN_DLL CRL_Distribution_Points final : public Certificate_Extension std::vector<Distribution_Point> m_distribution_points; }; +/** +* An unknown X.509 extension marked as critical +* Will always add a failure to the path validation result. +*/ +class BOTAN_DLL Unknown_Critical_Extension final : public Certificate_Extension + { + public: + explicit Unknown_Critical_Extension(OID oid) : m_oid(oid) {} + + Unknown_Critical_Extension* copy() const override + { return new Unknown_Critical_Extension(m_oid); } + + OID oid_of() const override + { return m_oid; }; + + void validate(const X509_Certificate&, const X509_Certificate&, + const std::vector<X509_Certificate>&, + std::vector<std::set<Certificate_Status_Code>>& cert_status, + size_t pos) override + { + cert_status.at(pos).insert(Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION); + } + + private: + std::string oid_name() const override + { return "Unknown OID name"; } + + bool should_encode() const { return false; } + std::vector<byte> encode_inner() const override; + void decode_inner(const std::vector<byte>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + OID m_oid; + }; + } } diff --git a/src/lib/cert/x509/x509cert.cpp b/src/lib/cert/x509/x509cert.cpp index 959cddb53..f68956859 100644 --- a/src/lib/cert/x509/x509cert.cpp +++ b/src/lib/cert/x509/x509cert.cpp @@ -42,9 +42,10 @@ std::vector<std::string> lookup_oids(const std::vector<std::string>& in) * X509_Certificate Constructor */ X509_Certificate::X509_Certificate(DataSource& in) : - X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) { - m_self_signed = false; do_decode(); } @@ -52,9 +53,10 @@ X509_Certificate::X509_Certificate(DataSource& in) : * X509_Certificate Constructor */ X509_Certificate::X509_Certificate(const std::string& in) : - X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) { - m_self_signed = false; do_decode(); } @@ -62,12 +64,39 @@ X509_Certificate::X509_Certificate(const std::string& in) : * X509_Certificate Constructor */ X509_Certificate::X509_Certificate(const std::vector<byte>& in) : - X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), + m_self_signed(false), + m_v3_extensions(false) { - m_self_signed = false; do_decode(); } +X509_Certificate::X509_Certificate(const X509_Certificate& other) : + X509_Object(other) + { + m_subject = other.m_subject; + m_issuer = other.m_issuer; + m_self_signed = other.m_self_signed; + m_v3_extensions = other.m_v3_extensions; + } + +X509_Certificate& X509_Certificate::operator=(const X509_Certificate& other) + { + if(&other == this) + { + return *this; + } + else + { + m_subject = other.m_subject; + m_issuer = other.m_issuer; + m_self_signed = other.m_self_signed; + m_v3_extensions = other.m_v3_extensions; + } + return *this; + } + + /* * Decode the TBSCertificate data */ @@ -120,12 +149,8 @@ void X509_Certificate::force_decode() if(v3_exts_data.type_tag == 3 && v3_exts_data.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { - Extensions extensions; - - BER_Decoder(v3_exts_data.value).decode(extensions).verify_end(); - - m_v3_extensions = extensions.extensions_raw(); - extensions.contents_to(m_subject, m_issuer); + BER_Decoder(v3_exts_data.value).decode(m_v3_extensions).verify_end(); + m_v3_extensions.contents_to(m_subject, m_issuer); } else if(v3_exts_data.type_tag != NO_OBJECT) throw BER_Bad_Tag("Unknown tag in X.509 cert", @@ -332,7 +357,7 @@ std::vector<std::string> X509_Certificate::policies() const return lookup_oids(m_subject.get("X509v3.CertificatePolicies")); } -std::map<OID, std::pair<std::vector<byte>, bool>> X509_Certificate::v3_extensions() const +Extensions X509_Certificate::v3_extensions() const { return m_v3_extensions; } diff --git a/src/lib/cert/x509/x509cert.h b/src/lib/cert/x509/x509cert.h index 54d82b1b4..c521cf7ca 100644 --- a/src/lib/cert/x509/x509cert.h +++ b/src/lib/cert/x509/x509cert.h @@ -11,11 +11,13 @@ #include <botan/x509_obj.h> #include <botan/x509_dn.h> #include <botan/x509_key.h> +#include <botan/x509_ext.h> #include <botan/asn1_alt_name.h> #include <botan/datastor.h> #include <botan/key_constraint.h> #include <botan/name_constraint.h> #include <map> +#include <memory> namespace Botan { @@ -191,10 +193,10 @@ class BOTAN_DLL X509_Certificate final : public X509_Object std::vector<std::string> policies() const; /** - * Get all extensions of this certificate indexed by oid. - * @return extension values and critical flag + * Get all extensions of this certificate. + * @return certificate extensions */ - std::map<OID, std::pair<std::vector<byte>, bool>> v3_extensions() const; + Extensions v3_extensions() const; /** * Return the listed address of an OCSP responder, or empty if not set @@ -250,6 +252,10 @@ class BOTAN_DLL X509_Certificate final : public X509_Object explicit X509_Certificate(const std::vector<byte>& in); + X509_Certificate(const X509_Certificate& other); + + X509_Certificate& operator=(const X509_Certificate& other); + private: void force_decode() override; friend class X509_CA; @@ -259,7 +265,7 @@ class BOTAN_DLL X509_Certificate final : public X509_Object Data_Store m_subject, m_issuer; bool m_self_signed; - std::map<OID, std::pair<std::vector<byte>, bool>> m_v3_extensions; + Extensions m_v3_extensions; }; /** diff --git a/src/lib/cert/x509/x509path.cpp b/src/lib/cert/x509/x509path.cpp index dd9df6f51..436e27d39 100644 --- a/src/lib/cert/x509/x509path.cpp +++ b/src/lib/cert/x509/x509path.cpp @@ -113,7 +113,6 @@ check_chain(const std::vector<X509_Certificate>& cert_path, // Check issuer constraints - // Don't require CA bit set on self-signed end entity cert if(!issuer.is_CA_cert() && !self_signed_ee_cert) status.insert(Certificate_Status_Code::CA_CERT_NOT_FOR_CERT_ISSUER); @@ -142,60 +141,11 @@ check_chain(const std::vector<X509_Certificate>& cert_path, status.insert(Certificate_Status_Code::UNTRUSTED_HASH); } - const NameConstraints& name_constr = issuer.name_constraints(); - - if(!name_constr.permitted().empty() || !name_constr.excluded().empty()) + // Check cert extensions + Extensions extensions = subject.v3_extensions(); + for(auto& extension : extensions.extensions()) { - 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); - } - } + extension.first->validate(subject, issuer, cert_path, cert_status, i); } } @@ -472,6 +422,8 @@ const char* Path_Validation_Result::status_string(Certificate_Status_Code code) 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::UNKNOWN_CRITICAL_EXTENSION: + return "Unknown critical extension encountered"; case Certificate_Status_Code::CERT_IS_REVOKED: return "Certificate is revoked"; |