diff options
author | Jack Lloyd <[email protected]> | 2017-11-15 15:55:40 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2017-11-15 15:55:40 -0500 |
commit | 24a5d0688748abe132b602995a993e5d0bc7d455 (patch) | |
tree | b5ebaa023fecd6900f9fe12b57ad946063c6fd29 | |
parent | 0bc3991616917745cbd78df2bf03f5c7b9c5e8ca (diff) | |
parent | 3b4a2c547a948b421d73ae7e1bc0ad9430cce465 (diff) |
Merge GH #884 Refactor X.509 cert/CRL internals
35 files changed, 1719 insertions, 1051 deletions
diff --git a/doc/manual/x509.rst b/doc/manual/x509.rst index d82dbccf2..daf154c9a 100644 --- a/doc/manual/x509.rst +++ b/doc/manual/x509.rst @@ -15,43 +15,96 @@ in the :doc:`tls` protocol. A X.509 certificate is represented by .. cpp:class:: X509_Certificate - .. cpp:function:: Public_Key* subject_public_key() const + .. cpp:function:: X509_Certificate(const std::string& filename) - Returns the public key of the subject + Load a certificate from a file. PEM or DER is accepted. - .. cpp:function:: X509_DN issuer_dn() const + .. cpp:function:: X509_Certificate(const std::vector<uint8_t>& in) - Returns the distinguished name (DN) of the certificate's issuer + Load a certificate from a byte string. + + .. cpp:function:: X509_Certificate(DataSource& source) + + Load a certificate from an abstract ``DataSource``. + + .. cpp:function:: std::unique_ptr<Public_Key> load_subject_public_key() const + + Deserialize the stored public key and return a new object. This + might throw, if it happens that the public key object stored in + the certificate is malformed in some way, or in the case that the + public key algorithm used is not supported by the library. + + See :ref:`serializing_public_keys` for more information about what to do + with the returned object. It may be any type of key, in principle, though + RSA and ECDSA are most common. + + .. cpp:function:: std::vector<uint8_t> serial_number() const + + Return the certificates serial number. The tuple of issuer DN and + serial number should be unique. .. cpp:function:: X509_DN subject_dn() const - Returns the distinguished name (DN) of the certificate's subject + Returns the distinguished name (DN) of the certificate's subject. - .. cpp:function:: std::string start_time() const + .. cpp:function:: X509_DN issuer_dn() const + + Returns the distinguished name (DN) of the certificate's issuer + + .. cpp:function:: X509_Time not_before() const Returns the point in time the certificate becomes valid - .. cpp:function:: std::string end_time() const + .. cpp:function:: X509_Time not_after() const Returns the point in time the certificate expires - .. cpp:function:: Extensions v3_extensions() const + .. cpp:function:: const Extensions& v3_extensions() const + + Returns all extensions of this certificate. You can use this + to examine any extension data associated with the certificate, + including custom extensions the library doesn't know about. + + .. cpp:function:: const AlternativeName& subject_alt_name() const + + Return the subjects alternative name. This is used to store + values like associated URIs, DNS addresses, and email addresses. + + .. cpp:function:: const AlternativeName& issuer_alt_name() const + + Return alternative names for the issuer. - Returns all extensions of this certificate + .. cpp:function:: std::string fingerprint(const std::string& hash_name = "SHA-1") const -When working with certificates, the main class to remember is -``X509_Certificate``. You can read an object of this type, but you -can't create one on the fly; a CA object is necessary for making a new -certificate. So for the most part, you only have to worry about -reading them in, verifying the signatures, and getting the bits of -data in them (most commonly the public key, and the information about -the user of that key). An X.509v3 certificate can contain a literally -infinite number of items related to all kinds of things. Botan doesn't -support a lot of them, because nobody uses them and they're an -impossible mess to work with. This section only documents the most -commonly used ones of the ones that are supported; for the rest, read -``x509cert.h`` and ``asn1_obj.h`` (which has the definitions of -various common ASN.1 constructs used in X.509). + Return a fingerprint for the certificate. + + .. cpp:function:: Key_Constraints constraints() const + + Returns either an enumeration listing key constraints (what the + associated key can be used for) or ``NO_CONSTRAINTS`` if the + relevent extension was not included. Example values are + ``DIGITAL_SIGNATURE`` and ``KEY_CERT_SIGN``. More than one value + might be specified. + + .. cpp:function:: bool allowed_extended_usage(const OID& usage) const + + Returns true if the OID is included in the extended usage extension. + + .. cpp:function:: bool matches_dns_name(const std::string& name) const + + Check if the certificate's subject alternative name DNS fields + match ``name``. This function also handles wildcard certificates. + + .. cpp:function:: std::string to_string() const + + Returns a free-form human readable string describing the certificate. + +The ``X509_Certificate`` class has several other functions not described here. +See the header ``x509cert.h`` for details. + +The data of an X.509 certificate is stored as a ``shared_ptr`` to a structure +containing the decoded information. So copying ``X509_Certificate`` objects is +quite cheap. So what's in an X.509 certificate? ----------------------------------- @@ -140,8 +193,7 @@ functions are provided to search them. .. note:: - Validation of custom extensions during path validation - is currently not supported. + Validation of custom extensions during path validation is currently not supported. .. cpp:class:: Extensions @@ -178,7 +230,7 @@ functions are provided to search them. together with the corresponding criticality flag. Contains all extensions, known as well as unknown extensions. -Revocation Lists +Certificate Revocation Lists ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ It will occasionally happen that a certificate must be revoked before @@ -209,22 +261,7 @@ with .. cpp:function:: void Certificate_Store::add_crl(const X509_CRL& crl) -and all future verifications will take into account the certificates -listed. - -Reading Certificates -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -``X509_Certificate`` has two constructors, each of which takes a source of -data; a filename to read, and a ``DataSource&``:: - - X509_Certificate cert1("cert1.pem"); - - /* This file contains two certificates, concatenated */ - DataSource_Stream in("certs2_and_3.pem"); - - X509_Certificate cert2(in); // read the first cert - X509_Certificate cert3(in); // read the second cert +and all future verifications will take into account the provided CRL. Certificate Stores ---------------------------------------- @@ -302,9 +339,9 @@ later. SQL-backed Certificate Stores ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The SQL-backed certificate stores store all objects in an SQL -database. They also additionally provide private key storage -and revocation of individual certificates. +The SQL-backed certificate stores store all objects in an SQL database. They +also additionally provide private key storage and revocation of individual +certificates. .. cpp:class:: Certificate_Store_In_SQL @@ -324,7 +361,6 @@ and revocation of individual certificates. Removes ``cert`` from the store. Returns `false` if the certificate could not be found and `true` if removal was successful. - .. cpp:function:: std::shared_ptr<const Private_Key> find_key(const X509_Certificate&) const Returns the private key for "cert" or an empty shared_ptr if none was found @@ -357,8 +393,10 @@ and revocation of individual certificates. Generates CRLs for all certificates marked as revoked. A CRL is returned for each unique issuer DN. -Botan currently only provides one SQL-backed certificate store using -sqlite. +The ``Certificate_Store_In_SQL`` class operates on an abstract ``SQL_Database`` +object. If support for sqlite3 was enabled at build time, Botan includes an +implementation of this interface for sqlite3, and a subclass of +``Certificate_Store_In_SQL`` which creates or opens a sqlite3 database. .. cpp:class:: Certificate_Store_In_SQLite @@ -475,7 +513,7 @@ step. The two constructors are: and, if `minimum_key_strength` is less than or equal to 80, then SHA-1 signatures will also be accepted. -Certificate Authorities +Creating New Certificates --------------------------------- A CA is represented by the type ``X509_CA``, which can be found in @@ -523,10 +561,9 @@ be issued. To generate a new, empty CRL, just call .. cpp:function:: X509_CRL X509_CA::new_crl(RandomNumberGenerator& rng, \ uint32_t next_update = 0) - This function will return a new, empty CRL. The - ``next_update`` parameter is the number of seconds before - the CRL expires. If it is set to the (default) value of zero, then a - reasonable default (currently 7 days) will be used. + This function will return a new, empty CRL. The ``next_update`` parameter is + the number of seconds before the CRL expires. If it is set to the (default) + value of zero, then a reasonable default (currently 7 days) will be used. On the other hand, you may have issued a CRL before. In that case, you will want to issue a new CRL that contains all previously revoked diff --git a/src/cli/asn1.cpp b/src/cli/asn1.cpp index 234cbd6e6..08c2562ba 100644 --- a/src/cli/asn1.cpp +++ b/src/cli/asn1.cpp @@ -336,13 +336,7 @@ void decode(std::ostream& output, emit(output, type_name(type_tag), level, length, bit_str); } - else if(type_tag == Botan::PRINTABLE_STRING || - type_tag == Botan::NUMERIC_STRING || - type_tag == Botan::IA5_STRING || - type_tag == Botan::T61_STRING || - type_tag == Botan::VISIBLE_STRING || - type_tag == Botan::UTF8_STRING || - type_tag == Botan::BMP_STRING) + else if(Botan::ASN1_String::is_string_type(type_tag)) { Botan::ASN1_String str; data.decode(str); diff --git a/src/lib/asn1/asn1_alt_name.h b/src/lib/asn1/asn1_alt_name.h deleted file mode 100644 index 9a9b759d7..000000000 --- a/src/lib/asn1/asn1_alt_name.h +++ /dev/null @@ -1,47 +0,0 @@ -/* -* Common ASN.1 Objects -* (C) 1999-2007 Jack Lloyd -* 2007 Yves Jerschow -* -* Botan is released under the Simplified BSD License (see license.txt) -*/ - -#ifndef BOTAN_ASN1_ALT_NAME_H_ -#define BOTAN_ASN1_ALT_NAME_H_ - -#include <botan/asn1_obj.h> -#include <botan/asn1_str.h> -#include <botan/asn1_oid.h> -#include <map> - -namespace Botan { - -/** -* Alternative Name -*/ -class BOTAN_PUBLIC_API(2,0) AlternativeName final : public ASN1_Object - { - public: - void encode_into(class DER_Encoder&) const override; - void decode_from(class BER_Decoder&) override; - - std::multimap<std::string, std::string> contents() const; - - void add_attribute(const std::string&, const std::string&); - std::multimap<std::string, std::string> get_attributes() const; - - void add_othername(const OID&, const std::string&, ASN1_Tag); - std::multimap<OID, ASN1_String> get_othernames() const; - - bool has_items() const; - - AlternativeName(const std::string& = "", const std::string& = "", - const std::string& = "", const std::string& = ""); - private: - std::multimap<std::string, std::string> m_alt_info; - std::multimap<OID, ASN1_String> m_othernames; - }; - -} - -#endif diff --git a/src/lib/asn1/asn1_str.cpp b/src/lib/asn1/asn1_str.cpp index d90aa215b..8b9524de2 100644 --- a/src/lib/asn1/asn1_str.cpp +++ b/src/lib/asn1/asn1_str.cpp @@ -55,14 +55,7 @@ ASN1_Tag choose_encoding(const std::string& str) void assert_is_string_type(ASN1_Tag tag) { - if(tag != NUMERIC_STRING && - tag != PRINTABLE_STRING && - tag != VISIBLE_STRING && - tag != T61_STRING && - tag != IA5_STRING && - tag != UTF8_STRING && - tag != BMP_STRING && - tag != UNIVERSAL_STRING) + if(!ASN1_String::is_string_type(tag)) { throw Invalid_Argument("ASN1_String: Unknown string type " + std::to_string(tag)); @@ -71,6 +64,20 @@ void assert_is_string_type(ASN1_Tag tag) } +//static +bool ASN1_String::is_string_type(ASN1_Tag tag) + { + return (tag == NUMERIC_STRING || + tag == PRINTABLE_STRING || + tag == VISIBLE_STRING || + tag == T61_STRING || + tag == IA5_STRING || + tag == UTF8_STRING || + tag == BMP_STRING || + tag == UNIVERSAL_STRING); + } + + /* * Create an ASN1_String */ diff --git a/src/lib/asn1/asn1_str.h b/src/lib/asn1/asn1_str.h index f19265494..77e2fc145 100644 --- a/src/lib/asn1/asn1_str.h +++ b/src/lib/asn1/asn1_str.h @@ -29,6 +29,12 @@ class BOTAN_PUBLIC_API(2,0) ASN1_String final : public ASN1_Object std::string BOTAN_DEPRECATED("Use value() to get UTF-8 string instead") iso_8859() const; + /** + * Return true iff this is a tag for a known string type we can handle. + * This ignores string types that are not supported, eg teletexString + */ + static bool is_string_type(ASN1_Tag tag); + explicit ASN1_String(const std::string& utf8 = ""); ASN1_String(const std::string& utf8, ASN1_Tag tag); private: diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 2ac9c69af..17b78cc7c 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -55,7 +55,7 @@ int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key) *key = nullptr; #if defined(BOTAN_HAS_RSA) - std::unique_ptr<Botan::Public_Key> publicKey(safe_get(cert).subject_public_key()); + std::unique_ptr<Botan::Public_Key> publicKey = safe_get(cert).load_subject_public_key(); *key = new botan_pubkey_struct(publicKey.release()); return BOTAN_FFI_SUCCESS; #else @@ -100,12 +100,12 @@ int botan_x509_cert_destroy(botan_x509_cert_t cert) int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len) { - return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.start_time()); }); + return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_before().to_string()); }); } int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len) { - return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.end_time()); }); + return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_after().to_string()); }); } int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len) diff --git a/src/lib/asn1/asn1_alt_name.cpp b/src/lib/x509/asn1_alt_name.cpp index 940312886..6b3eda917 100644 --- a/src/lib/asn1/asn1_alt_name.cpp +++ b/src/lib/x509/asn1_alt_name.cpp @@ -16,24 +16,6 @@ namespace Botan { -namespace { - -/* -* Check if type is a known ASN.1 string type -*/ -bool is_string_type(ASN1_Tag tag) - { - return (tag == NUMERIC_STRING || - tag == PRINTABLE_STRING || - tag == VISIBLE_STRING || - tag == T61_STRING || - tag == IA5_STRING || - tag == UTF8_STRING || - tag == BMP_STRING); - } - -} - /* * Create an AlternativeName */ @@ -52,17 +34,17 @@ AlternativeName::AlternativeName(const std::string& email_addr, * Add an attribute to an alternative name */ void AlternativeName::add_attribute(const std::string& type, - const std::string& str) + const std::string& value) { - if(type.empty() || str.empty()) + if(type.empty() || value.empty()) return; auto range = m_alt_info.equal_range(type); for(auto j = range.first; j != range.second; ++j) - if(j->second == str) + if(j->second == value) return; - multimap_insert(m_alt_info, type, str); + multimap_insert(m_alt_info, type, value); } /* @@ -77,22 +59,6 @@ void AlternativeName::add_othername(const OID& oid, const std::string& value, } /* -* Get the attributes of this alternative name -*/ -std::multimap<std::string, std::string> AlternativeName::get_attributes() const - { - return m_alt_info; - } - -/* -* Get the otherNames -*/ -std::multimap<OID, ASN1_String> AlternativeName::get_othernames() const - { - return m_othernames; - } - -/* * Return all of the alternative names */ std::multimap<std::string, std::string> AlternativeName::contents() const @@ -108,6 +74,30 @@ std::multimap<std::string, std::string> AlternativeName::contents() const return names; } +bool AlternativeName::has_field(const std::string& attr) const + { + auto range = m_alt_info.equal_range(attr); + return (range.first != range.second); + } + +std::string AlternativeName::get_first_attribute(const std::string& attr) const + { + auto i = m_alt_info.lower_bound(attr); + if(i != m_alt_info.end() && i->first == attr) + return i->second; + + return ""; + } + +std::vector<std::string> AlternativeName::get_attribute(const std::string& attr) const + { + std::vector<std::string> results; + auto range = m_alt_info.equal_range(attr); + for(auto i = range.first; i != range.second; ++i) + results.push_back(i->second); + return results; + } + /* * Return if this object has anything useful */ @@ -211,8 +201,10 @@ void AlternativeName::decode_from(BER_Decoder& source) const ASN1_Tag value_type = value.type_tag; - if(is_string_type(value_type) && value.class_tag == UNIVERSAL) + if(ASN1_String::is_string_type(value_type) && value.class_tag == UNIVERSAL) + { add_othername(oid, ASN1::to_string(value), value_type); + } } } else if(tag == 1 || tag == 2 || tag == 6) diff --git a/src/lib/x509/asn1_alt_name.h b/src/lib/x509/asn1_alt_name.h new file mode 100644 index 000000000..83ac215ba --- /dev/null +++ b/src/lib/x509/asn1_alt_name.h @@ -0,0 +1,60 @@ +/* +* (C) 1999-2007 Jack Lloyd +* 2007 Yves Jerschow +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_X509_ALT_NAME_H_ +#define BOTAN_X509_ALT_NAME_H_ + +#include <botan/asn1_obj.h> +#include <botan/asn1_str.h> +#include <botan/asn1_oid.h> +#include <map> + +namespace Botan { + +/** +* Alternative Name +*/ +class BOTAN_PUBLIC_API(2,0) AlternativeName final : public ASN1_Object + { + public: + void encode_into(class DER_Encoder&) const override; + void decode_from(class BER_Decoder&) override; + + std::multimap<std::string, std::string> contents() const; + + bool has_field(const std::string& attr) const; + std::vector<std::string> get_attribute(const std::string& attr) const; + + std::string get_first_attribute(const std::string& attr) const; + + void add_attribute(const std::string& type, const std::string& value); + void add_othername(const OID& oid, const std::string& value, ASN1_Tag type); + + const std::multimap<std::string, std::string>& get_attributes() const + { + return m_alt_info; + } + + const std::multimap<OID, ASN1_String>& get_othernames() const + { + return m_othernames; + } + + bool has_items() const; + + AlternativeName(const std::string& email_addr = "", + const std::string& uri = "", + const std::string& dns = "", + const std::string& ip_address = ""); + private: + std::multimap<std::string, std::string> m_alt_info; + std::multimap<OID, ASN1_String> m_othernames; + }; + +} + +#endif diff --git a/src/lib/x509/crl_ent.cpp b/src/lib/x509/crl_ent.cpp index fabd88326..61fd5d31f 100644 --- a/src/lib/x509/crl_ent.cpp +++ b/src/lib/x509/crl_ent.cpp @@ -14,24 +14,28 @@ namespace Botan { -/* -* Create a CRL_Entry -*/ -CRL_Entry::CRL_Entry(bool t_on_unknown_crit) : - m_throw_on_unknown_critical(t_on_unknown_crit) +struct CRL_Entry_Data { - m_reason = UNSPECIFIED; - } + std::vector<uint8_t> m_serial; + X509_Time m_time; + CRL_Code m_reason; + Extensions m_extensions; + }; /* * Create a CRL_Entry */ -CRL_Entry::CRL_Entry(const X509_Certificate& cert, CRL_Code why) : - m_throw_on_unknown_critical(false) +CRL_Entry::CRL_Entry(const X509_Certificate& cert, CRL_Code why) { - m_serial = cert.serial_number(); - m_time = X509_Time(std::chrono::system_clock::now()); - m_reason = why; + m_data.reset(new CRL_Entry_Data); + m_data->m_serial = cert.serial_number(); + m_data->m_time = X509_Time(std::chrono::system_clock::now()); + m_data->m_reason = why; + + if(why != UNSPECIFIED) + { + m_data->m_extensions.add(new Cert_Extension::CRL_ReasonCode(why)); + } } /* @@ -61,17 +65,13 @@ bool operator!=(const CRL_Entry& a1, const CRL_Entry& a2) */ void CRL_Entry::encode_into(DER_Encoder& der) const { - Extensions extensions; - - extensions.add(new Cert_Extension::CRL_ReasonCode(m_reason)); - der.start_cons(SEQUENCE) - .encode(BigInt::decode(m_serial)) - .encode(m_time) - .start_cons(SEQUENCE) - .encode(extensions) - .end_cons() - .end_cons(); + .encode(BigInt::decode(serial_number())) + .encode(expire_time()) + .start_cons(SEQUENCE) + .encode(extensions()) + .end_cons() + .end_cons(); } /* @@ -80,24 +80,58 @@ void CRL_Entry::encode_into(DER_Encoder& der) const void CRL_Entry::decode_from(BER_Decoder& source) { BigInt serial_number_bn; - m_reason = UNSPECIFIED; + + std::unique_ptr<CRL_Entry_Data> data(new CRL_Entry_Data); BER_Decoder entry = source.start_cons(SEQUENCE); - entry.decode(serial_number_bn).decode(m_time); + entry.decode(serial_number_bn).decode(data->m_time); + data->m_serial = BigInt::encode(serial_number_bn); if(entry.more_items()) { - Extensions extensions(m_throw_on_unknown_critical); - entry.decode(extensions); - Data_Store info; - extensions.contents_to(info, info); - m_reason = CRL_Code(info.get1_uint32("X509v3.CRLReasonCode")); + entry.decode(data->m_extensions); + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::CRL_ReasonCode>()) + { + data->m_reason = ext->get_reason(); + } + else + { + data->m_reason = UNSPECIFIED; + } } entry.end_cons(); - m_serial = BigInt::encode(serial_number_bn); + m_data.reset(data.release()); + } + +const CRL_Entry_Data& CRL_Entry::data() const + { + if(!m_data) + throw Decoding_Error("Uninitialized CRL_Entry"); + return *m_data.get(); + } + +const std::vector<uint8_t>& CRL_Entry::serial_number() const + { + return data().m_serial; } +const X509_Time& CRL_Entry::expire_time() const + { + return data().m_time; + } + +CRL_Code CRL_Entry::reason_code() const + { + return data().m_reason; + } + +const Extensions& CRL_Entry::extensions() const + { + return data().m_extensions; + } + + } diff --git a/src/lib/x509/crl_ent.h b/src/lib/x509/crl_ent.h index cf509d3c1..967dc92d2 100644 --- a/src/lib/x509/crl_ent.h +++ b/src/lib/x509/crl_ent.h @@ -12,7 +12,9 @@ namespace Botan { +class Extensions; class X509_Certificate; +struct CRL_Entry_Data; /** * X.509v2 CRL Reason Code. @@ -47,26 +49,29 @@ class BOTAN_PUBLIC_API(2,0) CRL_Entry final : public ASN1_Object * Get the serial number of the certificate associated with this entry. * @return certificate's serial number */ - std::vector<uint8_t> serial_number() const { return m_serial; } + const std::vector<uint8_t>& serial_number() const; /** * Get the revocation date of the certificate associated with this entry * @return certificate's revocation date */ - X509_Time expire_time() const { return m_time; } + const X509_Time& expire_time() const; /** * Get the entries reason code * @return reason code */ - CRL_Code reason_code() const { return m_reason; } + CRL_Code reason_code() const; /** - * Construct an empty CRL entry. - * @param throw_on_unknown_critical_extension should we throw an exception - * if an unknown CRL extension marked as critical is encountered + * Get the extensions on this CRL entry */ - explicit CRL_Entry(bool throw_on_unknown_critical_extension = false); + const Extensions& extensions() const; + + /** + * Create uninitialized CRL_Entry object + */ + CRL_Entry() {} /** * Construct an CRL entry. @@ -77,10 +82,11 @@ class BOTAN_PUBLIC_API(2,0) CRL_Entry final : public ASN1_Object CRL_Code reason = UNSPECIFIED); private: - bool m_throw_on_unknown_critical; - std::vector<uint8_t> m_serial; - X509_Time m_time; - CRL_Code m_reason; + friend class X509_CRL; + + const CRL_Entry_Data& data() const; + + std::shared_ptr<CRL_Entry_Data> m_data; }; /** diff --git a/src/lib/x509/datastor.cpp b/src/lib/x509/datastor.cpp index ae6b1e45c..2cdd3458c 100644 --- a/src/lib/x509/datastor.cpp +++ b/src/lib/x509/datastor.cpp @@ -161,4 +161,45 @@ void Data_Store::add(const std::multimap<std::string, std::string>& in) } } +/* +* Create and populate a X509_DN +*/ +X509_DN create_dn(const Data_Store& info) + { + auto names = info.search_for( + [](const std::string& key, const std::string&) + { + return (key.find("X520.") != std::string::npos); + }); + + X509_DN dn; + + for(auto i = names.begin(); i != names.end(); ++i) + dn.add_attribute(i->first, i->second); + + return dn; + } + +/* +* Create and populate an AlternativeName +*/ +AlternativeName create_alt_name(const Data_Store& info) + { + auto names = info.search_for( + [](const std::string& key, const std::string&) + { + return (key == "RFC822" || + key == "DNS" || + key == "URI" || + key == "IP"); + }); + + AlternativeName alt_name; + + for(auto i = names.begin(); i != names.end(); ++i) + alt_name.add_attribute(i->first, i->second); + + return alt_name; + } + } diff --git a/src/lib/x509/datastor.h b/src/lib/x509/datastor.h index c730c7140..556a78984 100644 --- a/src/lib/x509/datastor.h +++ b/src/lib/x509/datastor.h @@ -8,7 +8,8 @@ #ifndef BOTAN_DATA_STORE_H_ #define BOTAN_DATA_STORE_H_ -#include <botan/secmem.h> +#include <botan/x509_dn.h> +#include <botan/asn1_alt_name.h> #include <functional> #include <string> #include <vector> @@ -23,7 +24,7 @@ namespace Botan { * reasons. There is no reason for applications to use this type directly. * It will be removed in a future major release. */ -class BOTAN_PUBLIC_API(2,0) Data_Store +class BOTAN_UNSTABLE_API Data_Store { public: /** @@ -55,6 +56,29 @@ class BOTAN_PUBLIC_API(2,0) Data_Store std::multimap<std::string, std::string> m_contents; }; +/* +* Data Store Extraction Operations +*/ + +/* +* Create and populate a X509_DN +* @param info data store containing DN information +* @return DN containing attributes from data store +*/ +BOTAN_PUBLIC_API(2,0) X509_DN +BOTAN_DEPRECATED("Avoid roundtripping names through Data_Store") +create_dn(const Data_Store& info); + +/* +* Create and populate an AlternativeName +* @param info data store containing AlternativeName information +* @return AlternativeName containing attributes from data store +*/ +BOTAN_PUBLIC_API(2,0) AlternativeName +BOTAN_DEPRECATED("Avoid roundtripping names through Data_Store") +create_alt_name(const Data_Store& info); + + } #endif diff --git a/src/lib/x509/info.txt b/src/lib/x509/info.txt index 8221dc0f1..6b136cbcf 100644 --- a/src/lib/x509/info.txt +++ b/src/lib/x509/info.txt @@ -7,4 +7,5 @@ OCSP -> 20161118 asn1 pubkey sha1 +sha2_32 </requires> diff --git a/src/lib/x509/name_constraint.cpp b/src/lib/x509/name_constraint.cpp index e098bcd8d..21145824b 100644 --- a/src/lib/x509/name_constraint.cpp +++ b/src/lib/x509/name_constraint.cpp @@ -6,6 +6,7 @@ */ #include <botan/name_constraint.h> +#include <botan/asn1_alt_name.h> #include <botan/ber_dec.h> #include <botan/loadstor.h> #include <botan/x509_dn.h> @@ -105,14 +106,18 @@ GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) cons std::vector<std::string> nam; std::function<bool(const GeneralName*, const std::string&)> match_fn; + const X509_DN& dn = cert.subject_dn(); + const AlternativeName& alt_name = cert.subject_alt_name(); + if(type() == "DNS") { match_fn = std::mem_fn(&GeneralName::matches_dns); - nam = cert.subject_info("DNS"); + + nam = alt_name.get_attribute("DNS"); if(nam.empty()) { - nam = cert.subject_info("CN"); + nam = dn.get_attribute("CN"); } } else if(type() == "DN") @@ -120,13 +125,13 @@ GeneralName::MatchResult GeneralName::matches(const X509_Certificate& cert) cons match_fn = std::mem_fn(&GeneralName::matches_dn); std::stringstream ss; - ss << cert.subject_dn(); + ss << dn; nam.push_back(ss.str()); } else if(type() == "IP") { match_fn = std::mem_fn(&GeneralName::matches_ip); - nam = cert.subject_info("IP"); + nam = alt_name.get_attribute("IP"); } else { diff --git a/src/lib/x509/ocsp.h b/src/lib/x509/ocsp.h index 254de5038..33177dc59 100644 --- a/src/lib/x509/ocsp.h +++ b/src/lib/x509/ocsp.h @@ -10,6 +10,7 @@ #include <botan/cert_status.h> #include <botan/ocsp_types.h> +#include <botan/x509_dn.h> namespace Botan { diff --git a/src/lib/x509/pkcs10.cpp b/src/lib/x509/pkcs10.cpp index 911d6958c..82ef0945d 100644 --- a/src/lib/x509/pkcs10.cpp +++ b/src/lib/x509/pkcs10.cpp @@ -1,6 +1,6 @@ /* * PKCS #10 -* (C) 1999-2007 Jack Lloyd +* (C) 1999-2007,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -14,6 +14,15 @@ namespace Botan { +struct PKCS10_Data + { + X509_DN m_subject_dn; + std::vector<uint8_t> m_public_key_bits; + AlternativeName m_alt_name; + std::string m_challenge; + Extensions m_extensions; + }; + /* * PKCS10_Request Constructor */ @@ -46,9 +55,13 @@ PKCS10_Request::PKCS10_Request(const std::vector<uint8_t>& in) : /* * Decode the CertificateRequestInfo */ -void PKCS10_Request::force_decode() +namespace { + +std::unique_ptr<PKCS10_Data> decode_pkcs10(const std::vector<uint8_t>& body) { - BER_Decoder cert_req_info(signed_body()); + std::unique_ptr<PKCS10_Data> data(new PKCS10_Data); + + BER_Decoder cert_req_info(body); size_t version; cert_req_info.decode(version); @@ -56,25 +69,19 @@ void PKCS10_Request::force_decode() throw Decoding_Error("Unknown version code in PKCS #10 request: " + std::to_string(version)); - X509_DN dn_subject; - cert_req_info.decode(dn_subject); - - m_info.add(dn_subject.contents()); + cert_req_info.decode(data->m_subject_dn); BER_Object public_key = cert_req_info.get_next_object(); if(public_key.type_tag != SEQUENCE || public_key.class_tag != CONSTRUCTED) throw BER_Bad_Tag("PKCS10_Request: Unexpected tag for public key", public_key.type_tag, public_key.class_tag); - m_info.add("X509.Certificate.public_key", - PEM_Code::encode( - ASN1::put_in_sequence(unlock(public_key.value)), - "PUBLIC KEY" - ) - ); + data->m_public_key_bits = ASN1::put_in_sequence(unlock(public_key.value)); BER_Object attr_bits = cert_req_info.get_next_object(); + std::set<std::string> pkcs9_email; + if(attr_bits.type_tag == 0 && attr_bits.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { @@ -83,7 +90,24 @@ void PKCS10_Request::force_decode() { Attribute attr; attributes.decode(attr); - handle_attribute(attr); + BER_Decoder value(attr.parameters); + + if(attr.oid == OIDS::lookup("PKCS9.EmailAddress")) + { + ASN1_String email; + value.decode(email); + pkcs9_email.insert(email.value()); + } + else if(attr.oid == OIDS::lookup("PKCS9.ChallengePassword")) + { + ASN1_String challenge_password; + value.decode(challenge_password); + data->m_challenge = challenge_password.value(); + } + else if(attr.oid == OIDS::lookup("PKCS9.ExtensionRequest")) + { + value.decode(data->m_extensions).verify_end(); + } } attributes.verify_end(); } @@ -93,33 +117,38 @@ void PKCS10_Request::force_decode() cert_req_info.verify_end(); + if(auto ext = data->m_extensions.get_extension_object_as<Cert_Extension::Subject_Alternative_Name>()) + { + data->m_alt_name = ext->get_alt_name(); + } + + for(std::string email : pkcs9_email) + { + data->m_alt_name.add_attribute("RFC882", email); + } + + return data; + } + +} + +void PKCS10_Request::force_decode() + { + m_data.reset(); + + std::unique_ptr<PKCS10_Data> data = decode_pkcs10(signed_body()); + + m_data.reset(data.release()); + if(!this->check_signature(subject_public_key())) throw Decoding_Error("PKCS #10 request: Bad signature detected"); } -/* -* Handle attributes in a PKCS #10 request -*/ -void PKCS10_Request::handle_attribute(const Attribute& attr) +const PKCS10_Data& PKCS10_Request::data() const { - BER_Decoder value(attr.parameters); - - if(attr.oid == OIDS::lookup("PKCS9.EmailAddress")) - { - ASN1_String email; - value.decode(email); - m_info.add("RFC822", email.value()); - } - else if(attr.oid == OIDS::lookup("PKCS9.ChallengePassword")) - { - ASN1_String challenge_password; - value.decode(challenge_password); - m_info.add("PKCS9.ChallengePassword", challenge_password.value()); - } - else if(attr.oid == OIDS::lookup("PKCS9.ExtensionRequest")) - { - value.decode(m_extensions).verify_end(); - } + if(m_data == nullptr) + throw Decoding_Error("PKCS10_Request decoding failed"); + return *m_data.get(); } /* @@ -127,24 +156,23 @@ void PKCS10_Request::handle_attribute(const Attribute& attr) */ std::string PKCS10_Request::challenge_password() const { - return m_info.get1("PKCS9.ChallengePassword"); + return data().m_challenge; } /* * Return the name of the requestor */ -X509_DN PKCS10_Request::subject_dn() const +const X509_DN& PKCS10_Request::subject_dn() const { - return create_dn(m_info); + return data().m_subject_dn; } /* * Return the public key of the requestor */ -std::vector<uint8_t> PKCS10_Request::raw_public_key() const +const std::vector<uint8_t>& PKCS10_Request::raw_public_key() const { - DataSource_Memory source(m_info.get1("X509.Certificate.public_key")); - return unlock(PEM_Code::decode_check_label(source, "PUBLIC KEY")); + return data().m_public_key_bits; } /* @@ -152,16 +180,24 @@ std::vector<uint8_t> PKCS10_Request::raw_public_key() const */ Public_Key* PKCS10_Request::subject_public_key() const { - DataSource_Memory source(m_info.get1("X509.Certificate.public_key")); + DataSource_Memory source(raw_public_key()); return X509::load_key(source); } /* * Return the alternative names of the requestor */ -AlternativeName PKCS10_Request::subject_alt_name() const +const AlternativeName& PKCS10_Request::subject_alt_name() const { - return create_alt_name(m_info); + return data().m_alt_name; + } + +/* +* Return the X509v3 extensions +*/ +const Extensions& PKCS10_Request::extensions() const + { + return data().m_extensions; } /* @@ -169,7 +205,7 @@ AlternativeName PKCS10_Request::subject_alt_name() const */ Key_Constraints PKCS10_Request::constraints() const { - if(auto ext = m_extensions.get(OIDS::lookup("X509v3.KeyUsage"))) + if(auto ext = extensions().get(OIDS::lookup("X509v3.KeyUsage"))) { return dynamic_cast<Cert_Extension::Key_Usage&>(*ext).get_constraints(); } @@ -182,7 +218,7 @@ Key_Constraints PKCS10_Request::constraints() const */ std::vector<OID> PKCS10_Request::ex_constraints() const { - if(auto ext = m_extensions.get(OIDS::lookup("X509v3.ExtendedKeyUsage"))) + if(auto ext = extensions().get(OIDS::lookup("X509v3.ExtendedKeyUsage"))) { return dynamic_cast<Cert_Extension::Extended_Key_Usage&>(*ext).get_oids(); } @@ -195,7 +231,7 @@ std::vector<OID> PKCS10_Request::ex_constraints() const */ bool PKCS10_Request::is_CA() const { - if(auto ext = m_extensions.get(OIDS::lookup("X509v3.BasicConstraints"))) + if(auto ext = extensions().get(OIDS::lookup("X509v3.BasicConstraints"))) { return dynamic_cast<Cert_Extension::Basic_Constraints&>(*ext).get_is_ca(); } @@ -208,7 +244,7 @@ bool PKCS10_Request::is_CA() const */ size_t PKCS10_Request::path_limit() const { - if(auto ext = m_extensions.get(OIDS::lookup("X509v3.BasicConstraints"))) + if(auto ext = extensions().get(OIDS::lookup("X509v3.BasicConstraints"))) { Cert_Extension::Basic_Constraints& basic_constraints = dynamic_cast<Cert_Extension::Basic_Constraints&>(*ext); if(basic_constraints.get_is_ca()) @@ -220,12 +256,4 @@ size_t PKCS10_Request::path_limit() const return 0; } -/* -* Return the X509v3 extensions -*/ -Extensions PKCS10_Request::extensions() const - { - return m_extensions; - } - } diff --git a/src/lib/x509/pkcs10.h b/src/lib/x509/pkcs10.h index 973b91b8a..abed5fa75 100644 --- a/src/lib/x509/pkcs10.h +++ b/src/lib/x509/pkcs10.h @@ -12,7 +12,6 @@ #include <botan/x509_obj.h> #include <botan/x509_dn.h> #include <botan/x509_ext.h> -#include <botan/datastor.h> #include <botan/key_constraint.h> #include <botan/asn1_attribute.h> #include <botan/asn1_alt_name.h> @@ -20,6 +19,8 @@ namespace Botan { +struct PKCS10_Data; + /** * PKCS #10 Certificate Request. */ @@ -36,19 +37,19 @@ class BOTAN_PUBLIC_API(2,0) PKCS10_Request final : public X509_Object * Get the raw DER encoded public key. * @return raw DER encoded public key */ - std::vector<uint8_t> raw_public_key() const; + const std::vector<uint8_t>& raw_public_key() const; /** * Get the subject DN. * @return subject DN */ - X509_DN subject_dn() const; + const X509_DN& subject_dn() const; /** * Get the subject alternative name. * @return subject alternative name. */ - AlternativeName subject_alt_name() const; + const AlternativeName& subject_alt_name() const; /** * Get the key constraints for the key associated with this @@ -86,7 +87,7 @@ class BOTAN_PUBLIC_API(2,0) PKCS10_Request final : public X509_Object * Get the X509v3 extensions. * @return X509v3 extensions */ - Extensions extensions() const; + const Extensions& extensions() const; /** * Create a PKCS#10 Request from a data source. @@ -110,10 +111,10 @@ class BOTAN_PUBLIC_API(2,0) PKCS10_Request final : public X509_Object explicit PKCS10_Request(const std::vector<uint8_t>& vec); private: void force_decode() override; - void handle_attribute(const Attribute&); - Data_Store m_info; - Extensions m_extensions; + const PKCS10_Data& data() const; + + std::shared_ptr<PKCS10_Data> m_data; }; } diff --git a/src/lib/x509/x509_ca.cpp b/src/lib/x509/x509_ca.cpp index 81536c768..682002111 100644 --- a/src/lib/x509/x509_ca.cpp +++ b/src/lib/x509/x509_ca.cpp @@ -25,12 +25,14 @@ namespace Botan { X509_CA::X509_CA(const X509_Certificate& c, const Private_Key& key, const std::string& hash_fn, - RandomNumberGenerator& rng) : m_ca_cert(c) + RandomNumberGenerator& rng) : + m_ca_cert(c), + m_hash_fn(hash_fn) { if(!m_ca_cert.is_CA_cert()) throw Invalid_Argument("X509_CA: This certificate is not for a CA"); - m_signer.reset(choose_sig_format(key, rng, hash_fn, m_ca_sig_algo)); + m_signer.reset(choose_sig_format(key, rng, m_hash_fn, m_ca_sig_algo)); } /* diff --git a/src/lib/x509/x509_ca.h b/src/lib/x509/x509_ca.h index 7859a2058..cd122a6fc 100644 --- a/src/lib/x509/x509_ca.h +++ b/src/lib/x509/x509_ca.h @@ -116,7 +116,13 @@ class BOTAN_PUBLIC_API(2,0) X509_CA final X509_CA(const X509_CA&) = delete; X509_CA& operator=(const X509_CA&) = delete; +#if !defined(BOTAN_BUILD_COMPILER_IS_MSVC_2013) + X509_CA(X509_CA&&) = default; + X509_CA& operator=(X509_CA&&) = default; +#endif + ~X509_CA(); + private: X509_CRL make_crl(const std::vector<CRL_Entry>& entries, uint32_t crl_number, uint32_t next_update, @@ -124,6 +130,7 @@ class BOTAN_PUBLIC_API(2,0) X509_CA final AlgorithmIdentifier m_ca_sig_algo; X509_Certificate m_ca_cert; + std::string m_hash_fn; std::unique_ptr<PK_Signer> m_signer; }; diff --git a/src/lib/x509/x509_crl.cpp b/src/lib/x509/x509_crl.cpp index e27733d32..4a6c4249a 100644 --- a/src/lib/x509/x509_crl.cpp +++ b/src/lib/x509/x509_crl.cpp @@ -12,11 +12,24 @@ namespace Botan { +struct CRL_Data + { + X509_DN m_issuer; + X509_Time m_this_update; + X509_Time m_next_update; + std::vector<CRL_Entry> m_entries; + Extensions m_extensions; + + // cached values from extensions + size_t m_crl_number = 0; + std::vector<uint8_t> m_auth_key_id; + }; + /* * Load a X.509 CRL */ -X509_CRL::X509_CRL(DataSource& in, bool touc) : - X509_Object(in, "X509 CRL/CRL"), m_throw_on_unknown_critical(touc) +X509_CRL::X509_CRL(DataSource& in) : + X509_Object(in, "X509 CRL/CRL") { do_decode(); } @@ -25,26 +38,30 @@ X509_CRL::X509_CRL(DataSource& in, bool touc) : /* * Load a X.509 CRL */ -X509_CRL::X509_CRL(const std::string& fsname, bool touc) : - X509_Object(fsname, "CRL/X509 CRL"), m_throw_on_unknown_critical(touc) +X509_CRL::X509_CRL(const std::string& fsname) : + X509_Object(fsname, "CRL/X509 CRL") { do_decode(); } #endif -X509_CRL::X509_CRL(const std::vector<uint8_t>& in, bool touc) : - X509_Object(in, "CRL/X509 CRL"), m_throw_on_unknown_critical(touc) +X509_CRL::X509_CRL(const std::vector<uint8_t>& in) : + X509_Object(in, "CRL/X509 CRL") { do_decode(); } -X509_CRL::X509_CRL(const X509_DN& issuer, const X509_Time& thisUpdate, - const X509_Time& nextUpdate, const std::vector<CRL_Entry>& revoked) : - X509_Object(), m_throw_on_unknown_critical(false), m_revoked(revoked) +X509_CRL::X509_CRL(const X509_DN& issuer, + const X509_Time& this_update, + const X509_Time& next_update, + const std::vector<CRL_Entry>& revoked) : + X509_Object() { - m_info.add(issuer.contents()); - m_info.add("X509.CRL.start", thisUpdate.to_string()); - m_info.add("X509.CRL.end", nextUpdate.to_string()); + m_data.reset(new CRL_Data); + m_data->m_issuer = issuer; + m_data->m_this_update = this_update; + m_data->m_next_update = next_update; + m_data->m_entries = revoked; } /** @@ -63,18 +80,21 @@ bool X509_CRL::is_revoked(const X509_Certificate& cert) const std::vector<uint8_t> cert_akid = cert.authority_key_id(); if(!crl_akid.empty() && !cert_akid.empty()) + { if(crl_akid != cert_akid) return false; + } std::vector<uint8_t> cert_serial = cert.serial_number(); bool is_revoked = false; - for(size_t i = 0; i != m_revoked.size(); ++i) + // FIXME would be nice to avoid a linear scan here - maybe sort the entries? + for(const CRL_Entry& entry : get_revoked()) { - if(cert_serial == m_revoked[i].serial_number()) + if(cert_serial == entry.serial_number()) { - if(m_revoked[i].reason_code() == REMOVE_FROM_CRL) + if(entry.reason_code() == REMOVE_FROM_CRL) is_revoked = false; else is_revoked = true; @@ -87,31 +107,31 @@ bool X509_CRL::is_revoked(const X509_Certificate& cert) const /* * Decode the TBSCertList data */ -void X509_CRL::force_decode() +namespace { + +std::unique_ptr<CRL_Data> decode_crl_body(const std::vector<uint8_t>& body, + const AlgorithmIdentifier& sig_algo) { - BER_Decoder tbs_crl(signed_body()); + std::unique_ptr<CRL_Data> data(new CRL_Data); + + BER_Decoder tbs_crl(body); size_t version; tbs_crl.decode_optional(version, INTEGER, UNIVERSAL); if(version != 0 && version != 1) - throw X509_CRL_Error("Unknown X.509 CRL version " + + throw X509_CRL::X509_CRL_Error("Unknown X.509 CRL version " + std::to_string(version+1)); AlgorithmIdentifier sig_algo_inner; tbs_crl.decode(sig_algo_inner); - if(signature_algorithm() != sig_algo_inner) - throw X509_CRL_Error("Algorithm identifier mismatch"); - - X509_DN dn_issuer; - tbs_crl.decode(dn_issuer); - m_info.add(dn_issuer.contents()); + if(sig_algo != sig_algo_inner) + throw X509_CRL::X509_CRL_Error("Algorithm identifier mismatch"); - X509_Time start, end; - tbs_crl.decode(start).decode(end); - m_info.add("X509.CRL.start", start.to_string()); - m_info.add("X509.CRL.end", end.to_string()); + tbs_crl.decode(data->m_issuer) + .decode(data->m_this_update) + .decode(data->m_next_update); BER_Object next = tbs_crl.get_next_object(); @@ -121,9 +141,9 @@ void X509_CRL::force_decode() while(cert_list.more_items()) { - CRL_Entry entry(m_throw_on_unknown_critical); + CRL_Entry entry; cert_list.decode(entry); - m_revoked.push_back(entry); + data->m_entries.push_back(entry); } next = tbs_crl.get_next_object(); } @@ -132,44 +152,59 @@ void X509_CRL::force_decode() next.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { BER_Decoder crl_options(next.value); - - Extensions extensions(m_throw_on_unknown_critical); - - crl_options.decode(extensions).verify_end(); - - extensions.contents_to(m_info, m_info); - + crl_options.decode(data->m_extensions).verify_end(); next = tbs_crl.get_next_object(); } if(next.type_tag != NO_OBJECT) - throw X509_CRL_Error("Unknown tag in CRL"); + throw X509_CRL::X509_CRL_Error("Unknown tag in CRL"); tbs_crl.verify_end(); + + return data; + } + +} + +void X509_CRL::force_decode() + { + m_data.reset(decode_crl_body(signed_body(), signature_algorithm()).release()); + } + +const CRL_Data& X509_CRL::data() const + { + if(!m_data) + throw Decoding_Error("Error decoding X509 CRL"); + return *m_data.get(); + } + +const Extensions& X509_CRL::extensions() const + { + return data().m_extensions; } /* * Return the list of revoked certificates */ -std::vector<CRL_Entry> X509_CRL::get_revoked() const +const std::vector<CRL_Entry>& X509_CRL::get_revoked() const { - return m_revoked; + return data().m_entries; } /* * Return the distinguished name of the issuer */ -X509_DN X509_CRL::issuer_dn() const +const X509_DN& X509_CRL::issuer_dn() const { - return create_dn(m_info); + return data().m_issuer; } /* * Return the key identifier of the issuer */ -std::vector<uint8_t> X509_CRL::authority_key_id() const +const std::vector<uint8_t>& X509_CRL::authority_key_id() const { - return m_info.get1_memvec("X509v3.AuthorityKeyIdentifier"); + return data().m_auth_key_id; } /* @@ -177,23 +212,23 @@ std::vector<uint8_t> X509_CRL::authority_key_id() const */ uint32_t X509_CRL::crl_number() const { - return m_info.get1_uint32("X509v3.CRLNumber"); + return data().m_crl_number; } /* * Return the issue data of the CRL */ -X509_Time X509_CRL::this_update() const +const X509_Time& X509_CRL::this_update() const { - return X509_Time(m_info.get1("X509.CRL.start"), ASN1_Tag::UTC_OR_GENERALIZED_TIME); + return data().m_this_update; } /* * Return the date when a new CRL will be issued */ -X509_Time X509_CRL::next_update() const +const X509_Time& X509_CRL::next_update() const { - return X509_Time(m_info.get1("X509.CRL.end"), ASN1_Tag::UTC_OR_GENERALIZED_TIME); + return data().m_next_update; } } diff --git a/src/lib/x509/x509_crl.h b/src/lib/x509/x509_crl.h index 865117300..c3c986cc1 100644 --- a/src/lib/x509/x509_crl.h +++ b/src/lib/x509/x509_crl.h @@ -11,11 +11,12 @@ #include <botan/x509_obj.h> #include <botan/x509_dn.h> #include <botan/crl_ent.h> -#include <botan/datastor.h> #include <vector> namespace Botan { +class CRL_Data; +class Extensions; class X509_Certificate; /** @@ -43,19 +44,24 @@ class BOTAN_PUBLIC_API(2,0) X509_CRL final : public X509_Object * Get the entries of this CRL in the form of a vector. * @return vector containing the entries of this CRL. */ - std::vector<CRL_Entry> get_revoked() const; + const std::vector<CRL_Entry>& get_revoked() const; /** * Get the issuer DN of this CRL. * @return CRLs issuer DN */ - X509_DN issuer_dn() const; + const X509_DN& issuer_dn() const; + + /** + * @return extension data for this CRL + */ + const Extensions& extensions() const; /** * Get the AuthorityKeyIdentifier of this CRL. * @return this CRLs AuthorityKeyIdentifier */ - std::vector<uint8_t> authority_key_id() const; + const std::vector<uint8_t>& authority_key_id() const; /** * Get the serial number of this CRL. @@ -67,41 +73,33 @@ class BOTAN_PUBLIC_API(2,0) X509_CRL final : public X509_Object * Get the CRL's thisUpdate value. * @return CRLs thisUpdate */ - X509_Time this_update() const; + const X509_Time& this_update() const; /** * Get the CRL's nextUpdate value. * @return CRLs nextdUpdate */ - X509_Time next_update() const; + const X509_Time& next_update() const; /** * Construct a CRL from a data source. * @param source the data source providing the DER or PEM encoded CRL. - * @param throw_on_unknown_critical should we throw an exception - * if an unknown CRL extension marked as critical is encountered. */ - X509_CRL(DataSource& source, bool throw_on_unknown_critical = false); + X509_CRL(DataSource& source); #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) /** * Construct a CRL from a file containing the DER or PEM encoded CRL. * @param filename the name of the CRL file - * @param throw_on_unknown_critical should we throw an exception - * if an unknown CRL extension marked as critical is encountered. */ - X509_CRL(const std::string& filename, - bool throw_on_unknown_critical = false); + X509_CRL(const std::string& filename); #endif /** * Construct a CRL from a binary vector * @param vec the binary (DER) representation of the CRL - * @param throw_on_unknown_critical should we throw an exception - * if an unknown CRL extension marked as critical is encountered. */ - X509_CRL(const std::vector<uint8_t>& vec, - bool throw_on_unknown_critical = false); + X509_CRL(const std::vector<uint8_t>& vec); /** * Construct a CRL @@ -116,9 +114,9 @@ class BOTAN_PUBLIC_API(2,0) X509_CRL final : public X509_Object private: void force_decode() override; - bool m_throw_on_unknown_critical; - std::vector<CRL_Entry> m_revoked; - Data_Store m_info; + const CRL_Data& data() const; + + std::shared_ptr<CRL_Data> m_data; }; } diff --git a/src/lib/asn1/x509_dn.cpp b/src/lib/x509/x509_dn.cpp index dd92b25ec..d07344aae 100644 --- a/src/lib/asn1/x509_dn.cpp +++ b/src/lib/x509/x509_dn.cpp @@ -79,10 +79,34 @@ std::multimap<std::string, std::string> X509_DN::contents() const { std::multimap<std::string, std::string> retval; for(auto i = m_dn_info.begin(); i != m_dn_info.end(); ++i) - multimap_insert(retval, OIDS::lookup(i->first), i->second.value()); + { + std::string str_value = OIDS::oid2str(i->first); + + if(str_value.empty()) + str_value = i->first.as_string(); + multimap_insert(retval, str_value, i->second.value()); + } return retval; } +bool X509_DN::has_field(const std::string& attr) const + { + const OID oid = OIDS::lookup(deref_info_field(attr)); + auto range = m_dn_info.equal_range(oid); + return (range.first != range.second); + } + +std::string X509_DN::get_first_attribute(const std::string& attr) const + { + const OID oid = OIDS::lookup(deref_info_field(attr)); + + auto i = m_dn_info.lower_bound(oid); + if(i != m_dn_info.end() && i->first == oid) + return i->second.value(); + + return ""; + } + /* * Get a single attribute type */ @@ -98,10 +122,7 @@ std::vector<std::string> X509_DN::get_attribute(const std::string& attr) const return values; } -/* -* Return the BER encoded data, if any -*/ -std::vector<uint8_t> X509_DN::get_bits() const +const std::vector<uint8_t>& X509_DN::get_bits() const { return m_dn_bits; } @@ -278,6 +299,9 @@ std::string to_short_form(const std::string& long_id) if(long_id == "X520.CommonName") return "CN"; + if(long_id == "X520.Country") + return "C"; + if(long_id == "X520.Organization") return "O"; diff --git a/src/lib/asn1/x509_dn.h b/src/lib/x509/x509_dn.h index 09f8cf16b..cbd89de7c 100644 --- a/src/lib/asn1/x509_dn.h +++ b/src/lib/x509/x509_dn.h @@ -25,23 +25,29 @@ class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object void encode_into(class DER_Encoder&) const override; void decode_from(class BER_Decoder&) override; - std::multimap<OID, std::string> get_attributes() const; - std::vector<std::string> get_attribute(const std::string&) const; + bool has_field(const std::string& attr) const; + std::vector<std::string> get_attribute(const std::string& attr) const; + + std::string get_first_attribute(const std::string& attr) const; + std::multimap<OID, std::string> get_attributes() const; std::multimap<std::string, std::string> contents() const; - void add_attribute(const std::string&, const std::string&); - void add_attribute(const OID&, const std::string&); + void add_attribute(const std::string& key, const std::string& val); + void add_attribute(const OID& oid, const std::string& val); - static std::string deref_info_field(const std::string&); + static std::string deref_info_field(const std::string& key); - std::vector<uint8_t> get_bits() const; + /* + * Return the BER encoded data, if any + */ + const std::vector<uint8_t>& get_bits() const; bool empty() const { return m_dn_info.empty(); } X509_DN() = default; - explicit X509_DN(const std::multimap<OID, std::string>&); - explicit X509_DN(const std::multimap<std::string, std::string>&); + explicit X509_DN(const std::multimap<OID, std::string>& vals); + explicit X509_DN(const std::multimap<std::string, std::string>& vals); private: std::multimap<OID, ASN1_String> m_dn_info; std::vector<uint8_t> m_dn_bits; diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index 6e4c29d42..682cc1cc7 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -8,10 +8,11 @@ #include <botan/x509_ext.h> #include <botan/x509cert.h> -#include <botan/sha160.h> +#include <botan/datastor.h> #include <botan/der_enc.h> #include <botan/ber_dec.h> #include <botan/oids.h> +#include <botan/hash.h> #include <botan/internal/bit_ops.h> #include <algorithm> #include <sstream> @@ -19,138 +20,177 @@ namespace Botan { /* -* List of X.509 Certificate Extensions +* Create a Certificate_Extension object of some kind to handle */ -Certificate_Extension* Extensions::create_extension(const OID& oid, bool critical) +Certificate_Extension* +Extensions::create_extn_obj(const OID& oid, + bool critical, + const std::vector<uint8_t>& body) { -#define X509_EXTENSION(NAME, TYPE) \ - if(oid == OIDS::lookup(NAME)) { return new Cert_Extension::TYPE(); } + const std::string oid_str = oid.as_string(); - X509_EXTENSION("X509v3.KeyUsage", Key_Usage); - X509_EXTENSION("X509v3.BasicConstraints", Basic_Constraints); - X509_EXTENSION("X509v3.SubjectKeyIdentifier", Subject_Key_ID); - X509_EXTENSION("X509v3.AuthorityKeyIdentifier", Authority_Key_ID); - 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); - X509_EXTENSION("X509v3.CRLNumber", CRL_Number); - X509_EXTENSION("X509v3.ReasonCode", CRL_ReasonCode); + Certificate_Extension* extn = nullptr; - return critical ? new Cert_Extension::Unknown_Critical_Extension(oid) : nullptr; + if(oid == Cert_Extension::Subject_Key_ID::static_oid()) + { + extn = new Cert_Extension::Subject_Key_ID; + } + else if(oid == Cert_Extension::Key_Usage::static_oid()) + { + extn = new Cert_Extension::Key_Usage; + } + else if(oid == Cert_Extension::Subject_Alternative_Name::static_oid()) + { + extn = new Cert_Extension::Subject_Alternative_Name; + } + else if(oid == Cert_Extension::Issuer_Alternative_Name::static_oid()) + { + extn = new Cert_Extension::Issuer_Alternative_Name; + } + else if(oid == Cert_Extension::Basic_Constraints::static_oid()) + { + extn = new Cert_Extension::Basic_Constraints; + } + else if(oid == Cert_Extension::CRL_Number::static_oid()) + { + extn = new Cert_Extension::CRL_Number; + } + else if(oid == Cert_Extension::CRL_ReasonCode::static_oid()) + { + extn = new Cert_Extension::CRL_ReasonCode; + } + else if(oid == Cert_Extension::Authority_Key_ID::static_oid()) + { + extn = new Cert_Extension::Authority_Key_ID; + } + else if(oid == Cert_Extension::Name_Constraints::static_oid()) + { + extn = new Cert_Extension::Name_Constraints; + } + else if(oid == Cert_Extension::CRL_Distribution_Points::static_oid()) + { + extn = new Cert_Extension::CRL_Distribution_Points; + } + else if(oid == Cert_Extension::Certificate_Policies::static_oid()) + { + extn = new Cert_Extension::Certificate_Policies; + } + else if(oid == Cert_Extension::Extended_Key_Usage::static_oid()) + { + extn = new Cert_Extension::Extended_Key_Usage; + } + else if(oid == Cert_Extension::Authority_Information_Access::static_oid()) + { + extn = new Cert_Extension::Authority_Information_Access; + } + else + { + // some other unknown extension type + extn = new Cert_Extension::Unknown_Extension(oid, critical); + } + + try + { + extn->decode_inner(body); + } + catch(Decoding_Error& e) + { + throw Decoding_Error("Decoding X.509 extension " + oid.as_string() + " failed", e.what()); + } + return extn; } /* -* Extensions Copy Constructor +* Validate the extension (the default implementation is a NOP) */ -Extensions::Extensions(const Extensions& extensions) : ASN1_Object() +void Certificate_Extension::validate(const X509_Certificate&, const X509_Certificate&, + const std::vector<std::shared_ptr<const X509_Certificate>>&, + std::vector<std::set<Certificate_Status_Code>>&, + size_t) { - *this = extensions; } /* -* Extensions Assignment Operator +* Add a new cert */ -Extensions& Extensions::operator=(const Extensions& other) +void Extensions::add(Certificate_Extension* extn, bool critical) { - if(this == &other) - return *this; - - m_extensions.clear(); - - for(size_t i = 0; i != other.m_extensions.size(); ++i) - m_extensions.push_back( - 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; + // sanity check: we don't want to have the same extension more than once + if(m_extension_info.count(extn->oid_of()) > 0) + throw Invalid_Argument(extn->oid_name() + " extension already present in Extensions::add"); - return (*this); + const OID oid = extn->oid_of(); + Extensions_Info info(critical, extn); + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); } -/* -* Return the OID of this extension -*/ -OID Certificate_Extension::oid_of() const +void Extensions::replace(Certificate_Extension* extn, bool critical) { - return OIDS::lookup(oid_name()); + // Remove it if it existed + m_extension_info.erase(extn->oid_of()); + + const OID oid = extn->oid_of(); + Extensions_Info info(critical, extn); + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); } -/* -* Validate the extension (the default implementation is a NOP) -*/ -void Certificate_Extension::validate(const X509_Certificate&, const X509_Certificate&, - const std::vector<std::shared_ptr<const X509_Certificate>>&, - std::vector<std::set<Certificate_Status_Code>>&, - size_t) +bool Extensions::extension_set(const OID& oid) const { + return (m_extension_info.find(oid) != m_extension_info.end()); } -void Extensions::add(Certificate_Extension* extn, bool critical) +bool Extensions::critical_extension_set(const OID& oid) const { - // sanity check: we don't want to have the same extension more than once - for(const auto& ext : m_extensions) - { - if(ext.first->oid_of() == extn->oid_of()) - { - throw Invalid_Argument(extn->oid_name() + " extension already present"); - } - } - - if(m_extensions_raw.count(extn->oid_of()) > 0) - { - throw Invalid_Argument(extn->oid_name() + " extension already present"); - } - - 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)); + auto i = m_extension_info.find(oid); + if(i != m_extension_info.end()) + return i->second.is_critical(); + return false; } -void Extensions::replace(Certificate_Extension* extn, bool critical) +const Certificate_Extension* Extensions::get_extension_object(const OID& oid) const { - for(auto it = m_extensions.begin(); it != m_extensions.end(); ++it) - { - if(it->first->oid_of() == extn->oid_of()) - { - m_extensions.erase(it); - break; - } - } + auto extn = m_extension_info.find(oid); + if(extn == m_extension_info.end()) + return nullptr; - m_extensions.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(extn), critical)); - m_extensions_raw[extn->oid_of()] = std::make_pair(extn->encode_inner(), critical); + return &extn->second.obj(); } std::unique_ptr<Certificate_Extension> Extensions::get(const OID& oid) const { - for(auto& ext : m_extensions) + if(const Certificate_Extension* ext = this->get_extension_object(oid)) { - if(ext.first->oid_of() == oid) - { - return std::unique_ptr<Certificate_Extension>(ext.first->copy()); - } + return std::unique_ptr<Certificate_Extension>(ext->copy()); } - return nullptr; } 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) + for(auto&& ext : m_extension_info) { - exts.push_back(std::make_pair(std::unique_ptr<Certificate_Extension>(ext.first->copy()), ext.second)); + exts.push_back( + std::make_pair( + std::unique_ptr<Certificate_Extension>(ext.second.obj().copy()), + ext.second.is_critical()) + ); } return exts; } std::map<OID, std::pair<std::vector<uint8_t>, bool>> Extensions::extensions_raw() const { - return m_extensions_raw; + std::map<OID, std::pair<std::vector<uint8_t>, bool>> out; + for(auto&& ext : m_extension_info) + { + out.emplace(ext.first, + std::make_pair(ext.second.bits(), + ext.second.is_critical())); + } + return out; } /* @@ -158,44 +198,20 @@ std::map<OID, std::pair<std::vector<uint8_t>, bool>> Extensions::extensions_raw( */ void Extensions::encode_into(DER_Encoder& to_object) const { - // encode any known extensions - for(size_t i = 0; i != m_extensions.size(); ++i) + for(auto ext_info : m_extension_info) { - const Certificate_Extension* ext = m_extensions[i].first.get(); - const bool is_critical = m_extensions[i].second; - - const bool should_encode = ext->should_encode(); + const OID& oid = ext_info.first; + const bool should_encode = ext_info.second.obj().should_encode(); if(should_encode) { - to_object.start_cons(SEQUENCE) - .encode(ext->oid_of()) - .encode_optional(is_critical, false) - .encode(ext->encode_inner(), OCTET_STRING) - .end_cons(); - } - } - - // encode any unknown extensions - for(const auto& ext_raw : m_extensions_raw) - { - const bool is_critical = ext_raw.second.second; - const OID oid = ext_raw.first; - const std::vector<uint8_t> value = ext_raw.second.first; - - auto pos = std::find_if(std::begin(m_extensions), std::end(m_extensions), - [&oid](const std::pair<std::unique_ptr<Certificate_Extension>, bool>& ext) -> bool - { - return ext.first->oid_of() == oid; - }); + const bool is_critical = ext_info.second.is_critical(); + const std::vector<uint8_t>& ext_value = ext_info.second.bits(); - if(pos == std::end(m_extensions)) - { - // not found in m_extensions, must be unknown to_object.start_cons(SEQUENCE) .encode(oid) .encode_optional(is_critical, false) - .encode(value, OCTET_STRING) + .encode(ext_value, OCTET_STRING) .end_cons(); } } @@ -206,47 +222,29 @@ void Extensions::encode_into(DER_Encoder& to_object) const */ void Extensions::decode_from(BER_Decoder& from_source) { - m_extensions.clear(); - m_extensions_raw.clear(); + m_extension_oids.clear(); + m_extension_info.clear(); BER_Decoder sequence = from_source.start_cons(SEQUENCE); while(sequence.more_items()) { OID oid; - std::vector<uint8_t> value; bool critical; + std::vector<uint8_t> bits; sequence.start_cons(SEQUENCE) - .decode(oid) - .decode_optional(critical, BOOLEAN, UNIVERSAL, false) - .decode(value, OCTET_STRING) - .end_cons(); - - m_extensions_raw.emplace(oid, std::make_pair(value, critical)); - - std::unique_ptr<Certificate_Extension> ext(create_extension(oid, critical)); + .decode(oid) + .decode_optional(critical, BOOLEAN, UNIVERSAL, false) + .decode(bits, OCTET_STRING) + .end_cons(); - if(!ext && critical && m_throw_on_unknown_critical) - throw Decoding_Error("Encountered unknown X.509 extension marked " - "as critical; OID = " + oid.as_string()); + Extensions_Info info(critical, bits, + create_extn_obj(oid, critical, bits)); - if(ext) - { - try - { - ext->decode_inner(value); - } - catch(std::exception& e) - { - throw Decoding_Error("Exception while decoding extension " + - oid.as_string() + ": " + e.what()); - } - - m_extensions.push_back(std::make_pair(std::move(ext), critical)); - } + m_extension_oids.push_back(oid); + m_extension_info.emplace(oid, info); } - sequence.verify_end(); } @@ -256,14 +254,14 @@ void Extensions::decode_from(BER_Decoder& from_source) void Extensions::contents_to(Data_Store& subject_info, Data_Store& issuer_info) const { - for(size_t i = 0; i != m_extensions.size(); ++i) + for(auto&& m_extn_info : m_extension_info) { - 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)); + m_extn_info.second.obj().contents_to(subject_info, issuer_info); + subject_info.add(m_extn_info.second.obj().oid_name() + ".is_critical", + m_extn_info.second.is_critical()); } } - namespace Cert_Extension { /* @@ -402,8 +400,15 @@ void Subject_Key_ID::contents_to(Data_Store& subject, Data_Store&) const /* * Subject_Key_ID Constructor */ -Subject_Key_ID::Subject_Key_ID(const std::vector<uint8_t>& pub_key) : m_key_id(unlock(SHA_160().process(pub_key))) - {} +Subject_Key_ID::Subject_Key_ID(const std::vector<uint8_t>& pub_key, const std::string& hash_name) + { + std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_name)); + + m_key_id.resize(hash->output_length()); + + hash->update(pub_key); + hash->final(m_key_id.data()); + } /* * Encode the extension @@ -439,61 +444,50 @@ void Authority_Key_ID::contents_to(Data_Store&, Data_Store& issuer) const /* * Encode the extension */ -std::vector<uint8_t> Alternative_Name::encode_inner() const +std::vector<uint8_t> Subject_Alternative_Name::encode_inner() const { return DER_Encoder().encode(m_alt_name).get_contents_unlocked(); } /* -* Decode the extension +* Encode the extension */ -void Alternative_Name::decode_inner(const std::vector<uint8_t>& in) +std::vector<uint8_t> Issuer_Alternative_Name::encode_inner() const { - BER_Decoder(in).decode(m_alt_name); + return DER_Encoder().encode(m_alt_name).get_contents_unlocked(); } /* -* Return a textual representation +* Decode the extension */ -void Alternative_Name::contents_to(Data_Store& subject_info, - Data_Store& issuer_info) const +void Subject_Alternative_Name::decode_inner(const std::vector<uint8_t>& in) { - std::multimap<std::string, std::string> contents = - get_alt_name().contents(); - - if(m_oid_name_str == "X509v3.SubjectAlternativeName") - subject_info.add(contents); - else if(m_oid_name_str == "X509v3.IssuerAlternativeName") - issuer_info.add(contents); - else - throw Internal_Error("In Alternative_Name, unknown type " + - m_oid_name_str); + BER_Decoder(in).decode(m_alt_name); } /* -* Alternative_Name Constructor +* Decode the extension */ -Alternative_Name::Alternative_Name(const AlternativeName& alt_name, - const std::string& oid_name_str) : - m_oid_name_str(oid_name_str), - m_alt_name(alt_name) - {} +void Issuer_Alternative_Name::decode_inner(const std::vector<uint8_t>& in) + { + BER_Decoder(in).decode(m_alt_name); + } /* -* Subject_Alternative_Name Constructor +* Return a textual representation */ -Subject_Alternative_Name::Subject_Alternative_Name( - const AlternativeName& name) : - Alternative_Name(name, "X509v3.SubjectAlternativeName") +void Subject_Alternative_Name::contents_to(Data_Store& subject_info, + Data_Store&) const { + subject_info.add(get_alt_name().contents()); } /* -* Issuer_Alternative_Name Constructor +* Return a textual representation */ -Issuer_Alternative_Name::Issuer_Alternative_Name(const AlternativeName& name) : - Alternative_Name(name, "X509v3.IssuerAlternativeName") +void Issuer_Alternative_Name::contents_to(Data_Store&, Data_Store& issuer_info) const { + issuer_info.add(get_alt_name().contents()); } /* @@ -849,22 +843,26 @@ std::vector<uint8_t> CRL_Distribution_Points::encode_inner() const void CRL_Distribution_Points::decode_inner(const std::vector<uint8_t>& buf) { - BER_Decoder(buf).decode_list(m_distribution_points).verify_end(); - } + BER_Decoder(buf) + .decode_list(m_distribution_points) + .verify_end(); -void CRL_Distribution_Points::contents_to(Data_Store& info, Data_Store&) const - { for(size_t i = 0; i != m_distribution_points.size(); ++i) { auto point = m_distribution_points[i].point().contents(); auto uris = point.equal_range("URI"); - for(auto uri = uris.first; uri != uris.second; ++uri) - info.add("CRL.DistributionPoint", uri->second); + m_crl_distribution_urls.push_back(uri->second); } } +void CRL_Distribution_Points::contents_to(Data_Store& subject, Data_Store&) const + { + for(const std::string& crl_url : m_crl_distribution_urls) + subject.add("CRL.DistributionPoint", crl_url); + } + void CRL_Distribution_Points::Distribution_Point::encode_into(class DER_Encoder&) const { throw Not_Implemented("CRL_Distribution_Points encoding"); @@ -880,17 +878,20 @@ void CRL_Distribution_Points::Distribution_Point::decode_from(class BER_Decoder& .end_cons().end_cons(); } -std::vector<uint8_t> Unknown_Critical_Extension::encode_inner() const +std::vector<uint8_t> Unknown_Extension::encode_inner() const { - throw Not_Implemented("Unknown_Critical_Extension encoding"); + return m_bytes; } -void Unknown_Critical_Extension::decode_inner(const std::vector<uint8_t>&) +void Unknown_Extension::decode_inner(const std::vector<uint8_t>& bytes) { + // Just treat as an opaque blob at this level + m_bytes = bytes; } -void Unknown_Critical_Extension::contents_to(Data_Store&, Data_Store&) const +void Unknown_Extension::contents_to(Data_Store&, Data_Store&) const { + // No information store } } diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index 69647616f..2243d6deb 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -12,7 +12,6 @@ #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> @@ -20,6 +19,7 @@ namespace Botan { +class Data_Store; class X509_Certificate; /** @@ -31,7 +31,15 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Extension /** * @return OID representing this extension */ - virtual OID oid_of() const; + virtual OID oid_of() const = 0; + + /* + * @return specific OID name + * If possible OIDS table should match oid_name to OIDS, ie + * OIDS::lookup(ext->oid_name()) == ext->oid_of() + * Should return empty string if OID is not known + */ + virtual std::string oid_name() const = 0; /** * Make a copy of this extension @@ -49,11 +57,6 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Extension Data_Store& issuer) const = 0; /* - * @return specific OID name - */ - virtual std::string oid_name() const = 0; - - /* * Callback visited during path validation. * * An extension can implement this callback to inspect @@ -87,13 +90,65 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Extension class BOTAN_PUBLIC_API(2,0) Extensions final : public ASN1_Object { public: + /** + * Look up an object in the extensions, based on OID Returns + * nullptr if not set, if the extension was either absent or not + * handled. The pointer returned is owned by the Extensions + * object. + * This would be better with an optional<T> return value + */ + const Certificate_Extension* get_extension_object(const OID& oid) const; + + template<typename T> + const T* get_extension_object_as(const OID& oid = T::static_oid()) const + { + if(const Certificate_Extension* extn = get_extension_object(oid)) + { + if(const T* extn_as_T = dynamic_cast<const T*>(extn)) + { + return extn_as_T; + } + else + { + throw Exception("Exception::get_extension_object_as dynamic_cast failed"); + } + } + + return nullptr; + } + + /** + * Return the set of extensions in the order they appeared in the certificate + * (or as they were added, if constructed) + */ + const std::vector<OID>& get_extension_oids() const + { + return m_extension_oids; + } + + /** + * Return true if an extension was set + */ + bool extension_set(const OID& oid) const; + + /** + * Return true if an extesion was set and marked critical + */ + bool critical_extension_set(const OID& oid) const; + + /** + * Return the raw bytes of the extension + * Will throw if OID was not set as an extension. + */ + std::vector<uint8_t> get_extension_bits(const OID& oid) const; + void encode_into(class DER_Encoder&) const override; void decode_from(class BER_Decoder&) override; void contents_to(Data_Store&, Data_Store&) const; /** * Adds a new extension to the list. - * @param extn the certificate extension + * @param extn pointer to the certificate extension (Extensions takes ownership) * @param critical whether this extension should be marked as critical * @throw Invalid_Argument if the extension is already present in the list */ @@ -110,67 +165,103 @@ class BOTAN_PUBLIC_API(2,0) Extensions final : public ASN1_Object * Searches for an extension by OID and returns the result. * Only the known extensions types declared in this header * are searched for by this function. - * @return Pointer to extension with oid, nullptr if not found. + * @return Copy of extension with oid, nullptr if not found. + * Can avoid creating a copy by using get_extension_object function */ std::unique_ptr<Certificate_Extension> get(const OID& oid) const; /** - * Searches for an extension by OID and returns the result. - * Only the unknown extensions, that is, extensions - * types that are not declared in this header, are searched - * for by this function. - * @return Pointer to extension with oid, nullptr if not found. + * Searches for an extension by OID and returns the result decoding + * it to some arbitrary extension type chosen by the application. + * + * Only the unknown extensions, that is, extensions types that + * are not declared in this header, are searched for by this + * function. + * + * @return Pointer to new extension with oid, nullptr if not found. */ template<typename T> - std::unique_ptr<T> get_raw(const OID& oid) - { - try + std::unique_ptr<T> get_raw(const OID& oid) const { - if(m_extensions_raw.count(oid) > 0) + auto extn_info = m_extension_info.find(oid); + + if(extn_info != m_extension_info.end()) { - std::unique_ptr<T> ext(new T); - ext->decode_inner(m_extensions_raw[oid].first); - return std::move(ext); + // Unknown_Extension oid_name is empty + if(extn_info->second.obj().oid_name() == "") + { + std::unique_ptr<T> ext(new T); + ext->decode_inner(extn_info->second.bits()); + return std::move(ext); + } } + return nullptr; } - catch(std::exception& e) - { - throw Decoding_Error("Exception while decoding extension " + - oid.as_string() + ": " + e.what()); - } - return nullptr; - } /** - * Returns the list of extensions together with the corresponding - * criticality flag. Only contains the known extensions - * types declared in this header. + * Returns a copy of the list of extensions together with the corresponding + * criticality flag. All extensions are encoded as some object, falling back + * to Unknown_Extension class which simply allows reading the bytes as well + * as the criticality flag. */ std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const; /** * Returns the list of extensions as raw, encoded bytes * together with the corresponding criticality flag. - * Contains all extensions, known as well as unknown extensions. + * Contains all extensions, including any extensions encoded as Unknown_Extension */ std::map<OID, std::pair<std::vector<uint8_t>, bool>> extensions_raw() const; - Extensions& operator=(const Extensions&); + Extensions() {} - Extensions(const Extensions&); + Extensions(const Extensions&) = default; + Extensions& operator=(const Extensions&) = default; - /** - * @param st whether to throw an exception when encountering an unknown - * extension type during decoding - */ - explicit Extensions(bool st = true) : m_throw_on_unknown_critical(st) {} +#if !defined(BOTAN_BUILD_COMPILER_IS_MSVC_2013) + Extensions(Extensions&&) = default; + Extensions& operator=(Extensions&&) = default; +#endif private: - static Certificate_Extension* create_extension(const OID&, bool); + static Certificate_Extension* create_extn_obj(const OID& oid, + bool critical, + const std::vector<uint8_t>& body); - std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> m_extensions; - bool m_throw_on_unknown_critical; - std::map<OID, std::pair<std::vector<uint8_t>, bool>> m_extensions_raw; + class Extensions_Info + { + public: + Extensions_Info(bool critical, + Certificate_Extension* ext) : + m_critical(critical), + m_bits(ext->encode_inner()), + m_obj(ext) + {} + + Extensions_Info(bool critical, + const std::vector<uint8_t>& encoding, + Certificate_Extension* ext) : + m_critical(critical), + m_bits(encoding), + m_obj(ext) + {} + + bool is_critical() const { return m_critical; } + const std::vector<uint8_t>& bits() const { return m_bits; } + const Certificate_Extension& obj() const + { + BOTAN_ASSERT_NONNULL(m_obj.get()); + return *m_obj.get(); + } + + private: + bool m_critical = false; + std::vector<uint8_t> m_bits; + std::shared_ptr<const Certificate_Extension> m_obj; + }; + + std::vector<OID> m_extension_oids; + std::map<OID, Extensions_Info> m_extension_info; }; namespace Cert_Extension { @@ -192,6 +283,9 @@ class BOTAN_PUBLIC_API(2,0) Basic_Constraints final : public Certificate_Extensi bool get_is_ca() const { return m_is_ca; } size_t get_path_limit() const; + static OID static_oid() { return OID("2.5.29.19"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.BasicConstraints"; } @@ -216,6 +310,9 @@ class BOTAN_PUBLIC_API(2,0) Key_Usage final : public Certificate_Extension Key_Constraints get_constraints() const { return m_constraints; } + static OID static_oid() { return OID("2.5.29.15"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.KeyUsage"; } @@ -234,13 +331,21 @@ class BOTAN_PUBLIC_API(2,0) Key_Usage final : public Certificate_Extension class BOTAN_PUBLIC_API(2,0) Subject_Key_ID final : public Certificate_Extension { public: + Subject_Key_ID() = default; + + explicit Subject_Key_ID(const std::vector<uint8_t>& k) : m_key_id(k) {} + + Subject_Key_ID(const std::vector<uint8_t>& public_key, + const std::string& hash_fn); + Subject_Key_ID* copy() const override { return new Subject_Key_ID(m_key_id); } - Subject_Key_ID() = default; - explicit Subject_Key_ID(const std::vector<uint8_t>&); + const std::vector<uint8_t>& get_key_id() const { return m_key_id; } + + static OID static_oid() { return OID("2.5.29.14"); } + OID oid_of() const override { return static_oid(); } - std::vector<uint8_t> get_key_id() const { return m_key_id; } private: std::string oid_name() const override { return "X509v3.SubjectKeyIdentifier"; } @@ -265,7 +370,10 @@ class BOTAN_PUBLIC_API(2,0) Authority_Key_ID final : public Certificate_Extensio Authority_Key_ID() = default; explicit Authority_Key_ID(const std::vector<uint8_t>& k) : m_key_id(k) {} - std::vector<uint8_t> get_key_id() const { return m_key_id; } + const std::vector<uint8_t>& get_key_id() const { return m_key_id; } + + static OID static_oid() { return OID("2.5.29.35"); } + OID oid_of() const override { return static_oid(); } private: std::string oid_name() const override @@ -280,52 +388,59 @@ class BOTAN_PUBLIC_API(2,0) Authority_Key_ID final : public Certificate_Extensio }; /** -* Alternative Name Extension Base Class +* Subject Alternative Name Extension */ -class BOTAN_PUBLIC_API(2,0) Alternative_Name : public Certificate_Extension +class BOTAN_PUBLIC_API(2,4) Subject_Alternative_Name final : public Certificate_Extension { public: - AlternativeName get_alt_name() const { return m_alt_name; } + const AlternativeName& get_alt_name() const { return m_alt_name; } - protected: - Alternative_Name(const AlternativeName&, const std::string& oid_name); + static OID static_oid() { return OID("2.5.29.17"); } + OID oid_of() const override { return static_oid(); } + + Subject_Alternative_Name* copy() const override + { return new Subject_Alternative_Name(get_alt_name()); } - Alternative_Name(const std::string&, const std::string&); + explicit Subject_Alternative_Name(const AlternativeName& name = AlternativeName()) : + m_alt_name(name) {} private: - std::string oid_name() const override { return m_oid_name_str; } + std::string oid_name() const override { return "X509v3.SubjectAlternativeName"; } bool should_encode() const override { return m_alt_name.has_items(); } std::vector<uint8_t> encode_inner() const override; void decode_inner(const std::vector<uint8_t>&) override; void contents_to(Data_Store&, Data_Store&) const override; - std::string m_oid_name_str; AlternativeName m_alt_name; }; /** -* Subject Alternative Name Extension +* Issuer Alternative Name Extension */ -class BOTAN_PUBLIC_API(2,0) Subject_Alternative_Name final : public Alternative_Name +class BOTAN_PUBLIC_API(2,0) Issuer_Alternative_Name final : public Certificate_Extension { public: - Subject_Alternative_Name* copy() const override - { return new Subject_Alternative_Name(get_alt_name()); } + const AlternativeName& get_alt_name() const { return m_alt_name; } - explicit Subject_Alternative_Name(const AlternativeName& = AlternativeName()); - }; + static OID static_oid() { return OID("2.5.29.18"); } + OID oid_of() const override { return static_oid(); } -/** -* Issuer Alternative Name Extension -*/ -class BOTAN_PUBLIC_API(2,0) Issuer_Alternative_Name final : public Alternative_Name - { - public: Issuer_Alternative_Name* copy() const override { return new Issuer_Alternative_Name(get_alt_name()); } - explicit Issuer_Alternative_Name(const AlternativeName& = AlternativeName()); + explicit Issuer_Alternative_Name(const AlternativeName& name = AlternativeName()) : + m_alt_name(name) {} + + private: + std::string oid_name() const override { return "X509v3.IssuerAlternativeName"; } + + bool should_encode() const override { return m_alt_name.has_items(); } + std::vector<uint8_t> encode_inner() const override; + void decode_inner(const std::vector<uint8_t>&) override; + void contents_to(Data_Store&, Data_Store&) const override; + + AlternativeName m_alt_name; }; /** @@ -340,11 +455,13 @@ class BOTAN_PUBLIC_API(2,0) Extended_Key_Usage final : public Certificate_Extens Extended_Key_Usage() = default; explicit Extended_Key_Usage(const std::vector<OID>& o) : m_oids(o) {} - std::vector<OID> get_oids() const { return m_oids; } + const std::vector<OID>& get_oids() const { return m_oids; } + + static OID static_oid() { return OID("2.5.29.37"); } + OID oid_of() const override { return static_oid(); } private: - std::string oid_name() const override - { return "X509v3.ExtendedKeyUsage"; } + std::string oid_name() const override { return "X509v3.ExtendedKeyUsage"; } bool should_encode() const override { return (m_oids.size() > 0); } std::vector<uint8_t> encode_inner() const override; @@ -371,6 +488,11 @@ class BOTAN_PUBLIC_API(2,0) Name_Constraints final : public Certificate_Extensio std::vector<std::set<Certificate_Status_Code>>& cert_status, size_t pos) override; + const NameConstraints& get_name_constraints() const { return m_name_constraints; } + + static OID static_oid() { return OID("2.5.29.30"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.NameConstraints"; } @@ -395,8 +517,14 @@ class BOTAN_PUBLIC_API(2,0) Certificate_Policies final : public Certificate_Exte Certificate_Policies() = default; explicit Certificate_Policies(const std::vector<OID>& o) : m_oids(o) {} + BOTAN_DEPRECATED("Use get_policy_oids") std::vector<OID> get_oids() const { return m_oids; } + const std::vector<OID>& get_policy_oids() const { return m_oids; } + + static OID static_oid() { return OID("2.5.29.32"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.CertificatePolicies"; } @@ -420,6 +548,11 @@ class BOTAN_PUBLIC_API(2,0) Authority_Information_Access final : public Certific explicit Authority_Information_Access(const std::string& ocsp) : m_ocsp_responder(ocsp) {} + std::string ocsp_responder() const { return m_ocsp_responder; } + + static OID static_oid() { return OID("1.3.6.1.5.5.7.1.1"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "PKIX.AuthorityInformationAccess"; } @@ -447,6 +580,9 @@ class BOTAN_PUBLIC_API(2,0) CRL_Number final : public Certificate_Extension size_t get_crl_number() const; + static OID static_oid() { return OID("2.5.29.20"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.CRLNumber"; } @@ -472,6 +608,9 @@ class BOTAN_PUBLIC_API(2,0) CRL_ReasonCode final : public Certificate_Extension CRL_Code get_reason() const { return m_reason; } + static OID static_oid() { return OID("2.5.29.21"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.ReasonCode"; } @@ -508,9 +647,15 @@ class BOTAN_PUBLIC_API(2,0) CRL_Distribution_Points final : public Certificate_E explicit CRL_Distribution_Points(const std::vector<Distribution_Point>& points) : m_distribution_points(points) {} - std::vector<Distribution_Point> distribution_points() const + const std::vector<Distribution_Point>& distribution_points() const { return m_distribution_points; } + const std::vector<std::string>& crl_distribution_urls() const + { return m_crl_distribution_urls; } + + static OID static_oid() { return OID("2.5.29.31"); } + OID oid_of() const override { return static_oid(); } + private: std::string oid_name() const override { return "X509v3.CRLDistributionPoints"; } @@ -523,44 +668,65 @@ class BOTAN_PUBLIC_API(2,0) CRL_Distribution_Points final : public Certificate_E void contents_to(Data_Store&, Data_Store&) const override; std::vector<Distribution_Point> m_distribution_points; + std::vector<std::string> m_crl_distribution_urls; }; /** -* An unknown X.509 extension marked as critical -* Will always add a failure to the path validation result. +* An unknown X.509 extension +* Will add a failure to the path validation result, if critical */ -class BOTAN_PUBLIC_API(2,0) Unknown_Critical_Extension final : public Certificate_Extension +class BOTAN_PUBLIC_API(2,4) Unknown_Extension final : public Certificate_Extension { public: - explicit Unknown_Critical_Extension(OID oid) : m_oid(oid) {} + Unknown_Extension(const OID& oid, bool critical) : + m_oid(oid), m_critical(critical) {} - Unknown_Critical_Extension* copy() const override - { return new Unknown_Critical_Extension(m_oid); } + Unknown_Extension* copy() const override + { return new Unknown_Extension(m_oid, m_critical); } + /** + * Return the OID of this unknown extension + */ OID oid_of() const override { return m_oid; } + //static_oid not defined for Unknown_Extension + + /** + * Return the extension contents + */ + const std::vector<uint8_t>& extension_contents() const { return m_bytes; } + + /** + * Return if this extension was marked critical + */ + bool is_critical_extension() const { return m_critical; } + void validate(const X509_Certificate&, const X509_Certificate&, const std::vector<std::shared_ptr<const 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); + if(m_critical) + { + cert_status.at(pos).insert(Certificate_Status_Code::UNKNOWN_CRITICAL_EXTENSION); + } } private: - std::string oid_name() const override - { return "Unknown OID name"; } + std::string oid_name() const override { return ""; } - bool should_encode() const override { return false; } + bool should_encode() const override { return true; } std::vector<uint8_t> encode_inner() const override; void decode_inner(const std::vector<uint8_t>&) override; void contents_to(Data_Store&, Data_Store&) const override; OID m_oid; + bool m_critical; + std::vector<uint8_t> m_bytes; }; -} + } } diff --git a/src/lib/x509/x509_obj.cpp b/src/lib/x509/x509_obj.cpp index f566be00e..dad27d6ff 100644 --- a/src/lib/x509/x509_obj.cpp +++ b/src/lib/x509/x509_obj.cpp @@ -299,13 +299,11 @@ void X509_Object::do_decode() } catch(Decoding_Error& e) { - throw Decoding_Error(m_PEM_label_pref + " decoding failed (" + - e.what() + ")"); + throw Decoding_Error(m_PEM_label_pref + " decoding failed", e.what()); } catch(Invalid_Argument& e) { - throw Decoding_Error(m_PEM_label_pref + " decoding failed (" + - e.what() + ")"); + throw Decoding_Error(m_PEM_label_pref + " decoding failed", e.what()); } } diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp index 5a6588ecc..6814f54c1 100644 --- a/src/lib/x509/x509cert.cpp +++ b/src/lib/x509/x509cert.cpp @@ -1,12 +1,13 @@ /* * X.509 Certificates -* (C) 1999-2010,2015 Jack Lloyd +* (C) 1999-2010,2015,2017 Jack Lloyd * (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ #include <botan/x509cert.h> +#include <botan/datastor.h> #include <botan/pk_keys.h> #include <botan/x509_ext.h> #include <botan/ber_dec.h> @@ -20,29 +21,59 @@ namespace Botan { -namespace { +struct X509_Certificate_Data + { + size_t m_version = 0; + std::vector<uint8_t> m_serial; + AlgorithmIdentifier m_sig_algo_inner; + X509_DN m_issuer_dn; + X509_DN m_subject_dn; + std::vector<uint8_t> m_issuer_dn_bits; + std::vector<uint8_t> m_subject_dn_bits; + X509_Time m_not_before; + X509_Time m_not_after; + std::vector<uint8_t> m_subject_public_key_bits; + AlgorithmIdentifier m_subject_public_key_algid; + std::vector<uint8_t> m_subject_public_key_bitstring; + std::vector<uint8_t> m_subject_public_key_bitstring_sha1; + + std::vector<uint8_t> m_v2_issuer_key_id; + std::vector<uint8_t> m_v2_subject_key_id; + Extensions m_v3_extensions; + + Key_Constraints m_key_constraints; + std::vector<OID> m_extended_key_usage; + std::vector<uint8_t> m_authority_key_id; + std::vector<uint8_t> m_subject_key_id; + + std::vector<std::string> m_crl_distribution_points; + std::string m_ocsp_responder; + + size_t m_path_len_constraint = 0; + bool m_self_signed = false; + bool m_is_ca_certificate = false; + + AlternativeName m_subject_alt_name; + AlternativeName m_issuer_alt_name; + + Data_Store m_subject_ds; + Data_Store m_issuer_ds; + }; /* -* Lookup each OID in the vector +* X509_Certificate Constructor */ -std::vector<std::string> lookup_oids(const std::vector<std::string>& in) +X509_Certificate::X509_Certificate(DataSource& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") { - std::vector<std::string> out; - - for(auto i = in.begin(); i != in.end(); ++i) - out.push_back(OIDS::lookup(OID(*i))); - return out; + do_decode(); } -} - /* * X509_Certificate Constructor */ -X509_Certificate::X509_Certificate(DataSource& in) : - X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), - m_self_signed(false), - m_v3_extensions(false) +X509_Certificate::X509_Certificate(const std::vector<uint8_t>& in) : + X509_Object(in, "CERTIFICATE/X509 CERTIFICATE") { do_decode(); } @@ -52,71 +83,55 @@ X509_Certificate::X509_Certificate(DataSource& in) : * X509_Certificate Constructor */ X509_Certificate::X509_Certificate(const std::string& fsname) : - X509_Object(fsname, "CERTIFICATE/X509 CERTIFICATE"), - m_self_signed(false), - m_v3_extensions(false) + X509_Object(fsname, "CERTIFICATE/X509 CERTIFICATE") { do_decode(); } #endif -/* -* X509_Certificate Constructor -*/ -X509_Certificate::X509_Certificate(const std::vector<uint8_t>& in) : - X509_Object(in, "CERTIFICATE/X509 CERTIFICATE"), - m_self_signed(false), - m_v3_extensions(false) - { - do_decode(); - } +namespace { -/* -* Decode the TBSCertificate data -*/ -void X509_Certificate::force_decode() +std::unique_ptr<X509_Certificate_Data> parse_x509_cert_body(const X509_Object& obj) { - size_t version; - BigInt serial_bn; - AlgorithmIdentifier sig_algo_inner; - X509_DN dn_issuer, dn_subject; - X509_Time start, end; + std::unique_ptr<X509_Certificate_Data> data(new X509_Certificate_Data); - BER_Decoder tbs_cert(signed_body()); + BER_Decoder tbs_cert(obj.signed_body()); + BigInt serial_bn; - tbs_cert.decode_optional(version, ASN1_Tag(0), + tbs_cert.decode_optional(data->m_version, ASN1_Tag(0), ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) .decode(serial_bn) - .decode(sig_algo_inner) - .decode(dn_issuer) + .decode(data->m_sig_algo_inner) + .decode(data->m_issuer_dn) .start_cons(SEQUENCE) - .decode(start) - .decode(end) + .decode(data->m_not_before) + .decode(data->m_not_after) .end_cons() - .decode(dn_subject); - - if(version > 2) - throw Decoding_Error("Unknown X.509 cert version " + std::to_string(version)); - if(signature_algorithm() != sig_algo_inner) - throw Decoding_Error("Algorithm identifier mismatch"); + .decode(data->m_subject_dn); + if(data->m_version > 2) + throw Decoding_Error("Unknown X.509 cert version " + std::to_string(data->m_version)); + if(obj.signature_algorithm() != data->m_sig_algo_inner) + throw Decoding_Error("X.509 Certificate had differing algorithm identifers in inner and outer ID fields"); - m_subject.add(dn_subject.contents()); - m_issuer.add(dn_issuer.contents()); + // for general sanity convert wire version (0 based) to standards version (v1 .. v3) + data->m_version += 1; - m_subject.add("X509.Certificate.dn_bits", ASN1::put_in_sequence(dn_subject.get_bits())); - m_issuer.add("X509.Certificate.dn_bits", ASN1::put_in_sequence(dn_issuer.get_bits())); + data->m_serial = BigInt::encode(serial_bn); + data->m_subject_dn_bits = ASN1::put_in_sequence(data->m_subject_dn.get_bits()); + data->m_issuer_dn_bits = ASN1::put_in_sequence(data->m_issuer_dn.get_bits()); BER_Object public_key = tbs_cert.get_next_object(); if(public_key.type_tag != SEQUENCE || public_key.class_tag != CONSTRUCTED) throw BER_Bad_Tag("X509_Certificate: Unexpected tag for public key", public_key.type_tag, public_key.class_tag); + // validate_public_key_params(public_key.value); AlgorithmIdentifier public_key_alg_id; BER_Decoder(public_key.value).decode(public_key_alg_id).discard_remaining(); std::vector<std::string> public_key_info = - split_on(OIDS::lookup(public_key_alg_id.oid), '/'); + split_on(OIDS::oid2str(public_key_alg_id.oid), '/'); if(!public_key_info.empty() && public_key_info[0] == "RSA") { @@ -139,7 +154,7 @@ void X509_Certificate::force_decode() ToDo: Allow salt length to be greater */ - if(public_key_alg_id != signature_algorithm()) + if(public_key_alg_id != obj.signature_algorithm()) { throw Decoding_Error("Algorithm identifier mismatch"); } @@ -159,146 +174,259 @@ void X509_Certificate::force_decode() } } - std::vector<uint8_t> v2_issuer_key_id, v2_subject_key_id; + data->m_subject_public_key_bits = unlock(public_key.value); - tbs_cert.decode_optional_string(v2_issuer_key_id, BIT_STRING, 1); - tbs_cert.decode_optional_string(v2_subject_key_id, BIT_STRING, 2); + BER_Decoder(data->m_subject_public_key_bits) + .decode(data->m_subject_public_key_algid) + .decode(data->m_subject_public_key_bitstring, BIT_STRING); + + tbs_cert.decode_optional_string(data->m_v2_issuer_key_id, BIT_STRING, 1); + tbs_cert.decode_optional_string(data->m_v2_subject_key_id, BIT_STRING, 2); BER_Object v3_exts_data = tbs_cert.get_next_object(); if(v3_exts_data.type_tag == 3 && v3_exts_data.class_tag == ASN1_Tag(CONSTRUCTED | CONTEXT_SPECIFIC)) { - BER_Decoder(v3_exts_data.value).decode(m_v3_extensions).verify_end(); - m_v3_extensions.contents_to(m_subject, m_issuer); + BER_Decoder(v3_exts_data.value).decode(data->m_v3_extensions).verify_end(); } else if(v3_exts_data.type_tag != NO_OBJECT) throw BER_Bad_Tag("Unknown tag in X.509 cert", v3_exts_data.type_tag, v3_exts_data.class_tag); if(tbs_cert.more_items()) - throw Decoding_Error("TBSCertificate has more items that expected"); + throw Decoding_Error("TBSCertificate has extra data after extensions block"); + + // Now cache some fields from the extensions + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Key_Usage>()) + { + data->m_key_constraints = ext->get_constraints(); + } + else + { + data->m_key_constraints = NO_CONSTRAINTS; + } - m_subject.add("X509.Certificate.version", static_cast<uint32_t>(version)); - m_subject.add("X509.Certificate.serial", BigInt::encode(serial_bn)); - m_subject.add("X509.Certificate.start", start.to_string()); - m_subject.add("X509.Certificate.end", end.to_string()); + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Subject_Key_ID>()) + { + data->m_subject_key_id = ext->get_key_id(); + } - m_issuer.add("X509.Certificate.v2.key_id", v2_issuer_key_id); - m_subject.add("X509.Certificate.v2.key_id", v2_subject_key_id); + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Authority_Key_ID>()) + { + data->m_authority_key_id = ext->get_key_id(); + } - m_subject.add("X509.Certificate.public_key", hex_encode(public_key.value)); + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Basic_Constraints>()) + { + if(ext->get_is_ca() == true) + { + if(data->m_key_constraints == NO_CONSTRAINTS || + (data->m_key_constraints & KEY_CERT_SIGN)) + { + data->m_is_ca_certificate = true; + data->m_path_len_constraint = ext->get_path_limit(); + } + } + } + + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Issuer_Alternative_Name>()) + { + data->m_issuer_alt_name = ext->get_alt_name(); + } - m_self_signed = false; - if(dn_subject == dn_issuer) + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Subject_Alternative_Name>()) { - std::unique_ptr<Public_Key> pub_key(subject_public_key()); - m_self_signed = check_signature(*pub_key); + data->m_subject_alt_name = ext->get_alt_name(); } - if(m_self_signed && version == 0) + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Extended_Key_Usage>()) { - m_subject.add("X509v3.BasicConstraints.is_ca", 1); - m_subject.add("X509v3.BasicConstraints.path_constraint", Cert_Extension::NO_CERT_PATH_LIMIT); + data->m_extended_key_usage = ext->get_oids(); } - if(is_CA_cert() && - !m_subject.has_value("X509v3.BasicConstraints.path_constraint")) + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::Authority_Information_Access>()) { - const size_t limit = (x509_version() < 3) ? - Cert_Extension::NO_CERT_PATH_LIMIT : 0; + data->m_ocsp_responder = ext->ocsp_responder(); + } - m_subject.add("X509v3.BasicConstraints.path_constraint", static_cast<uint32_t>(limit)); + if(auto ext = data->m_v3_extensions.get_extension_object_as<Cert_Extension::CRL_Distribution_Points>()) + { + data->m_crl_distribution_points = ext->crl_distribution_urls(); } + + // Check for self-signed vs self-issued certificates + if(data->m_subject_dn == data->m_issuer_dn) + { + std::unique_ptr<Public_Key> pub_key( + X509::load_key(ASN1::put_in_sequence(data->m_subject_public_key_bits))); + data->m_self_signed = obj.check_signature(*pub_key); + } + + std::unique_ptr<HashFunction> sha1(HashFunction::create("SHA-1")); + if(sha1) + { + sha1->update(data->m_subject_public_key_bitstring); + data->m_subject_public_key_bitstring_sha1 = sha1->final_stdvec(); + // otherwise left as empty, and we will throw if subject_public_key_bitstring_sha1 is called + } + + data->m_subject_ds.add(data->m_subject_dn.contents()); + data->m_issuer_ds.add(data->m_issuer_dn.contents()); + data->m_v3_extensions.contents_to(data->m_subject_ds, data->m_issuer_ds); + + return data; } +} + /* -* Return the X.509 version in use +* Decode the TBSCertificate data */ +void X509_Certificate::force_decode() + { + m_data.reset(); + + std::unique_ptr<X509_Certificate_Data> data = parse_x509_cert_body(*this); + + m_data.reset(data.release()); + } + +const X509_Certificate_Data& X509_Certificate::data() const + { + if(m_data == nullptr) + { + throw Decoding_Error("Failed to parse X509 certificate"); + } + return *m_data.get(); + } + uint32_t X509_Certificate::x509_version() const { - return (m_subject.get1_uint32("X509.Certificate.version") + 1); + return data().m_version; } -/* -* Return the time this cert becomes valid -*/ -std::string X509_Certificate::start_time() const +bool X509_Certificate::is_self_signed() const { - return m_subject.get1("X509.Certificate.start"); + return data().m_self_signed; } -/* -* Return the time this cert becomes invalid -*/ -std::string X509_Certificate::end_time() const +const X509_Time& X509_Certificate::not_before() const { - return m_subject.get1("X509.Certificate.end"); + return data().m_not_before; } -/* -* Return information about the subject -*/ -std::vector<std::string> -X509_Certificate::subject_info(const std::string& what) const +const X509_Time& X509_Certificate::not_after() const { - return m_subject.get(X509_DN::deref_info_field(what)); + return data().m_not_after; } -/* -* Return information about the issuer -*/ -std::vector<std::string> -X509_Certificate::issuer_info(const std::string& what) const +const std::vector<uint8_t>& X509_Certificate::v2_issuer_key_id() const { - return m_issuer.get(X509_DN::deref_info_field(what)); + return data().m_v2_issuer_key_id; } -/* -* Return the public key in this certificate -*/ -Public_Key* X509_Certificate::subject_public_key() const +const std::vector<uint8_t>& X509_Certificate::v2_subject_key_id() const + { + return data().m_v2_subject_key_id; + } + +const std::vector<uint8_t>& X509_Certificate::subject_public_key_bits() const { - return X509::load_key( - ASN1::put_in_sequence(this->subject_public_key_bits())); + return data().m_subject_public_key_bits; } -std::vector<uint8_t> X509_Certificate::subject_public_key_bits() const +const std::vector<uint8_t>& X509_Certificate::subject_public_key_bitstring() const { - return hex_decode(m_subject.get1("X509.Certificate.public_key")); + return data().m_subject_public_key_bitstring; } -std::vector<uint8_t> X509_Certificate::subject_public_key_bitstring() const +std::vector<uint8_t> X509_Certificate::subject_public_key_bitstring_sha1() const { - // TODO: cache this - const std::vector<uint8_t> key_bits = subject_public_key_bits(); + if(data().m_subject_public_key_bitstring_sha1.empty()) + throw Encoding_Error("X509_Certificate::subject_public_key_bitstring_sha1 called but SHA-1 disabled in build"); + + return data().m_subject_public_key_bitstring_sha1; + } - AlgorithmIdentifier public_key_algid; - std::vector<uint8_t> public_key_bitstr; +const std::vector<uint8_t>& X509_Certificate::authority_key_id() const + { + return data().m_authority_key_id; + } - BER_Decoder(key_bits) - .decode(public_key_algid) - .decode(public_key_bitstr, BIT_STRING); +const std::vector<uint8_t>& X509_Certificate::subject_key_id() const + { + return data().m_subject_key_id; + } - return public_key_bitstr; +const std::vector<uint8_t>& X509_Certificate::serial_number() const + { + return data().m_serial; } -std::vector<uint8_t> X509_Certificate::subject_public_key_bitstring_sha1() const +const X509_DN& X509_Certificate::issuer_dn() const { - // TODO: cache this value - std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-1")); - hash->update(this->subject_public_key_bitstring()); - return hash->final_stdvec(); + return data().m_issuer_dn; + } + +const X509_DN& X509_Certificate::subject_dn() const + { + return data().m_subject_dn; + } + +const std::vector<uint8_t>& X509_Certificate::raw_issuer_dn() const + { + return data().m_issuer_dn_bits; + } + +const std::vector<uint8_t>& X509_Certificate::raw_subject_dn() const + { + return data().m_subject_dn_bits; + } + +bool X509_Certificate::is_CA_cert() const + { + return data().m_is_ca_certificate; + } + +uint32_t X509_Certificate::path_limit() const + { + return data().m_path_len_constraint; + } + +Key_Constraints X509_Certificate::constraints() const + { + return data().m_key_constraints; + } + +const std::vector<OID>& X509_Certificate::extended_key_usage() const + { + return data().m_extended_key_usage; + } + +std::vector<OID> X509_Certificate::certificate_policy_oids() const + { + if(auto ext = v3_extensions().get_extension_object_as<Cert_Extension::Certificate_Policies>()) + { + return ext->get_policy_oids(); + } + return std::vector<OID>(); } /* -* Check if the certificate is for a CA +* Return the name constraints */ -bool X509_Certificate::is_CA_cert() const +NameConstraints X509_Certificate::name_constraints() const { - if(!m_subject.get1_uint32("X509v3.BasicConstraints.is_ca")) - return false; + if(auto ext = v3_extensions().get_extension_object_as<Cert_Extension::Name_Constraints>()) + { + return ext->get_name_constraints(); + } + return NameConstraints(); // no constraints + } - return allowed_usage(Key_Constraints(KEY_CERT_SIGN)); +const Extensions& X509_Certificate::v3_extensions() const + { + return data().m_v3_extensions; } bool X509_Certificate::allowed_usage(Key_Constraints usage) const @@ -310,8 +438,12 @@ bool X509_Certificate::allowed_usage(Key_Constraints usage) const bool X509_Certificate::allowed_extended_usage(const std::string& usage) const { - const std::vector<std::string> ex = ex_constraints(); + return allowed_extended_usage(OIDS::str2oid(usage)); + } +bool X509_Certificate::allowed_extended_usage(const OID& usage) const + { + const std::vector<OID>& ex = extended_key_usage(); if(ex.empty()) return true; @@ -358,27 +490,13 @@ bool X509_Certificate::has_constraints(Key_Constraints constraints) const bool X509_Certificate::has_ex_constraint(const std::string& ex_constraint) const { - const std::vector<std::string> ex = ex_constraints(); - - if(ex.empty()) - { - return false; - } - - if(std::find(ex.begin(), ex.end(), ex_constraint) != ex.end()) - { - return true; - } - - return false; + return has_ex_constraint(OIDS::str2oid(ex_constraint)); } -/* -* Return the path length constraint -*/ -uint32_t X509_Certificate::path_limit() const +bool X509_Certificate::has_ex_constraint(const OID& usage) const { - return m_subject.get1_uint32("X509v3.BasicConstraints.path_constraint", Cert_Extension::NO_CERT_PATH_LIMIT); + const std::vector<OID>& ex = extended_key_usage(); + return (std::find(ex.begin(), ex.end(), usage) != ex.end()); } /* @@ -386,132 +504,155 @@ uint32_t X509_Certificate::path_limit() const */ bool X509_Certificate::is_critical(const std::string& ex_name) const { - return !!m_subject.get1_uint32(ex_name + ".is_critical",0); + return v3_extensions().critical_extension_set(OIDS::str2oid(ex_name)); } -/* -* Return the key usage constraints -*/ -Key_Constraints X509_Certificate::constraints() const +std::string X509_Certificate::ocsp_responder() const { - return Key_Constraints(m_subject.get1_uint32("X509v3.KeyUsage", - NO_CONSTRAINTS)); + return data().m_ocsp_responder; } -/* -* Return the list of extended key usage OIDs -*/ -std::vector<std::string> X509_Certificate::ex_constraints() const +std::string X509_Certificate::crl_distribution_point() const { - return lookup_oids(m_subject.get("X509v3.ExtendedKeyUsage")); + // just returns the first (arbitrarily) + if(data().m_crl_distribution_points.size() > 0) + return data().m_crl_distribution_points[0]; + return ""; } -/* -* Return the name constraints -*/ -NameConstraints X509_Certificate::name_constraints() const +const AlternativeName& X509_Certificate::subject_alt_name() 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 data().m_subject_alt_name; + } - return NameConstraints(std::move(permit),std::move(exclude)); +const AlternativeName& X509_Certificate::issuer_alt_name() const + { + return data().m_issuer_alt_name; } /* -* Return the list of certificate policies +* Return information about the subject */ -std::vector<std::string> X509_Certificate::policies() const +std::vector<std::string> +X509_Certificate::subject_info(const std::string& req) const { - return lookup_oids(m_subject.get("X509v3.CertificatePolicies")); - } + if(subject_dn().has_field(req)) + return subject_dn().get_attribute(req); -Extensions X509_Certificate::v3_extensions() const - { - return m_v3_extensions; - } + if(subject_alt_name().has_field(req)) + return subject_alt_name().get_attribute(req); -std::string X509_Certificate::ocsp_responder() const - { - return m_subject.get1("OCSP.responder", ""); - } + // These will be removed later: + if(req == "X509.Certificate.v2.key_id") + return {hex_encode(this->v2_subject_key_id())}; + if(req == "X509v3.SubjectKeyIdentifier") + return {hex_encode(this->subject_key_id())}; + if(req == "X509.Certificate.dn_bits") + return {hex_encode(this->raw_subject_dn())}; + if(req == "X509.Certificate.start") + return {not_before().to_string()}; + if(req == "X509.Certificate.end") + return {not_after().to_string()}; -std::string X509_Certificate::crl_distribution_point() const - { - return m_subject.get1("CRL.DistributionPoint", ""); - } + if(req == "X509.Certificate.version") + return {std::to_string(x509_version())}; + if(req == "X509.Certificate.serial") + return {hex_encode(serial_number())}; -/* -* Return the authority key id -*/ -std::vector<uint8_t> X509_Certificate::authority_key_id() const - { - return m_issuer.get1_memvec("X509v3.AuthorityKeyIdentifier"); + return data().m_subject_ds.get(req); } /* -* Return the subject key id +* Return information about the issuer */ -std::vector<uint8_t> X509_Certificate::subject_key_id() const +std::vector<std::string> +X509_Certificate::issuer_info(const std::string& req) const { - return m_subject.get1_memvec("X509v3.SubjectKeyIdentifier"); + if(issuer_dn().has_field(req)) + return issuer_dn().get_attribute(req); + + if(issuer_alt_name().has_field(req)) + return issuer_alt_name().get_attribute(req); + + // These will be removed later: + if(req == "X509.Certificate.v2.key_id") + return {hex_encode(this->v2_issuer_key_id())}; + if(req == "X509v3.AuthorityKeyIdentifier") + return {hex_encode(this->authority_key_id())}; + if(req == "X509.Certificate.dn_bits") + return {hex_encode(this->raw_issuer_dn())}; + + return data().m_issuer_ds.get(req); } /* -* Return the certificate serial number +* Return the public key in this certificate */ -std::vector<uint8_t> X509_Certificate::serial_number() const - { - return m_subject.get1_memvec("X509.Certificate.serial"); - } - -X509_DN X509_Certificate::issuer_dn() const +std::unique_ptr<Public_Key> X509_Certificate::load_subject_public_key() const { - return create_dn(m_issuer); + try + { + return std::unique_ptr<Public_Key>(X509::load_key(ASN1::put_in_sequence(this->subject_public_key_bits()))); + } + catch(std::exception& e) + { + throw Decoding_Error("X509_Certificate::load_subject_public_key", e.what()); + } } -std::vector<uint8_t> X509_Certificate::raw_issuer_dn() const +std::vector<uint8_t> X509_Certificate::raw_issuer_dn_sha256() const { - return m_issuer.get1_memvec("X509.Certificate.dn_bits"); + std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw("SHA-256")); + hash->update(raw_issuer_dn()); + return hash->final_stdvec(); } -std::vector<uint8_t> X509_Certificate::raw_issuer_dn_sha256() const +std::vector<uint8_t> X509_Certificate::raw_subject_dn_sha256() const { std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-256")); - hash->update(raw_issuer_dn()); + hash->update(raw_subject_dn()); return hash->final_stdvec(); } -X509_DN X509_Certificate::subject_dn() const +namespace { + +/* +* Lookup each OID in the vector +*/ +std::vector<std::string> lookup_oids(const std::vector<OID>& oids) { - return create_dn(m_subject); + std::vector<std::string> out; + + for(const OID& oid : oids) + { + out.push_back(OIDS::oid2str(oid)); + } + return out; } -std::vector<uint8_t> X509_Certificate::raw_subject_dn() const +} + +/* +* Return the list of extended key usage OIDs +*/ +std::vector<std::string> X509_Certificate::ex_constraints() const { - return m_subject.get1_memvec("X509.Certificate.dn_bits"); + return lookup_oids(extended_key_usage()); } -std::vector<uint8_t> X509_Certificate::raw_subject_dn_sha256() const +/* +* Return the list of certificate policies +*/ +std::vector<std::string> X509_Certificate::policies() const { - std::unique_ptr<HashFunction> hash(HashFunction::create("SHA-256")); - hash->update(raw_subject_dn()); - return hash->final_stdvec(); + return lookup_oids(certificate_policy_oids()); } std::string X509_Certificate::fingerprint(const std::string& hash_name) const { - std::unique_ptr<HashFunction> hash(HashFunction::create(hash_name)); + std::unique_ptr<HashFunction> hash(HashFunction::create_or_throw(hash_name)); hash->update(this->BER_encode()); - const auto hex_print = hex_encode(hash->final()); + const std::string hex_print = hex_encode(hash->final()); std::string formatted_print; @@ -579,42 +720,13 @@ bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2) std::string X509_Certificate::to_string() const { - const std::vector<std::string> dn_fields{ - "Name", - "Email", - "Organization", - "Organizational Unit", - "Locality", - "State", - "Country", - "IP", - "DNS", - "URI", - "PKIX.XMPPAddr" - }; - std::ostringstream out; - for(auto&& field : dn_fields) - { - for(auto&& val : subject_info(field)) - { - out << "Subject " << field << ": " << val << "\n"; - } - } - - for(auto&& field : dn_fields) - { - for(auto&& val : issuer_info(field)) - { - out << "Issuer " << field << ": " << val << "\n"; - } - } - out << "Version: " << this->x509_version() << "\n"; - - out << "Not valid before: " << this->start_time() << "\n"; - out << "Not valid after: " << this->end_time() << "\n"; + out << "Subject: " << subject_dn() << "\n"; + out << "Issuer: " << issuer_dn() << "\n"; + out << "Issued: " << this->not_before().readable_string() << "\n"; + out << "Expires: " << this->not_after().readable_string() << "\n"; out << "Constraints:\n"; Key_Constraints constraints = this->constraints(); @@ -642,20 +754,20 @@ std::string X509_Certificate::to_string() const out << " Decipher Only\n"; } - std::vector<std::string> policies = this->policies(); + const std::vector<OID> policies = this->certificate_policy_oids(); if(!policies.empty()) { out << "Policies: " << "\n"; - for(size_t i = 0; i != policies.size(); i++) - out << " " << policies[i] << "\n"; + for(auto oid : policies) + out << " " << oid.as_string() << "\n"; } - std::vector<std::string> ex_constraints = this->ex_constraints(); + std::vector<OID> ex_constraints = this->extended_key_usage(); if(!ex_constraints.empty()) { out << "Extended Constraints:\n"; for(size_t i = 0; i != ex_constraints.size(); i++) - out << " " << ex_constraints[i] << "\n"; + out << " " << OIDS::oid2str(ex_constraints[i]) << "\n"; } NameConstraints name_constraints = this->name_constraints(); @@ -691,7 +803,7 @@ std::string X509_Certificate::to_string() const out << "CRL " << crl_distribution_point() << "\n"; out << "Signature algorithm: " << - OIDS::lookup(this->signature_algorithm().oid) << "\n"; + OIDS::oid2str(this->signature_algorithm().oid) << "\n"; out << "Serial number: " << hex_encode(this->serial_number()) << "\n"; @@ -702,50 +814,10 @@ std::string X509_Certificate::to_string() const out << "Subject keyid: " << hex_encode(this->subject_key_id()) << "\n"; std::unique_ptr<Public_Key> pubkey(this->subject_public_key()); - out << "Public Key:\n" << X509::PEM_encode(*pubkey); + out << "Public Key [" << pubkey->algo_name() << "-" << pubkey->key_length() << "]\n\n"; + out << X509::PEM_encode(*pubkey); return out.str(); } -/* -* Create and populate a X509_DN -*/ -X509_DN create_dn(const Data_Store& info) - { - auto names = info.search_for( - [](const std::string& key, const std::string&) - { - return (key.find("X520.") != std::string::npos); - }); - - X509_DN dn; - - for(auto i = names.begin(); i != names.end(); ++i) - dn.add_attribute(i->first, i->second); - - return dn; - } - -/* -* Create and populate an AlternativeName -*/ -AlternativeName create_alt_name(const Data_Store& info) - { - auto names = info.search_for( - [](const std::string& key, const std::string&) - { - return (key == "RFC822" || - key == "DNS" || - key == "URI" || - key == "IP"); - }); - - AlternativeName alt_name; - - for(auto i = names.begin(); i != names.end(); ++i) - alt_name.add_attribute(i->first, i->second); - - return alt_name; - } - } diff --git a/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h index 14db2c133..c9cf8bb7b 100644 --- a/src/lib/x509/x509cert.h +++ b/src/lib/x509/x509cert.h @@ -1,6 +1,6 @@ /* * X.509 Certificates -* (C) 1999-2007,2015 Jack Lloyd +* (C) 1999-2007,2015,2017 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,17 +9,19 @@ #define BOTAN_X509_CERTS_H_ #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/asn1_time.h> #include <botan/key_constraint.h> #include <botan/name_constraint.h> #include <memory> namespace Botan { +class Public_Key; +class X509_DN; +class AlternativeName; +class Extensions; + enum class Usage_Type { UNSPECIFIED, // no restrictions @@ -29,33 +31,53 @@ enum class Usage_Type OCSP_RESPONDER }; +struct X509_Certificate_Data; + /** -* This class represents X.509 Certificate +* This class represents an X.509 Certificate */ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object { public: /** - * Get the public key associated with this certificate. + * Return a newly allocated copy of the public key associated + * with the subject of this certificate. This object is owned + * by the caller. + * + * @return public key + */ + Public_Key* subject_public_key() const + { + return load_subject_public_key().release(); + } + + /** + * Create a public key object associated with the public key bits in this + * certificate. If the public key bits was valid for X.509 encoding + * purposes but invalid algorithmically (for example, RSA with an even + * modulus) that will be detected at this point, and an exception will be + * thrown. + * * @return subject public key of this certificate */ - Public_Key* subject_public_key() const; + std::unique_ptr<Public_Key> load_subject_public_key() const; /** - * Get the public key associated with this certificate. + * Get the public key associated with this certificate. This includes the + * outer AlgorithmIdentifier * @return subject public key of this certificate */ - std::vector<uint8_t> subject_public_key_bits() const; + const std::vector<uint8_t>& subject_public_key_bits() const; /** * Get the bit string of the public key associated with this certificate - * @return subject public key of this certificate + * @return public key bits */ - std::vector<uint8_t> subject_public_key_bitstring() const; + const std::vector<uint8_t>& subject_public_key_bitstring() const; /** * Get the SHA-1 bit string of the public key associated with this certificate. - * This is used for OCSP among other protocols + * This is used for OCSP among other protocols. * @return hash of subject public key of this certificate */ std::vector<uint8_t> subject_public_key_bitstring_sha1() const; @@ -64,13 +86,13 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object * Get the certificate's issuer distinguished name (DN). * @return issuer DN of this certificate */ - X509_DN issuer_dn() const; + const X509_DN& issuer_dn() const; /** * Get the certificate's subject distinguished name (DN). * @return subject DN of this certificate */ - X509_DN subject_dn() const; + const X509_DN& subject_dn() const; /** * Get a value for a specific subject_info parameter name. @@ -97,9 +119,9 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object std::vector<std::string> issuer_info(const std::string& name) const; /** - * Raw issuer DN + * Raw issuer DN bits */ - std::vector<uint8_t> raw_issuer_dn() const; + const std::vector<uint8_t>& raw_issuer_dn() const; /** * SHA-256 of Raw issuer DN @@ -109,7 +131,7 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object /** * Raw subject DN */ - std::vector<uint8_t> raw_subject_dn() const; + const std::vector<uint8_t>& raw_subject_dn() const; /** * SHA-256 of Raw subject DN @@ -117,16 +139,34 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object std::vector<uint8_t> raw_subject_dn_sha256() const; /** - * Get the notBefore of the certificate. + * Get the notBefore of the certificate as a string * @return notBefore of the certificate */ - std::string start_time() const; + std::string BOTAN_DEPRECATED("Use not_before().to_string()") start_time() const + { + return not_before().to_string(); + } /** - * Get the notAfter of the certificate. + * Get the notAfter of the certificate as a string * @return notAfter of the certificate */ - std::string end_time() const; + std::string BOTAN_DEPRECATED("Use not_after().to_string()") end_time() const + { + return not_after().to_string(); + } + + /** + * Get the notBefore of the certificate as X509_Time + * @return notBefore of the certificate + */ + const X509_Time& not_before() const; + + /** + * Get the notAfter of the certificate as X509_Time + * @return notAfter of the certificate + */ + const X509_Time& not_after() const; /** * Get the X509 version of this certificate object. @@ -138,25 +178,26 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object * Get the serial number of this certificate. * @return certificates serial number */ - std::vector<uint8_t> serial_number() const; + const std::vector<uint8_t>& serial_number() const; /** * Get the DER encoded AuthorityKeyIdentifier of this certificate. * @return DER encoded AuthorityKeyIdentifier */ - std::vector<uint8_t> authority_key_id() const; + const std::vector<uint8_t>& authority_key_id() const; /** * Get the DER encoded SubjectKeyIdentifier of this certificate. * @return DER encoded SubjectKeyIdentifier */ - std::vector<uint8_t> subject_key_id() const; + const std::vector<uint8_t>& subject_key_id() const; /** * Check whether this certificate is self signed. + * If the DN issuer and subject agree, * @return true if this certificate is self signed */ - bool is_self_signed() const { return m_self_signed; } + bool is_self_signed() const; /** * Check whether this certificate is a CA certificate. @@ -181,21 +222,39 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object bool allowed_extended_usage(const std::string& usage) const; /** + * Returns true if the specified usage is set in the extended key usage extension, + * or if no extended key usage constraints are set at all. + * To check if a certain extended key constraint is set in the certificate + * use @see X509_Certificate#has_ex_constraint. + */ + bool allowed_extended_usage(const OID& usage) const; + + /** * Returns true if the required key and extended key constraints are set in the certificate * for the specified @param usage or if no key constraints are set in both the key usage * and extended key usage extension. */ bool allowed_usage(Usage_Type usage) const; - /// Returns true if the specified @param constraints are included in the key usage extension. + /** + * Returns true if the specified @param constraints are included in the key + * usage extension. + */ bool has_constraints(Key_Constraints constraints) const; /** - * Returns true if and only if @param ex_constraint (referring to an extended key - * constraint, eg "PKIX.ServerAuth") is included in the extended - * key extension. + * Returns true if and only if @param ex_constraint (referring to an + * extended key constraint, eg "PKIX.ServerAuth") is included in the + * extended key extension. + */ + bool BOTAN_DEPRECATED("Use version taking an OID") + has_ex_constraint(const std::string& ex_constraint) const; + + /** + * Returns true if and only if OID @param ex_constraint is + * included in the extended key extension. */ - bool has_ex_constraint(const std::string& ex_constraint) const; + bool has_ex_constraint(const OID& ex_constraint) const; /** * Get the path limit as defined in the BasicConstraints extension of @@ -222,7 +281,15 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object * extension of this certificate. * @return key constraints */ - std::vector<std::string> ex_constraints() const; + std::vector<std::string> + BOTAN_DEPRECATED("Use extended_key_usage") ex_constraints() const; + + /** + * Get the key usage as defined in the ExtendedKeyUsage extension + * of this certificate, or else an empty vector. + * @return key usage + */ + const std::vector<OID>& extended_key_usage() const; /** * Get the name constraints as defined in the NameConstraints @@ -238,11 +305,28 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object */ std::vector<std::string> policies() const; + std::vector<OID> certificate_policy_oids() const; + /** * Get all extensions of this certificate. * @return certificate extensions */ - Extensions v3_extensions() const; + const Extensions& v3_extensions() const; + + /** + * Return the v2 issuer key ID. v2 key IDs are almost never used, + * instead see v3_subject_key_id. + */ + const std::vector<uint8_t>& v2_issuer_key_id() const; + + /** + * Return the v2 subject key ID. v2 key IDs are almost never used, + * instead see v3_subject_key_id. + */ + const std::vector<uint8_t>& v2_subject_key_id() const; + + const AlternativeName& subject_alt_name() const; + const AlternativeName& issuer_alt_name() const; /** * Return the listed address of an OCSP responder, or empty if not set @@ -255,7 +339,7 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object std::string crl_distribution_point() const; /** - * @return a string describing the certificate + * @return a free-form string describing the certificate */ std::string to_string() const; @@ -313,13 +397,12 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object private: void force_decode() override; friend class X509_CA; - friend class BER_Decoder; X509_Certificate() = default; - Data_Store m_subject, m_issuer; - bool m_self_signed; - Extensions m_v3_extensions; + const X509_Certificate_Data& data() const; + + std::shared_ptr<X509_Certificate_Data> m_data; }; /** @@ -331,24 +414,6 @@ class BOTAN_PUBLIC_API(2,0) X509_Certificate : public X509_Object */ BOTAN_PUBLIC_API(2,0) bool operator!=(const X509_Certificate& cert1, const X509_Certificate& cert2); -/* -* Data Store Extraction Operations -*/ - -/* -* Create and populate a X509_DN -* @param info data store containing DN information -* @return DN containing attributes from data store -*/ -BOTAN_PUBLIC_API(2,0) X509_DN create_dn(const Data_Store& info); - -/* -* Create and populate an AlternativeName -* @param info data store containing AlternativeName information -* @return AlternativeName containing attributes from data store -*/ -BOTAN_PUBLIC_API(2,0) AlternativeName create_alt_name(const Data_Store& info); - } #endif diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp index fcc5bf0ba..11bcdbb12 100644 --- a/src/lib/x509/x509path.cpp +++ b/src/lib/x509/x509path.cpp @@ -6,6 +6,7 @@ */ #include <botan/x509path.h> +#include <botan/x509_ext.h> #include <botan/pk_keys.h> #include <botan/ocsp.h> #include <algorithm> @@ -46,6 +47,20 @@ PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& ce if(!cert_path[0]->allowed_usage(usage)) cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); + if(cert_path[0]->is_CA_cert() == false && + cert_path[0]->has_constraints(KEY_CERT_SIGN)) + { + /* + "If the keyCertSign bit is asserted, then the cA bit in the + basic constraints extension (Section 4.2.1.9) MUST also be + asserted." - RFC 5280 + + We don't bother doing this check on the rest of the path since they + must have the cA bit asserted or the validation will fail anyway. + */ + cert_status[0].insert(Certificate_Status_Code::INVALID_USAGE); + } + for(size_t i = 0; i != cert_path.size(); ++i) { std::set<Certificate_Status_Code>& status = cert_status.at(i); @@ -67,10 +82,10 @@ PKIX::check_chain(const std::vector<std::shared_ptr<const X509_Certificate>>& ce } // Check all certs for valid time range - if(validation_time < X509_Time(subject->start_time(), ASN1_Tag::UTC_OR_GENERALIZED_TIME)) + if(validation_time < subject->not_before()) status.insert(Certificate_Status_Code::CERT_NOT_YET_VALID); - if(validation_time > X509_Time(subject->end_time(), ASN1_Tag::UTC_OR_GENERALIZED_TIME)) + if(validation_time > subject->not_after()) status.insert(Certificate_Status_Code::CERT_HAS_EXPIRED); // Check issuer constraints @@ -495,7 +510,9 @@ PKIX::build_certificate_path(std::vector<std::shared_ptr<const X509_Certificate> const std::string fprint = issuer->fingerprint("SHA-256"); if(certs_seen.count(fprint) > 0) // already seen? + { return Certificate_Status_Code::CERT_CHAIN_LOOP; + } certs_seen.insert(fprint); cert_path.push_back(issuer); diff --git a/src/tests/data/misc_certs/opcuactt_ca.der b/src/tests/data/misc_certs/opcuactt_ca.der Binary files differnew file mode 100644 index 000000000..35e8e5cb0 --- /dev/null +++ b/src/tests/data/misc_certs/opcuactt_ca.der diff --git a/src/tests/data/misc_certs/opcuactt_ca.pem b/src/tests/data/misc_certs/opcuactt_ca.pem new file mode 100644 index 000000000..1987a0a07 --- /dev/null +++ b/src/tests/data/misc_certs/opcuactt_ca.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDMAXkiTXQ8mQnY +n+99br/MU9dRvUzliOZRu0owQnAgTtH2Rl+Q9icXQyhcowaiCv6DFrvPlR1MKMux +kJngiOYLI10gLgXjiO4pd53IPNa60+6j4Xgobl7lfCGnSwbYVE8OYl0MyjfrC8Ca +OmbYYVLvgBSW2fPyBHrucCBTB+f4FT+Wuu473hmNHrRx8WT9H9GjRcUQ+RkDqI8S +1ydcQdGu6BWTHCvvRtdxvDk+4WzBdtgoSkGeFBZa3BAnsHTMiKHzMogeqNUGPHVJ +hBobqxLha1ztkRvaIByuidNN1lZAhnonkFNU5QAykPqSAaem1RwPi6xA5VQUxCoz +LmZBvubHAgMBAAECggEBAJtOM82tcWQAjcJgX436FgGTgkQz/KpxggWOs3fx3DJ8 +TtNR18cf3bqT4dJEOfR6si9Ry0DIoDkuhYN0NfD9x0OLdIXfA5So1cazzWZ3CnHI +jdAtAbCl9ZB245fcQiXGaTjmIThvagqCM+o1s73euaiitQnyewgv6PZEXhdX2Xy+ +Mqm1gXi0edyegSdjVRYd1vfx0S52R1TfoLWUy471VlOsKTo+ukUARryB36VA8iNh +0Wn2419zMyjt1ocOIU0RGFFag7wjZ7rGhv+p9Bb92gBrKkbC/2UWgJRpO4OZ17zX +R3AajPW30e4iUYgFqvDpEhi48N9G7zAxia1FWF0VR8ECgYEA/KVK6trcbxy8Ohqd +hBQUkElCAnBvkIY+OStAieLMppVEvQfMgTWiOCzub4hiGKkPZUK9ibmQgDzcXbZy +bB1cXVK6nwAEfABTYzifu2ADJCDOBd3B1PXW2waLHuMVI0CEnySmADDUJ5+LmZm/ +um6j4iGcJIGZjGNVMQTdTampcs8CgYEAzrbcOMBaQZMW5fDD21x/tGwiNmyIBpNC +M865xHjRHFvGdVkWoIvm2wLV5O7AZMzqc5h6sOxCTj2E4dlWnshn0pTBR48epyXl +cv287G2czafQsttyEJq83InV3JlwycyOy2/EJOCuL/T2O8nGJlNTTUEQbC6Udu8v +/r/HGkO9qokCgYB0LaqCzzwY2FTyPzT5/KXsJ9Pz/TJAeor4jRwzjBjh7bhbWM/B +ByHexUKsBUJe5rdOsF8qiyuY3OPVMEXz05iazaVF4qMtRpUSBoLljmRDY9Z5uh0d +SiOQOrUU8gXRXSTfbeHsKogU5Hg0nRAesiwom54K55HtjewqC3uc8A0c3wKBgQDC +XNNiFRKIN7o/CAvQFQAKb+YXUCLyM8H6nnSzFHph9LT8n4CUAhdVdCwTrp196eLE +P+mUswCBOnzYMpesgniEWtQE6cADn7FHVuctUr8t641irs1oaWYM4xkP68JOLCVT +iUpe9lcxxl1DyCuk25ImwHelkIKN9cYl/MJDotASKQKBgFMX48y7qyqYTZUnXHza +g8yVwpU3DaynPNd7IHwC2/Nj1jZW79Tg8YHzKxujvHSByyqt7UOY8oMTdywzqAh5 +JH3Z9JfPKFTp18cNk8Z0vhPnNV5lQPPXKaWsW7YW62AuDYTX/CwL9+Z+sCIQ/WBz +MMcuPWIKk0t5acScslBtLdk3 +-----END PRIVATE KEY----- diff --git a/src/tests/test_pkcs11_high_level.cpp b/src/tests/test_pkcs11_high_level.cpp index 7b4f749fb..1ae7f5527 100644 --- a/src/tests/test_pkcs11_high_level.cpp +++ b/src/tests/test_pkcs11_high_level.cpp @@ -47,6 +47,7 @@ #if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_HAS_PKCS11) #include <botan/p11_x509.h> + #include <botan/x509_dn.h> #endif #if defined(BOTAN_HAS_HMAC_DRBG) diff --git a/src/tests/test_x509_dn.cpp b/src/tests/test_x509_dn.cpp index 55cf05c1c..74803909b 100644 --- a/src/tests/test_x509_dn.cpp +++ b/src/tests/test_x509_dn.cpp @@ -6,14 +6,14 @@ #include "tests.h" -#if defined(BOTAN_HAS_ASN1) +#if defined(BOTAN_HAS_CERTIFICATES) #include <botan/x509_dn.h> #include <botan/ber_dec.h> #endif namespace Botan_Tests { -#if defined(BOTAN_HAS_ASN1) +#if defined(BOTAN_HAS_CERTIFICATES) class X509_DN_Comparisons_Tests final : public Text_Based_Test { public: diff --git a/src/tests/test_x509_path.cpp b/src/tests/test_x509_path.cpp index cb0895369..c1d8a5b17 100644 --- a/src/tests/test_x509_path.cpp +++ b/src/tests/test_x509_path.cpp @@ -182,55 +182,63 @@ std::vector<Test::Result> NIST_Path_Validation_Tests::run() for(auto i = expected.begin(); i != expected.end(); ++i) { - const std::string test_name = i->first; - const std::string expected_result = i->second; - - const std::string test_dir = nist_test_dir + "/" + test_name; - Test::Result result("NIST path validation"); result.start_timer(); - const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir); + const std::string test_name = i->first; - if(all_files.empty()) + try { - result.test_failure("No test files found in " + test_dir); - results.push_back(result); - continue; - } + const std::string expected_result = i->second; - Botan::Certificate_Store_In_Memory store; + const std::string test_dir = nist_test_dir + "/" + test_name; - store.add_certificate(root_cert); - store.add_crl(root_crl); + const std::vector<std::string> all_files = Botan::get_files_recursive(test_dir); - for(auto const& file : all_files) - { - if(file.find(".crt") != std::string::npos && file != "end.crt") + if(all_files.empty()) { - store.add_certificate(Botan::X509_Certificate(file)); + result.test_failure("No test files found in " + test_dir); + results.push_back(result); + continue; } - else if(file.find(".crl") != std::string::npos) + + Botan::Certificate_Store_In_Memory store; + + store.add_certificate(root_cert); + store.add_crl(root_crl); + + for(auto const& file : all_files) { - Botan::DataSource_Stream in(file, true); - Botan::X509_CRL crl(in); - store.add_crl(crl); + if(file.find(".crt") != std::string::npos && file != "end.crt") + { + store.add_certificate(Botan::X509_Certificate(file)); + } + else if(file.find(".crl") != std::string::npos) + { + Botan::DataSource_Stream in(file, true); + Botan::X509_CRL crl(in); + store.add_crl(crl); + } } - } - Botan::X509_Certificate end_user(test_dir + "/end.crt"); + Botan::X509_Certificate end_user(test_dir + "/end.crt"); - // 1024 bit root cert - Botan::Path_Validation_Restrictions restrictions(true, 80); + // 1024 bit root cert + Botan::Path_Validation_Restrictions restrictions(true, 80); - Botan::Path_Validation_Result validation_result = - Botan::x509_path_validate(end_user, - restrictions, - store); + Botan::Path_Validation_Result validation_result = + Botan::x509_path_validate(end_user, + restrictions, + store); - result.test_eq(test_name + " path validation result", - validation_result.result_string(), - expected_result); + result.test_eq(test_name + " path validation result", + validation_result.result_string(), + expected_result); + } + catch(std::exception& e) + { + result.test_failure(test_name, e.what()); + } result.end_timer(); results.push_back(result); diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index d635f7fe1..d2156cf60 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -11,6 +11,7 @@ #include <botan/calendar.h> #include <botan/pkcs10.h> + #include <botan/pkcs8.h> #include <botan/x509self.h> #include <botan/x509path.h> #include <botan/x509_ca.h> @@ -358,6 +359,30 @@ Test::Result test_x509_dates() return result; } +Test::Result test_crl_dn_name() + { + Test::Result result("CRL DN name"); + + // See GH #1252 + + const Botan::OID dc_oid("0.9.2342.19200300.100.1.25"); + + Botan::X509_Certificate cert(Test::data_file("misc_certs/opcuactt_ca.der")); + + Botan::DataSource_Stream key_input(Test::data_file("misc_certs/opcuactt_ca.pem")); + std::unique_ptr<Botan::Private_Key> key = Botan::PKCS8::load_key(key_input); + Botan::X509_CA ca(cert, *key, "SHA-256", Test::rng()); + + Botan::X509_CRL crl = ca.new_crl(Test::rng()); + + result.confirm("matches issuer cert", crl.issuer_dn() == cert.subject_dn()); + + result.confirm("contains DC component", + crl.issuer_dn().get_attributes().count(dc_oid) == 1); + + return result; + } + Test::Result test_x509_utf8() { Test::Result result("X509 with UTF-8 encoded fields"); @@ -379,10 +404,12 @@ Test::Result test_x509_utf8() const std::string location = "\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0"; - result.test_eq("O", utf8_cert.issuer_info("O").at(0), organization); - result.test_eq("OU", utf8_cert.issuer_info("OU").at(0), organization_unit); - result.test_eq("CN", utf8_cert.issuer_info("CN").at(0), common_name); - result.test_eq("L", utf8_cert.issuer_info("L").at(0), location); + const Botan::X509_DN& issuer_dn = utf8_cert.issuer_dn(); + + result.test_eq("O", issuer_dn.get_first_attribute("O"), organization); + result.test_eq("OU", issuer_dn.get_first_attribute("OU"), organization_unit); + result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name); + result.test_eq("L", issuer_dn.get_first_attribute("L"), location); } catch (const Botan::Decoding_Error &ex) { @@ -409,9 +436,11 @@ Test::Result test_x509_bmpstring() // UTF-8 encoded fields of test certificate (contains only ASCII characters) const std::string location = "Berlin"; - result.test_eq("O", ucs2_cert.issuer_info("O").at(0), organization); - result.test_eq("CN", ucs2_cert.issuer_info("CN").at(0), common_name); - result.test_eq("L", ucs2_cert.issuer_info("L").at(0), location); + const Botan::X509_DN& issuer_dn = ucs2_cert.issuer_dn(); + + result.test_eq("O", issuer_dn.get_first_attribute("O"), organization); + result.test_eq("CN", issuer_dn.get_first_attribute("CN"), common_name); + result.test_eq("L", issuer_dn.get_first_attribute("L"), location); } catch (const Botan::Decoding_Error &ex) { @@ -503,10 +532,16 @@ Test::Result test_x509_cert(const std::string& sig_algo, const std::string& hash /* Get cert data */ result.test_eq("x509 version", user1_cert.x509_version(), size_t(3)); - result.test_eq("issuer info CN", user1_cert.issuer_info("CN").at(0), ca_opts().common_name); - result.test_eq("issuer info Country", user1_cert.issuer_info("C").at(0), ca_opts().country); - result.test_eq("issuer info Orga", user1_cert.issuer_info("O").at(0), ca_opts().organization); - result.test_eq("issuer info OrgaUnit", user1_cert.issuer_info("OU").at(0), ca_opts().org_unit); + const Botan::X509_DN& user1_issuer_dn = user1_cert.issuer_dn(); + result.test_eq("issuer info CN", user1_issuer_dn.get_first_attribute("CN"), ca_opts().common_name); + result.test_eq("issuer info Country", user1_issuer_dn.get_first_attribute("C"), ca_opts().country); + result.test_eq("issuer info Orga", user1_issuer_dn.get_first_attribute("O"), ca_opts().organization); + result.test_eq("issuer info OrgaUnit", user1_issuer_dn.get_first_attribute("OU"), ca_opts().org_unit); + + const Botan::AlternativeName& user1_altname = user1_cert.subject_alt_name(); + result.test_eq("subject alt email", user1_altname.get_first_attribute("RFC822"), "[email protected]"); + result.test_eq("subject alt email", user1_altname.get_first_attribute("DNS"), "botan.randombit.net"); + result.test_eq("subject alt email", user1_altname.get_first_attribute("URI"), "https://botan.randombit.net"); const Botan::X509_CRL crl1 = ca.new_crl(Test::rng()); @@ -952,6 +987,12 @@ class String_Extension final : public Botan::Certificate_Extension { return m_oid; } + + bool should_encode() const override + { + return true; + } + std::string oid_name() const override { return "String Extension"; @@ -1005,16 +1046,19 @@ Test::Result test_x509_extensions(const std::string& sig_algo, const std::string // include a custom extension in the request Botan::Extensions req_extensions; - Botan::OID oid("1.2.3.4.5.6.7.8.9.1"); - req_extensions.add(new String_Extension("1Test"), false); + const Botan::OID oid("1.2.3.4.5.6.7.8.9.1"); + const Botan::OID ku_oid = Botan::OIDS::lookup("X509v3.KeyUsage"); + req_extensions.add(new String_Extension("AAAAAAAAAAAAAABCDEF"), false); opts.extensions = req_extensions; /* Create a self-signed certificate */ const Botan::X509_Certificate self_signed_cert = Botan::X509::create_self_signed_cert( opts, *user_key, hash_fn, Test::rng()); + result.confirm("Extensions::extension_set true for Key_Usage", self_signed_cert.v3_extensions().extension_set(ku_oid)); + // check if known Key_Usage extension is present in self-signed cert - auto key_usage_ext = self_signed_cert.v3_extensions().get(Botan::OIDS::lookup("X509v3.KeyUsage")); + auto key_usage_ext = self_signed_cert.v3_extensions().get(ku_oid); if(result.confirm("Key_Usage extension present in self-signed certificate", key_usage_ext != nullptr)) { result.confirm("Key_Usage extension value matches in self-signed certificate", @@ -1025,29 +1069,34 @@ Test::Result test_x509_extensions(const std::string& sig_algo, const std::string auto string_ext = self_signed_cert.v3_extensions().get_raw<String_Extension>(oid); if(result.confirm("Custom extension present in self-signed certificate", string_ext != nullptr)) { - result.test_eq("Custom extension value matches in self-signed certificate", string_ext->value(), "1Test"); + result.test_eq("Custom extension value matches in self-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF"); } const Botan::PKCS10_Request user_req = Botan::X509::create_cert_req(opts, *user_key, hash_fn, Test::rng()); /* Create a CA-signed certificate */ - const Botan::X509_Certificate user_cert = ca.sign_request( - user_req, Test::rng(), from_date(2008, 01, 01), from_date(2033, 01, 01)); + const Botan::X509_Certificate ca_signed_cert = + ca.sign_request(user_req, Test::rng(), + from_date(2008, 01, 01), + from_date(2033, 01, 01)); // check if known Key_Usage extension is present in CA-signed cert - key_usage_ext = self_signed_cert.v3_extensions().get(Botan::OIDS::lookup("X509v3.KeyUsage")); - if(result.confirm("Key_Usage extension present in user certificate", key_usage_ext != nullptr)) + result.confirm("Extensions::extension_set true for Key_Usage", ca_signed_cert.v3_extensions().extension_set(ku_oid)); + + key_usage_ext = ca_signed_cert.v3_extensions().get(ku_oid); + if(result.confirm("Key_Usage extension present in CA-signed certificate", key_usage_ext != nullptr)) { result.confirm("Key_Usage extension value matches in user certificate", dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == Botan::DIGITAL_SIGNATURE); } // check if custom extension is present in CA-signed cert - string_ext = user_cert.v3_extensions().get_raw<String_Extension>(oid); - if(result.confirm("Custom extension present in user certificate", string_ext != nullptr)) + result.confirm("Extensions::extension_set true for String_Extension", ca_signed_cert.v3_extensions().extension_set(oid)); + string_ext = ca_signed_cert.v3_extensions().get_raw<String_Extension>(oid); + if(result.confirm("Custom extension present in CA-signed certificate", string_ext != nullptr)) { - result.test_eq("Custom extension value matches in user certificate", string_ext->value(), "1Test"); + result.test_eq("Custom extension value matches in CA-signed certificate", string_ext->value(), "AAAAAAAAAAAAAABCDEF"); } return result; @@ -1200,6 +1249,7 @@ class X509_Cert_Unit_Tests final : public Test results.push_back(test_hashes("ECDSA")); results.push_back(test_x509_utf8()); results.push_back(test_x509_bmpstring()); + results.push_back(test_crl_dn_name()); return results; } |