aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRené Korthaus <[email protected]>2016-09-05 11:01:42 +0200
committerRené Korthaus <[email protected]>2016-12-02 11:01:59 +0100
commite8b3e26f4167524216718204c6b5a14ed0e7942d (patch)
tree12e4469750d81a565185212766c0d51a7312ea4d /src
parent5c49dbac212e53be821b0771d3df46f78801efbe (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.cpp53
-rw-r--r--src/lib/x509/pkcs10.h11
-rw-r--r--src/lib/x509/x509_ca.cpp14
-rw-r--r--src/lib/x509/x509_ext.cpp67
-rw-r--r--src/lib/x509/x509_ext.h23
-rw-r--r--src/lib/x509/x509self.cpp4
-rw-r--r--src/lib/x509/x509self.h6
-rw-r--r--src/tests/unit_x509.cpp123
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" };