diff options
author | René Korthaus <[email protected]> | 2016-09-05 11:01:42 +0200 |
---|---|---|
committer | René Korthaus <[email protected]> | 2016-12-02 11:01:59 +0100 |
commit | e8b3e26f4167524216718204c6b5a14ed0e7942d (patch) | |
tree | 12e4469750d81a565185212766c0d51a7312ea4d /src | |
parent | 5c49dbac212e53be821b0771d3df46f78801efbe (diff) |
Allow custom extensions in X509_Cert_Options
Allow custom extensions in CA-signed cert requests
Add templated getter for extensions
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 | 67 | ||||
-rw-r--r-- | src/lib/x509/x509_ext.h | 23 | ||||
-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 | 123 |
8 files changed, 276 insertions, 25 deletions
diff --git a/src/lib/x509/pkcs10.cpp b/src/lib/x509/pkcs10.cpp index ccd22454b..bac220277 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(Certificate_Extension* 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(Certificate_Extension* 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(Certificate_Extension* 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(Certificate_Extension* 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..f475c50c2 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -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); + } + +Certificate_Extension* Extensions::get(const OID& oid) const + { + for(auto& ext : m_extensions) + { + if(ext.first->oid_of() == oid) + { + return ext.first.get(); + } + } + + 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(); + } + } } /* diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index b1984fa94..ee7589ea5 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -92,6 +92,29 @@ class BOTAN_DLL Extensions : public ASN1_Object void contents_to(Data_Store&, Data_Store&) const; void add(Certificate_Extension* extn, bool critical = false); + void replace(Certificate_Extension* extn, bool critical = false); + + Certificate_Extension* get(const OID& oid) const; + + template<typename T> + std::unique_ptr<T> get_extension(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; + } std::vector<std::pair<std::unique_ptr<Certificate_Extension>, bool>> extensions() const; 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..ceed26c75 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -17,6 +17,8 @@ #include <botan/x509path.h> #include <botan/x509_ca.h> #include <botan/pk_algs.h> +#include <botan/ber_dec.h> +#include <botan/der_enc.h> #endif @@ -710,6 +712,117 @@ 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(const Botan::OID& oid, const std::string& val) : m_oid(oid), m_contents(val) {} + + std::string value() const { return m_contents; } + + String_Extension* copy() const override { return new String_Extension(m_oid, 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; + 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(oid, "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 custom extension is present in cert + // it would be nice if we could use v3_extensions().extensions() instead + auto ext_raw = self_signed_cert.v3_extensions().extensions_raw(); + if(result.confirm("Custom extension present in self-signed certificate", ext_raw.count(oid) > 0)) + { + std::vector<byte> in = ext_raw.at(oid).first; + String_Extension ext(oid, ""); + ext.decode_inner(in); + + result.confirm("Custom extension value matches in self-signed certificate", 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)); + + ext_raw = user_cert.v3_extensions().extensions_raw(); + if(result.confirm("Custom extension present in user certificate", ext_raw.count(oid) > 0)) + { + std::vector<byte> in = ext_raw.at(oid).first; + String_Extension ext(oid, ""); + ext.decode_inner(in); + + result.confirm("Custom extension value matches in user certificate", ext.value() == "1Test"); + } + + return result; + } class X509_Cert_Unit_Tests : public Test { @@ -722,6 +835,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 +862,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" }; |