diff options
author | Jack Lloyd <[email protected]> | 2016-12-08 20:32:33 -0500 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2016-12-08 20:32:33 -0500 |
commit | 7d3a5a44edecf2990e27dc7f0a8d18ac4a084f6a (patch) | |
tree | 62c5a2a497c5231c0a19d2febd6968ade516f964 /src | |
parent | e26875e52ddcabf21148dc3fed095a29033a4505 (diff) | |
parent | 00b99c4b6f9056be8d7749e84436489fbcfbc3e4 (diff) |
Merge GH #744 Allow custom extensions when creating certs
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/x509/pkcs10.cpp | 53 | ||||
-rw-r--r-- | src/lib/x509/pkcs10.h | 11 | ||||
-rw-r--r-- | src/lib/x509/x509_ca.cpp | 14 | ||||
-rw-r--r-- | src/lib/x509/x509_ext.cpp | 71 | ||||
-rw-r--r-- | src/lib/x509/x509_ext.h | 65 | ||||
-rw-r--r-- | src/lib/x509/x509self.cpp | 4 | ||||
-rw-r--r-- | src/lib/x509/x509self.h | 6 | ||||
-rw-r--r-- | src/tests/unit_x509.cpp | 133 |
8 files changed, 328 insertions, 29 deletions
diff --git a/src/lib/x509/pkcs10.cpp b/src/lib/x509/pkcs10.cpp index ccd22454b..22508f131 100644 --- a/src/lib/x509/pkcs10.cpp +++ b/src/lib/x509/pkcs10.cpp @@ -46,7 +46,7 @@ PKCS10_Request::PKCS10_Request(const std::vector<byte>& in) : } /* -* Deocde the CertificateRequestInfo +* Decode the CertificateRequestInfo */ void PKCS10_Request::force_decode() { @@ -120,11 +120,7 @@ void PKCS10_Request::handle_attribute(const Attribute& attr) } else if(attr.oid == OIDS::lookup("PKCS9.ExtensionRequest")) { - Extensions extensions; - value.decode(extensions).verify_end(); - - Data_Store issuer_info; - extensions.contents_to(m_info, issuer_info); + value.decode(m_extensions).verify_end(); } } @@ -175,7 +171,12 @@ AlternativeName PKCS10_Request::subject_alt_name() const */ Key_Constraints PKCS10_Request::constraints() const { - return Key_Constraints(m_info.get1_u32bit("X509v3.KeyUsage", NO_CONSTRAINTS)); + if(auto ext = m_extensions.get(OIDS::lookup("X509v3.KeyUsage"))) + { + return dynamic_cast<Cert_Extension::Key_Usage&>(*ext).get_constraints(); + } + + return NO_CONSTRAINTS; } /* @@ -183,12 +184,12 @@ Key_Constraints PKCS10_Request::constraints() const */ std::vector<OID> PKCS10_Request::ex_constraints() const { - std::vector<std::string> oids = m_info.get("X509v3.ExtendedKeyUsage"); + if(auto ext = m_extensions.get(OIDS::lookup("X509v3.ExtendedKeyUsage"))) + { + return dynamic_cast<Cert_Extension::Extended_Key_Usage&>(*ext).get_oids(); + } - std::vector<OID> result; - for(size_t i = 0; i != oids.size(); ++i) - result.push_back(OID(oids[i])); - return result; + return {}; } /* @@ -196,15 +197,37 @@ std::vector<OID> PKCS10_Request::ex_constraints() const */ bool PKCS10_Request::is_CA() const { - return (m_info.get1_u32bit("X509v3.BasicConstraints.is_ca") > 0); + if(auto ext = m_extensions.get(OIDS::lookup("X509v3.BasicConstraints"))) + { + return dynamic_cast<Cert_Extension::Basic_Constraints&>(*ext).get_is_ca(); + } + + return false; } /* * Return the desired path limit (if any) */ -u32bit PKCS10_Request::path_limit() const +size_t PKCS10_Request::path_limit() const + { + if(auto ext = m_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()) + { + return basic_constraints.get_path_limit(); + } + } + + return 0; + } + +/* +* Return the X509v3 extensions +*/ +Extensions PKCS10_Request::extensions() const { - return m_info.get1_u32bit("X509v3.BasicConstraints.path_constraint", 0); + return m_extensions; } } diff --git a/src/lib/x509/pkcs10.h b/src/lib/x509/pkcs10.h index c7a9ec300..2202b92a4 100644 --- a/src/lib/x509/pkcs10.h +++ b/src/lib/x509/pkcs10.h @@ -1,6 +1,7 @@ /* * PKCS #10 * (C) 1999-2007 Jack Lloyd +* (C) 2016 René Korthaus, Rohde & Schwarz Cybersecurity * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -10,6 +11,7 @@ #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> @@ -72,7 +74,7 @@ class BOTAN_DLL PKCS10_Request final : public X509_Object * in the BasicConstraints extension. * @return path limit */ - u32bit path_limit() const; + size_t path_limit() const; /** * Get the challenge password for this request @@ -81,6 +83,12 @@ class BOTAN_DLL PKCS10_Request final : public X509_Object std::string challenge_password() const; /** + * Get the X509v3 extensions. + * @return X509v3 extensions + */ + Extensions extensions() const; + + /** * Create a PKCS#10 Request from a data source. * @param source the data source providing the DER encoded request */ @@ -105,6 +113,7 @@ class BOTAN_DLL PKCS10_Request final : public X509_Object void handle_attribute(const Attribute&); Data_Store m_info; + Extensions m_extensions; }; } diff --git a/src/lib/x509/x509_ca.cpp b/src/lib/x509/x509_ca.cpp index 6aba7311c..ec56abc92 100644 --- a/src/lib/x509/x509_ca.cpp +++ b/src/lib/x509/x509_ca.cpp @@ -63,24 +63,24 @@ X509_Certificate X509_CA::sign_request(const PKCS10_Request& req, constraints = req.constraints(); } - Extensions extensions; + Extensions extensions = req.extensions(); - extensions.add( + extensions.replace( new Cert_Extension::Basic_Constraints(req.is_CA(), req.path_limit()), true); if(constraints != NO_CONSTRAINTS) { - extensions.add(new Cert_Extension::Key_Usage(constraints), true); + extensions.replace(new Cert_Extension::Key_Usage(constraints), true); } - extensions.add(new Cert_Extension::Authority_Key_ID(m_cert.subject_key_id())); - extensions.add(new Cert_Extension::Subject_Key_ID(req.raw_public_key())); + extensions.replace(new Cert_Extension::Authority_Key_ID(m_cert.subject_key_id())); + extensions.replace(new Cert_Extension::Subject_Key_ID(req.raw_public_key())); - extensions.add( + extensions.replace( new Cert_Extension::Subject_Alternative_Name(req.subject_alt_name())); - extensions.add( + extensions.replace( new Cert_Extension::Extended_Key_Usage(req.ex_constraints())); return make_cert(m_signer, rng, m_ca_sig_algo, diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index c22e9ebcb..9ef14e88d 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -22,7 +22,7 @@ namespace Botan { /* * List of X.509 Certificate Extensions */ -Certificate_Extension* Extensions::get_extension(const OID& oid, bool critical) +Certificate_Extension* Extensions::create_extension(const OID& oid, bool critical) { #define X509_EXTENSION(NAME, TYPE) \ if(oid == OIDS::lookup(NAME)) { return new Cert_Extension::TYPE(); } @@ -90,10 +90,52 @@ void Certificate_Extension::validate(const X509_Certificate&, const X509_Certifi void Extensions::add(Certificate_Extension* extn, bool critical) { + // 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)); } +void Extensions::replace(Certificate_Extension* extn, bool critical) + { + for(auto it = m_extensions.begin(); it != m_extensions.end(); ++it) + { + if(it->first->oid_of() == extn->oid_of()) + { + m_extensions.erase(it); + break; + } + } + + 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); + } + +std::unique_ptr<Certificate_Extension> Extensions::get(const OID& oid) const + { + for(auto& ext : m_extensions) + { + if(ext.first->oid_of() == oid) + { + return std::unique_ptr<Certificate_Extension>(ext.first->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; @@ -114,6 +156,7 @@ std::map<OID, std::pair<std::vector<byte>, bool>> Extensions::extensions_raw() c */ void Extensions::encode_into(DER_Encoder& to_object) const { + // encode any known extensions for(size_t i = 0; i != m_extensions.size(); ++i) { const Certificate_Extension* ext = m_extensions[i].first.get(); @@ -130,6 +173,30 @@ void Extensions::encode_into(DER_Encoder& to_object) const .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; + }); + + 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) + .end_cons(); + } + } } /* @@ -157,7 +224,7 @@ void Extensions::decode_from(BER_Decoder& from_source) m_extensions_raw.emplace(oid, std::make_pair(value, critical)); - std::unique_ptr<Certificate_Extension> ext(get_extension(oid, critical)); + std::unique_ptr<Certificate_Extension> ext(create_extension(oid, critical)); if(!ext && critical && m_throw_on_unknown_critical) throw Decoding_Error("Encountered unknown X.509 extension marked " diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index b1984fa94..1657613e7 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -88,23 +88,84 @@ class BOTAN_DLL Extensions : public ASN1_Object public: 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 critical whether this extension should be marked as critical + * @throw Invalid_Argument if the extension is already present in the list + */ void add(Certificate_Extension* extn, bool critical = false); + /** + * Adds an extension to the list or replaces it. + * @param extn the certificate extension + * @param critical whether this extension should be marked as critical + */ + void replace(Certificate_Extension* extn, bool critical = false); + + /** + * 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. + */ + 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. + */ + template<typename T> + std::unique_ptr<T> get_raw(const OID& oid) + { + try + { + if(m_extensions_raw.count(oid) > 0) + { + std::unique_ptr<T> ext(new T); + ext->decode_inner(m_extensions_raw[oid].first); + return std::move(ext); + } + } + 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. + */ 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. + */ std::map<OID, std::pair<std::vector<byte>, bool>> extensions_raw() const; Extensions& operator=(const Extensions&); Extensions(const Extensions&); + /** + * @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) {} private: - static Certificate_Extension* get_extension(const OID&, bool); + static Certificate_Extension* create_extension(const OID&, bool); std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> m_extensions; bool m_throw_on_unknown_critical; diff --git a/src/lib/x509/x509self.cpp b/src/lib/x509/x509self.cpp index b59b45f6a..fe0336014 100644 --- a/src/lib/x509/x509self.cpp +++ b/src/lib/x509/x509self.cpp @@ -65,7 +65,7 @@ X509_Certificate create_self_signed_cert(const X509_Cert_Options& opts, constraints = opts.constraints; } - Extensions extensions; + Extensions extensions = opts.extensions; extensions.add( new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit), @@ -119,7 +119,7 @@ PKCS10_Request create_cert_req(const X509_Cert_Options& opts, constraints = opts.constraints; } - Extensions extensions; + Extensions extensions = opts.extensions; extensions.add( new Cert_Extension::Basic_Constraints(opts.is_CA, opts.path_limit)); diff --git a/src/lib/x509/x509self.h b/src/lib/x509/x509self.h index 401b2eb2f..008eece51 100644 --- a/src/lib/x509/x509self.h +++ b/src/lib/x509/x509self.h @@ -9,6 +9,7 @@ #define BOTAN_X509_SELF_H__ #include <botan/x509cert.h> +#include <botan/x509_ext.h> #include <botan/pkcs10.h> #include <botan/asn1_time.h> @@ -115,6 +116,11 @@ class BOTAN_DLL X509_Cert_Options std::vector<OID> ex_constraints; /** + * Additional X.509 extensions + */ + Extensions extensions; + + /** * Mark the certificate as a CA certificate and set the path limit. * @param limit the path limit to be set in the BasicConstraints extension. */ diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index 7a22033a8..56a6e8b82 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -17,6 +17,9 @@ #include <botan/x509path.h> #include <botan/x509_ca.h> #include <botan/pk_algs.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> +#include <botan/oids.h> #endif @@ -710,6 +713,126 @@ Test::Result test_valid_constraints(const std::string& pk_algo) return result; } +/** + * @brief X.509v3 extension that encodes a given string + */ +class String_Extension : public Botan::Certificate_Extension + { + public: + String_Extension() : m_contents() {} + String_Extension(const std::string& val) : m_contents(val) {} + + std::string value() const { return m_contents; } + + String_Extension* copy() const override { return new String_Extension(m_contents); } + + Botan::OID oid_of() const override { return m_oid; } + std::string oid_name() const override { return "String Extension"; } + + void contents_to(Botan::Data_Store&, Botan::Data_Store&) const override {} + + std::vector<byte> encode_inner() const override + { + return Botan::DER_Encoder().encode(Botan::ASN1_String(m_contents, Botan::UTF8_STRING)).get_contents_unlocked(); + } + + void decode_inner(const std::vector<byte>& in) override + { + Botan::ASN1_String str; + Botan::BER_Decoder(in).decode(str, Botan::UTF8_STRING).verify_end(); + m_contents = str.value(); + } + + private: + Botan::OID m_oid {"1.2.3.4.5.6.7.8.9.1"}; + std::string m_contents; + }; + +Test::Result test_x509_extensions(const std::string& sig_algo, const std::string& hash_fn = "SHA-256") + { + using Botan::Key_Constraints; + + Test::Result result("X509 Extensions"); + + /* Create the CA's key and self-signed cert */ + std::unique_ptr<Botan::Private_Key> ca_key(make_a_private_key(sig_algo)); + + if(!ca_key) + { + // Failure because X.509 enabled but requested signature algorithm is not present + result.test_note("Skipping due to missing signature algorithm: " + sig_algo); + return result; + } + + /* Create the self-signed cert */ + Botan::X509_Certificate ca_cert = + Botan::X509::create_self_signed_cert(ca_opts(), + *ca_key, + hash_fn, + Test::rng()); + + /* Create the CA object */ + Botan::X509_CA ca(ca_cert, *ca_key, hash_fn); + + std::unique_ptr<Botan::Private_Key> user_key(make_a_private_key(sig_algo)); + + Botan::X509_Cert_Options opts("Test User 1/US/Botan Project/Testing"); + opts.constraints = Key_Constraints::DIGITAL_SIGNATURE; + + // 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); + opts.extensions = req_extensions; + + /* Create a self-signed certificate */ + Botan::X509_Certificate self_signed_cert = Botan::X509::create_self_signed_cert(opts, *user_key, hash_fn, Test::rng()); + + // 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")); + 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", + dynamic_cast<Botan::Cert_Extension::Key_Usage&>(*key_usage_ext).get_constraints() == opts.constraints); + } + + // check if custom extension is present in self-signed cert + 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"); + } + + + Botan::PKCS10_Request user_req = + Botan::X509::create_cert_req(opts, + *user_key, + hash_fn, + Test::rng()); + + /* Create a CA-signed certificate */ + Botan::X509_Certificate user_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("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.test_eq("Custom extension value matches in user certificate", string_ext->value(), "1Test"); + } + + return result; + } class X509_Cert_Unit_Tests : public Test { @@ -722,6 +845,7 @@ class X509_Cert_Unit_Tests : public Test Test::Result cert_result("X509 Unit"); Test::Result usage_result("X509 Usage"); Test::Result self_issued_result("X509 Self Issued"); + Test::Result extensions_result("X509 Extensions"); for(const auto& algo : sig_algos) { @@ -748,11 +872,20 @@ class X509_Cert_Unit_Tests : public Test { self_issued_result.test_failure("test_self_issued " + algo, e.what()); } + + try { + extensions_result.merge(test_x509_extensions(algo)); + } + catch(std::exception& e) + { + extensions_result.test_failure("test_extensions " + algo, e.what()); + } } results.push_back(cert_result); results.push_back(usage_result); results.push_back(self_issued_result); + results.push_back(extensions_result); const std::vector<std::string> pk_algos { "DH", "ECDH", "RSA", "ElGamal", "GOST-34.10", "DSA", "ECDSA", "ECGDSA", "ECKCDSA" }; |