diff options
author | Jack Lloyd <[email protected]> | 2018-03-14 08:44:01 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2018-03-14 09:02:36 -0400 |
commit | ea829d5904eeeca3be9a326a25a801526dbb5f7c (patch) | |
tree | 26eb5ad809c73a8d6a48cd10a920349d3ab64645 | |
parent | cf03866d0c9648b92501e4de2ca62e28ff19af3b (diff) |
Support custom DN entries
GH #1490
-rw-r--r-- | src/lib/asn1/asn1_str.h | 5 | ||||
-rw-r--r-- | src/lib/x509/x509_dn.cpp | 93 | ||||
-rw-r--r-- | src/lib/x509/x509_dn.h | 30 | ||||
-rw-r--r-- | src/tests/unit_x509.cpp | 86 |
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); } } |