aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2018-03-14 08:44:01 -0400
committerJack Lloyd <[email protected]>2018-03-14 09:02:36 -0400
commitea829d5904eeeca3be9a326a25a801526dbb5f7c (patch)
tree26eb5ad809c73a8d6a48cd10a920349d3ab64645
parentcf03866d0c9648b92501e4de2ca62e28ff19af3b (diff)
Support custom DN entries
GH #1490
-rw-r--r--src/lib/asn1/asn1_str.h5
-rw-r--r--src/lib/x509/x509_dn.cpp93
-rw-r--r--src/lib/x509/x509_dn.h30
-rw-r--r--src/tests/unit_x509.cpp86
4 files changed, 143 insertions, 71 deletions
diff --git a/src/lib/asn1/asn1_str.h b/src/lib/asn1/asn1_str.h
index 77e2fc145..9a571561c 100644
--- a/src/lib/asn1/asn1_str.h
+++ b/src/lib/asn1/asn1_str.h
@@ -26,6 +26,8 @@ class BOTAN_PUBLIC_API(2,0) ASN1_String final : public ASN1_Object
const std::string& value() const { return m_utf8_str; }
+ bool empty() const { return m_utf8_str.empty(); }
+
std::string BOTAN_DEPRECATED("Use value() to get UTF-8 string instead")
iso_8859() const;
@@ -35,6 +37,9 @@ class BOTAN_PUBLIC_API(2,0) ASN1_String final : public ASN1_Object
*/
static bool is_string_type(ASN1_Tag tag);
+ bool operator==(const ASN1_String& other) const
+ { return value() == other.value(); }
+
explicit ASN1_String(const std::string& utf8 = "");
ASN1_String(const std::string& utf8, ASN1_Tag tag);
private:
diff --git a/src/lib/x509/x509_dn.cpp b/src/lib/x509/x509_dn.cpp
index d07344aae..faeb4543c 100644
--- a/src/lib/x509/x509_dn.cpp
+++ b/src/lib/x509/x509_dn.cpp
@@ -40,24 +40,30 @@ X509_DN::X509_DN(const std::multimap<std::string, std::string>& args)
void X509_DN::add_attribute(const std::string& type,
const std::string& str)
{
- OID oid = OIDS::lookup(type);
- add_attribute(oid, str);
+ add_attribute(OIDS::lookup(type), str);
+ }
+
+void X509_DN::add_attribute(const OID& oid, const std::string& str)
+ {
+ add_attribute(oid, ASN1_String(str));
}
/*
* Add an attribute to a X509_DN
*/
-void X509_DN::add_attribute(const OID& oid, const std::string& str)
+void X509_DN::add_attribute(const OID& oid, const ASN1_String& str)
{
if(str.empty())
return;
auto range = m_dn_info.equal_range(oid);
for(auto i = range.first; i != range.second; ++i)
- if(i->second.value() == str)
+ {
+ if(i->second == str)
return;
+ }
- multimap_insert(m_dn_info, oid, ASN1_String(str));
+ multimap_insert(m_dn_info, oid, str);
m_dn_bits.clear();
}
@@ -91,7 +97,11 @@ std::multimap<std::string, std::string> X509_DN::contents() const
bool X509_DN::has_field(const std::string& attr) const
{
- const OID oid = OIDS::lookup(deref_info_field(attr));
+ return has_field(OIDS::lookup(deref_info_field(attr)));
+ }
+
+bool X509_DN::has_field(const OID& oid) const
+ {
auto range = m_dn_info.equal_range(oid);
return (range.first != range.second);
}
@@ -99,12 +109,16 @@ bool X509_DN::has_field(const std::string& attr) const
std::string X509_DN::get_first_attribute(const std::string& attr) const
{
const OID oid = OIDS::lookup(deref_info_field(attr));
+ return get_first_attribute(oid).value();
+ }
+ASN1_String X509_DN::get_first_attribute(const OID& oid) const
+ {
auto i = m_dn_info.lower_bound(oid);
if(i != m_dn_info.end() && i->first == oid)
- return i->second.value();
+ return i->second;
- return "";
+ return ASN1_String();
}
/*
@@ -122,11 +136,6 @@ std::vector<std::string> X509_DN::get_attribute(const std::string& attr) const
return values;
}
-const std::vector<uint8_t>& X509_DN::get_bits() const
- {
- return m_dn_bits;
- }
-
/*
* Deref aliases in a subject/issuer info request
*/
@@ -201,58 +210,32 @@ bool operator<(const X509_DN& dn1, const X509_DN& dn2)
return false;
}
-namespace {
-
-/*
-* DER encode a RelativeDistinguishedName
-*/
-void do_ava(DER_Encoder& encoder,
- const std::multimap<OID, std::string>& dn_info,
- ASN1_Tag string_type, const std::string& oid_str,
- bool must_exist = false)
- {
- const OID oid = OIDS::lookup(oid_str);
- const bool exists = (dn_info.find(oid) != dn_info.end());
-
- if(!exists && must_exist)
- throw Encoding_Error("X509_DN: No entry for " + oid_str);
- if(!exists) return;
-
- auto range = dn_info.equal_range(oid);
-
- for(auto i = range.first; i != range.second; ++i)
- {
- encoder.start_cons(SET)
- .start_cons(SEQUENCE)
- .encode(oid)
- .encode(ASN1_String(i->second, string_type))
- .end_cons()
- .end_cons();
- }
- }
-
-}
-
/*
* DER encode a DistinguishedName
*/
void X509_DN::encode_into(DER_Encoder& der) const
{
- auto dn_info = get_attributes();
-
der.start_cons(SEQUENCE);
if(!m_dn_bits.empty())
+ {
+ /*
+ If we decoded this from somewhere, encode it back exactly as
+ we recieved it
+ */
der.raw_bytes(m_dn_bits);
+ }
else
{
- do_ava(der, dn_info, PRINTABLE_STRING, "X520.Country");
- do_ava(der, dn_info, DIRECTORY_STRING, "X520.State");
- do_ava(der, dn_info, DIRECTORY_STRING, "X520.Locality");
- do_ava(der, dn_info, DIRECTORY_STRING, "X520.Organization");
- do_ava(der, dn_info, DIRECTORY_STRING, "X520.OrganizationalUnit");
- do_ava(der, dn_info, DIRECTORY_STRING, "X520.CommonName");
- do_ava(der, dn_info, PRINTABLE_STRING, "X520.SerialNumber");
+ for(auto dn : m_dn_info)
+ {
+ der.start_cons(SET)
+ .start_cons(SEQUENCE)
+ .encode(dn.first)
+ .encode(dn.second)
+ .end_cons()
+ .end_cons();
+ }
}
der.end_cons();
@@ -285,7 +268,7 @@ void X509_DN::decode_from(BER_Decoder& source)
.decode(str)
.end_cons();
- add_attribute(oid, str.value());
+ add_attribute(oid, str);
}
}
diff --git a/src/lib/x509/x509_dn.h b/src/lib/x509/x509_dn.h
index 88117a110..c08bf9e7e 100644
--- a/src/lib/x509/x509_dn.h
+++ b/src/lib/x509/x509_dn.h
@@ -1,6 +1,6 @@
/*
* X.509 Distinguished Name
-* (C) 1999-2010 Jack Lloyd
+* (C) 1999-2010,2018 Jack Lloyd
* (C) 2017 Fabian Weissberg, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
@@ -23,12 +23,27 @@ namespace Botan {
class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object
{
public:
+ X509_DN() = default;
+ explicit X509_DN(const std::multimap<OID, std::string>& vals);
+ explicit X509_DN(const std::multimap<std::string, std::string>& vals);
+
void encode_into(class DER_Encoder&) const override;
void decode_from(class BER_Decoder&) override;
+ const std::multimap<OID, ASN1_String>& dn_info() const { return m_dn_info; }
+
+ bool has_field(const OID& oid) const;
+ ASN1_String get_first_attribute(const OID& oid) const;
+
+ /*
+ * Return the BER encoded data, if any
+ */
+ const std::vector<uint8_t>& get_bits() const { return m_dn_bits; }
+
+ bool empty() const { return m_dn_info.empty(); }
+
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;
@@ -36,6 +51,7 @@ class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object
void add_attribute(const std::string& key, const std::string& val);
void add_attribute(const OID& oid, const std::string& val);
+ void add_attribute(const OID& oid, const ASN1_String& val);
static std::string deref_info_field(const std::string& key);
@@ -48,16 +64,6 @@ class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object
*/
static size_t lookup_ub(const OID& oid);
- /*
- * 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>& 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/tests/unit_x509.cpp b/src/tests/unit_x509.cpp
index 94fa95392..cfb84c51a 100644
--- a/src/tests/unit_x509.cpp
+++ b/src/tests/unit_x509.cpp
@@ -1208,6 +1208,73 @@ class String_Extension final : public Botan::Certificate_Extension
std::string m_contents;
};
+Test::Result test_custom_dn_attr(const Botan::Private_Key& ca_key,
+ const std::string& sig_algo,
+ const std::string& sig_padding = "",
+ const std::string& hash_fn = "SHA-256")
+ {
+ Test::Result result("X509 Custom DN");
+
+ /* Create the self-signed cert */
+ Botan::X509_Certificate ca_cert =
+ Botan::X509::create_self_signed_cert(ca_opts(sig_padding), ca_key, hash_fn, Test::rng());
+
+ /* Create the CA object */
+ Botan::X509_CA ca(ca_cert, ca_key, {{"padding",sig_padding}}, hash_fn, Test::rng());
+
+ std::unique_ptr<Botan::Private_Key> user_key(make_a_private_key(sig_algo));
+
+ Botan::X509_DN subject_dn;
+
+ const Botan::OID attr1(Botan::OID("1.3.6.1.4.1.25258.9.1.1"));
+ const Botan::OID attr2(Botan::OID("1.3.6.1.4.1.25258.9.1.2"));
+ const Botan::ASN1_String val1("Custom Attr 1", Botan::PRINTABLE_STRING);
+ const Botan::ASN1_String val2("12345", Botan::UTF8_STRING);
+
+ subject_dn.add_attribute(attr1, val1);
+ subject_dn.add_attribute(attr2, val2);
+
+ Botan::Extensions extensions;
+
+ Botan::PKCS10_Request req =
+ Botan::PKCS10_Request::create(*user_key,
+ subject_dn,
+ extensions,
+ hash_fn,
+ Test::rng(),
+ sig_padding);
+
+ Botan::X509_DN req_dn = req.subject_dn();
+
+ result.test_eq("Expected number of DN entries", req_dn.dn_info().size(), 2);
+
+ Botan::ASN1_String req_val1 = req_dn.get_first_attribute(attr1);
+ Botan::ASN1_String req_val2 = req_dn.get_first_attribute(attr2);
+ result.confirm("Attr1 matches encoded", req_val1 == val1);
+ result.confirm("Attr2 matches encoded", req_val2 == val2);
+ result.confirm("Attr1 tag matches encoded", req_val1.tagging() == val1.tagging());
+ result.confirm("Attr2 tag matches encoded", req_val2.tagging() == val2.tagging());
+
+ Botan::X509_Time not_before("100301123001Z", Botan::UTC_TIME);
+ Botan::X509_Time not_after("300301123001Z", Botan::UTC_TIME);
+
+ auto cert = ca.sign_request(req, Test::rng(), not_before, not_after);
+
+ Botan::X509_DN cert_dn = cert.subject_dn();
+
+ result.test_eq("Expected number of DN entries", cert_dn.dn_info().size(), 2);
+
+ Botan::ASN1_String cert_val1 = cert_dn.get_first_attribute(attr1);
+ Botan::ASN1_String cert_val2 = cert_dn.get_first_attribute(attr2);
+ result.confirm("Attr1 matches encoded", cert_val1 == val1);
+ result.confirm("Attr2 matches encoded", cert_val2 == val2);
+ result.confirm("Attr1 tag matches encoded", cert_val1.tagging() == val1.tagging());
+ result.confirm("Attr2 tag matches encoded", cert_val2.tagging() == val2.tagging());
+
+ return result;
+ }
+
+
Test::Result test_x509_extensions(const Botan::Private_Key& ca_key,
const std::string& sig_algo,
const std::string& sig_padding = "",
@@ -1319,14 +1386,14 @@ Test::Result test_hashes(const Botan::Private_Key& key,
{
"Test Issuer/US/Botan Project/Testing",
"Test Subject/US/Botan Project/Testing",
- "E2407027922619C0673E0AA59A9CD3673730C36A39F891BCE0806D1DD225A937",
- "42A63CB4FCCA81AC6D14D5E209B3156E033B90FF1007216927EA9324BA4EF2DB"
+ "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
+ "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
},
{
"Test Subject/US/Botan Project/Testing",
"Test Issuer/US/Botan Project/Testing",
- "42A63CB4FCCA81AC6D14D5E209B3156E033B90FF1007216927EA9324BA4EF2DB",
- "E2407027922619C0673E0AA59A9CD3673730C36A39F891BCE0806D1DD225A937"
+ "87039231C2205B74B6F1F3830A66272C0B41F71894B03AC3150221766D95267B",
+ "ACB4F373004A56A983A23EB8F60FA4706312B5DB90FD978574FE7ACC84E093A5",
}
};
@@ -1433,6 +1500,17 @@ class X509_Cert_Unit_Tests final : public Test
extensions_result.test_failure("test_extensions " + algo, e.what());
}
results.push_back(extensions_result);
+
+ Test::Result custom_dn_result("X509 Custom DN");
+ try
+ {
+ custom_dn_result.merge(test_custom_dn_attr(*key, algo, padding_scheme));
+ }
+ catch(std::exception& e)
+ {
+ custom_dn_result.test_failure("test_custom_dn_attr " + algo, e.what());
+ }
+ results.push_back(custom_dn_result);
}
}