aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2016-12-08 20:32:33 -0500
committerJack Lloyd <[email protected]>2016-12-08 20:32:33 -0500
commit7d3a5a44edecf2990e27dc7f0a8d18ac4a084f6a (patch)
tree62c5a2a497c5231c0a19d2febd6968ade516f964 /src
parente26875e52ddcabf21148dc3fed095a29033a4505 (diff)
parent00b99c4b6f9056be8d7749e84436489fbcfbc3e4 (diff)
Merge GH #744 Allow custom extensions when creating certs
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.cpp71
-rw-r--r--src/lib/x509/x509_ext.h65
-rw-r--r--src/lib/x509/x509self.cpp4
-rw-r--r--src/lib/x509/x509self.h6
-rw-r--r--src/tests/unit_x509.cpp133
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" };