diff options
-rw-r--r-- | src/cli/credentials.h | 37 | ||||
-rw-r--r-- | src/cli/x509.cpp | 58 | ||||
-rw-r--r-- | src/lib/x509/certstor_flatfile/certstor_flatfile.cpp | 24 | ||||
-rw-r--r-- | src/lib/x509/certstor_flatfile/certstor_flatfile.h | 5 | ||||
-rw-r--r-- | src/lib/x509/certstor_system/certstor_system.cpp | 65 | ||||
-rw-r--r-- | src/lib/x509/certstor_system/certstor_system.h | 42 | ||||
-rw-r--r-- | src/lib/x509/certstor_system/info.txt | 8 | ||||
-rw-r--r-- | src/lib/x509/x509_dn.cpp | 8 | ||||
-rw-r--r-- | src/lib/x509/x509_dn.h | 2 | ||||
-rwxr-xr-x | src/scripts/test_cli.py | 19 | ||||
-rw-r--r-- | src/tests/test_certstor_system.cpp (renamed from src/tests/test_certstor_macos.cpp) | 123 |
11 files changed, 292 insertions, 99 deletions
diff --git a/src/cli/credentials.h b/src/cli/credentials.h index da21dd842..74961a774 100644 --- a/src/cli/credentials.h +++ b/src/cli/credentials.h @@ -13,6 +13,10 @@ #include <botan/data_src.h> #include <memory> +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + #include <botan/certstor_system.h> +#endif + inline bool value_exists(const std::vector<std::string>& vec, const std::string& val) { @@ -30,23 +34,19 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager { public: Basic_Credentials_Manager(bool use_system_store, - const std::string& ca_paths) + const std::string& ca_path) { - std::vector<std::string> paths; - - if(ca_paths.empty() == false) - paths.push_back(ca_paths); - - if(use_system_store) + if(ca_path.empty() == false) { - paths.push_back("/etc/ssl/certs"); - paths.push_back("/usr/share/ca-certificates"); + m_certstores.push_back(std::make_shared<Botan::Certificate_Store_In_Memory>(ca_path)); } - if(paths.empty() == false) +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + if(use_system_store) { - load_certstores(paths); + m_certstores.push_back(std::make_shared<Botan::System_Certificate_Store>()); } +#endif } Basic_Credentials_Manager(Botan::RandomNumberGenerator& rng, @@ -74,21 +74,6 @@ class Basic_Credentials_Manager : public Botan::Credentials_Manager m_creds.push_back(cert); } - void load_certstores(const std::vector<std::string>& paths) - { - try - { - for(auto const& path : paths) - { - std::shared_ptr<Botan::Certificate_Store> cs(new Botan::Certificate_Store_In_Memory(path)); - m_certstores.push_back(cs); - } - } - catch(std::exception&) - { - } - } - std::vector<Botan::Certificate_Store*> trusted_certificate_authorities(const std::string& type, const std::string& /*hostname*/) override diff --git a/src/cli/x509.cpp b/src/cli/x509.cpp index 4c18b7ceb..a92ec1309 100644 --- a/src/cli/x509.cpp +++ b/src/cli/x509.cpp @@ -23,8 +23,66 @@ #include <botan/ocsp.h> #endif +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + #include <botan/certstor_system.h> +#endif + namespace Botan_CLI { +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) + +class Trust_Root_Info final : public Command + { + public: + Trust_Root_Info() : Command("trust_roots --dn --dn-only --display") {} + + std::string group() const override + { + return "x509"; + } + + std::string description() const override + { + return "List certs in the system trust store"; + } + + void go() override + { + Botan::System_Certificate_Store trust_roots; + + const auto dn_list = trust_roots.all_subjects(); + + if(flag_set("dn-only")) + { + for(auto dn : dn_list) + output() << dn << "\n"; + } + else + { + for(auto dn : dn_list) + { + // Some certstores have more than one cert with a particular DN + for(auto cert : trust_roots.find_all_certs(dn, std::vector<uint8_t>())) + { + if(flag_set("dn")) + output() << "# " << dn << "\n"; + + if(flag_set("display")) + output() << cert->to_string() << "\n"; + + output() << cert->PEM_encode() << "\n"; + } + } + + } + } + + }; + +BOTAN_REGISTER_COMMAND("trust_roots", Trust_Root_Info); + +#endif + class Sign_Cert final : public Command { public: diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp index cf0ca2d90..24cea1d40 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.cpp @@ -38,7 +38,7 @@ std::vector<std::vector<uint8_t>> decode_all_certificates(DataSource& source) } } -Flatfile_Certificate_Store::Flatfile_Certificate_Store(const std::string& file) +Flatfile_Certificate_Store::Flatfile_Certificate_Store(const std::string& file, bool ignore_non_ca) { if(file.empty()) { @@ -51,15 +51,23 @@ Flatfile_Certificate_Store::Flatfile_Certificate_Store(const std::string& file) { std::shared_ptr<const X509_Certificate> cert = std::make_shared<const X509_Certificate>(der.data(), der.size()); - if(!cert->is_self_signed() || !cert->is_CA_cert()) + /* + * Various weird or misconfigured system roots include intermediate certificates, + * or even stranger certificates which are not valid for cert issuance at all. + * Previously this code would error on such cases as an obvious misconfiguration, + * but we cannot fix the trust store. So instead just ignore any such certificate. + */ + if(cert->is_self_signed() && cert->is_CA_cert()) { - throw Invalid_Argument("Flatfile_Certificate_Store::Flatfile_Certificate_Store certificate is not self-signed CA"); + m_all_subjects.push_back(cert->subject_dn()); + m_dn_to_cert.emplace(cert->subject_dn(), cert); + m_pubkey_sha1_to_cert.emplace(cert->subject_public_key_bitstring_sha1(), cert); + m_subject_dn_sha256_to_cert.emplace(cert->raw_subject_dn_sha256(), cert); + } + else if(!ignore_non_ca) + { + throw Invalid_Argument("Flatfile_Certificate_Store received non CA cert " + cert->subject_dn().to_string()); } - - m_all_subjects.push_back(cert->subject_dn()); - m_dn_to_cert.emplace(cert->subject_dn(), cert); - m_pubkey_sha1_to_cert.emplace(cert->subject_public_key_bitstring_sha1(), cert); - m_subject_dn_sha256_to_cert.emplace(cert->raw_subject_dn_sha256(), cert); } if(m_all_subjects.empty()) diff --git a/src/lib/x509/certstor_flatfile/certstor_flatfile.h b/src/lib/x509/certstor_flatfile/certstor_flatfile.h index 611bd3895..8d1da3074 100644 --- a/src/lib/x509/certstor_flatfile/certstor_flatfile.h +++ b/src/lib/x509/certstor_flatfile/certstor_flatfile.h @@ -25,8 +25,11 @@ class BOTAN_PUBLIC_API(2, 11) Flatfile_Certificate_Store final : public Certific /** * Construct a new Certificate_Store given a file path to a file including * PEMs of trusted self-signed CAs. + * + * @param ignore_non_ca if true, certs that are not self-signed CA certs will + * be ignored. Otherwise (if false), an exception will be thrown instead. */ - Flatfile_Certificate_Store(const std::string& file); + Flatfile_Certificate_Store(const std::string& file, bool ignore_non_ca = false); Flatfile_Certificate_Store(const Flatfile_Certificate_Store&) = default; Flatfile_Certificate_Store(Flatfile_Certificate_Store&&) = default; diff --git a/src/lib/x509/certstor_system/certstor_system.cpp b/src/lib/x509/certstor_system/certstor_system.cpp new file mode 100644 index 000000000..1650a1784 --- /dev/null +++ b/src/lib/x509/certstor_system/certstor_system.cpp @@ -0,0 +1,65 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/certstor_system.h> +#include <botan/x509cert.h> + +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + #include <botan/certstor_macos.h> +#elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) + #include <botan/certstor_flatfile.h> +#endif + +namespace Botan { + +System_Certificate_Store::System_Certificate_Store() + { +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + m_system_store = std::make_shared<Certificate_Store_MacOS>(); +#elif defined(BOTAN_HAS_CERTSTOR_FLATFILE) && defined(BOTAN_SYSTEM_CERT_BUNDLE) + m_system_store = std::make_shared<Flatfile_Certificate_Store>(BOTAN_SYSTEM_CERT_BUNDLE, true); +#else + throw Not_Implemented("No system certificate store available in this build"); +#endif + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const + { + return m_system_store->find_cert(subject_dn, key_id); + } + +std::vector<std::shared_ptr<const X509_Certificate>> +System_Certificate_Store::find_all_certs(const X509_DN& subject_dn, + const std::vector<uint8_t>& key_id) const + { + return m_system_store->find_all_certs(subject_dn, key_id); + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const + { + return m_system_store->find_cert_by_pubkey_sha1(key_hash); + } + +std::shared_ptr<const X509_Certificate> +System_Certificate_Store::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const + { + return m_system_store->find_cert_by_raw_subject_dn_sha256(subject_hash); + } + +std::shared_ptr<const X509_CRL> +System_Certificate_Store::find_crl_for(const X509_Certificate& subject) const + { + return m_system_store->find_crl_for(subject); + } + +std::vector<X509_DN> System_Certificate_Store::all_subjects() const + { + return m_system_store->all_subjects(); + } + +} diff --git a/src/lib/x509/certstor_system/certstor_system.h b/src/lib/x509/certstor_system/certstor_system.h new file mode 100644 index 000000000..3a0fc6153 --- /dev/null +++ b/src/lib/x509/certstor_system/certstor_system.h @@ -0,0 +1,42 @@ +/* +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SYSTEM_CERT_STORE_H_ +#define BOTAN_SYSTEM_CERT_STORE_H_ + +#include <botan/certstor.h> + +namespace Botan { + +class BOTAN_PUBLIC_API(2,11) System_Certificate_Store final : public Certificate_Store + { + public: + + System_Certificate_Store(); + + std::shared_ptr<const X509_Certificate> + find_cert(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::vector<std::shared_ptr<const X509_Certificate>> + find_all_certs(const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override; + + std::shared_ptr<const X509_Certificate> + find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override; + + std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override; + + std::vector<X509_DN> all_subjects() const override; + + private: + std::shared_ptr<Certificate_Store> m_system_store; + }; + +} + +#endif diff --git a/src/lib/x509/certstor_system/info.txt b/src/lib/x509/certstor_system/info.txt new file mode 100644 index 000000000..c9b3ca73e --- /dev/null +++ b/src/lib/x509/certstor_system/info.txt @@ -0,0 +1,8 @@ +<defines> +CERTSTOR_SYSTEM -> 20190411 +</defines> + +<os_features> +apple_keychain +filesystem +</os_features> diff --git a/src/lib/x509/x509_dn.cpp b/src/lib/x509/x509_dn.cpp index 2285e561e..106190011 100644 --- a/src/lib/x509/x509_dn.cpp +++ b/src/lib/x509/x509_dn.cpp @@ -12,6 +12,7 @@ #include <botan/internal/stl_util.h> #include <botan/oids.h> #include <ostream> +#include <sstream> #include <cctype> namespace Botan { @@ -284,6 +285,13 @@ std::string to_short_form(const OID& oid) } +std::string X509_DN::to_string() const + { + std::ostringstream out; + out << *this; + return out.str(); + } + std::ostream& operator<<(std::ostream& out, const X509_DN& dn) { auto info = dn.dn_info(); diff --git a/src/lib/x509/x509_dn.h b/src/lib/x509/x509_dn.h index 9d8beb0bf..f47099c9a 100644 --- a/src/lib/x509/x509_dn.h +++ b/src/lib/x509/x509_dn.h @@ -51,6 +51,8 @@ class BOTAN_PUBLIC_API(2,0) X509_DN final : public ASN1_Object bool empty() const { return m_rdn.empty(); } + std::string to_string() const; + const std::vector<std::pair<OID,ASN1_String>>& dn_info() const { return m_rdn; } std::multimap<OID, std::string> get_attributes() const; diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index 9166489b9..308c4ed53 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -571,6 +571,21 @@ def cli_tls_socket_tests(): tls_server.communicate() +def cli_trust_root_tests(): + + tmp_dir = tempfile.mkdtemp(prefix='botan_cli') + pem_file = os.path.join(tmp_dir, 'pems') + dn_file = os.path.join(tmp_dir, 'dns') + + test_cli("trust_roots", ['--dn-only', '--output=%s' % (dn_file)], "") + + dn_re = re.compile('(.+=\".+\")(,.+=\".+\")') + for line in open(dn_file): + if dn_re.match(line) is None: + logging.error("Unexpected DN line %s", line) + + test_cli("trust_roots", ['--output=%s' % (pem_file)], "") + def cli_tss_tests(): tmp_dir = tempfile.mkdtemp(prefix='botan_cli') @@ -783,7 +798,6 @@ def main(args=None): test_fns = [ cli_asn1_tests, - cli_asn1_tests, cli_base58_tests, cli_base64_tests, cli_bcrypt_tests, @@ -803,16 +817,17 @@ def main(args=None): cli_is_prime_tests, cli_key_tests, cli_mod_inverse_tests, + cli_pbkdf_tune_tests, cli_pk_encrypt_tests, cli_pk_workfactor_tests, cli_psk_db_tests, - cli_pbkdf_tune_tests, cli_rng_tests, cli_speed_tests, cli_timing_test_tests, cli_tls_ciphersuite_tests, cli_tls_client_hello_tests, cli_tls_socket_tests, + cli_trust_root_tests, cli_tss_tests, cli_version_tests, ] diff --git a/src/tests/test_certstor_macos.cpp b/src/tests/test_certstor_system.cpp index a21fd2a32..854bf4bae 100644 --- a/src/tests/test_certstor_macos.cpp +++ b/src/tests/test_certstor_system.cpp @@ -7,10 +7,10 @@ #include "tests.h" -#if defined(BOTAN_HAS_CERTSTOR_MACOS) +#if defined(BOTAN_HAS_CERTSTOR_SYSTEM) #include "test_certstor_utils.h" -#include <botan/certstor_macos.h> +#include <botan/certstor_system.h> #include <botan/ber_dec.h> #include <botan/der_enc.h> #include <botan/hex.h> @@ -19,41 +19,20 @@ namespace Botan_Tests { namespace { -Test::Result open_certificate_store() - { - Test::Result result("macOS Certificate Store - Open Keychain"); - - try - { - result.start_timer(); - Botan::Certificate_Store_MacOS unused; - result.end_timer(); - } - catch(std::exception& e) - { - result.test_failure(e.what()); - } - - result.test_success(); - - return result; - } - -Test::Result find_certificate_by_pubkey_sha1() +Test::Result find_certificate_by_pubkey_sha1(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - Find Certificate by SHA1(pubkey)"); + Test::Result result("System Certificate Store - Find Certificate by SHA1(pubkey)"); try { result.start_timer(); - Botan::Certificate_Store_MacOS certstore; auto cert = certstore.find_cert_by_pubkey_sha1(get_key_id()); result.end_timer(); if(result.test_not_null("found certificate", cert.get())) { auto cns = cert->subject_dn().get_attribute("CN"); - result.test_is_eq("exactly one CN", cns.size(), 1ul); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); result.test_eq("CN", cns.front(), "DST Root CA X3"); } } @@ -64,30 +43,28 @@ Test::Result find_certificate_by_pubkey_sha1() result.test_throws("on invalid SHA1 hash data", [&] { - Botan::Certificate_Store_MacOS certstore; certstore.find_cert_by_pubkey_sha1({}); }); return result; } -Test::Result find_cert_by_subject_dn() +Test::Result find_cert_by_subject_dn(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - Find Certificate by subject DN"); + Test::Result result("System Certificate Store - Find Certificate by subject DN"); try { auto dn = get_dn(); result.start_timer(); - Botan::Certificate_Store_MacOS certstore; auto cert = certstore.find_cert(dn, std::vector<uint8_t>()); result.end_timer(); if(result.test_not_null("found certificate", cert.get())) { auto cns = cert->subject_dn().get_attribute("CN"); - result.test_is_eq("exactly one CN", cns.size(), 1ul); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); result.test_eq("CN", cns.front(), "DST Root CA X3"); } } @@ -99,23 +76,22 @@ Test::Result find_cert_by_subject_dn() return result; } -Test::Result find_cert_by_subject_dn_and_key_id() +Test::Result find_cert_by_subject_dn_and_key_id(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - Find Certificate by subject DN and key ID"); + Test::Result result("System Certificate Store - Find Certificate by subject DN and key ID"); try { auto dn = get_dn(); result.start_timer(); - Botan::Certificate_Store_MacOS certstore; auto cert = certstore.find_cert(dn, get_key_id()); result.end_timer(); if(result.test_not_null("found certificate", cert.get())) { auto cns = cert->subject_dn().get_attribute("CN"); - result.test_is_eq("exactly one CN", cns.size(), 1ul); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); result.test_eq("CN", cns.front(), "DST Root CA X3"); } } @@ -127,16 +103,15 @@ Test::Result find_cert_by_subject_dn_and_key_id() return result; } -Test::Result find_certs_by_subject_dn_and_key_id() +Test::Result find_certs_by_subject_dn_and_key_id(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - Find Certificates by subject DN and key ID"); + Test::Result result("System Certificate Store - Find Certificates by subject DN and key ID"); try { auto dn = get_dn(); result.start_timer(); - Botan::Certificate_Store_MacOS certstore; auto certs = certstore.find_all_certs(dn, get_key_id()); result.end_timer(); @@ -144,7 +119,7 @@ Test::Result find_certs_by_subject_dn_and_key_id() result.test_eq("exactly one certificate", certs.size(), 1)) { auto cns = certs.front()->subject_dn().get_attribute("CN"); - result.test_is_eq("exactly one CN", cns.size(), 1ul); + result.test_is_eq("exactly one CN", cns.size(), size_t(1)); result.test_eq("CN", cns.front(), "DST Root CA X3"); } } @@ -156,14 +131,13 @@ Test::Result find_certs_by_subject_dn_and_key_id() return result; } -Test::Result find_all_subjects() +Test::Result find_all_subjects(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - Find all Certificate Subjects"); + Test::Result result("System Certificate Store - Find all Certificate Subjects"); try { result.start_timer(); - Botan::Certificate_Store_MacOS certstore; auto subjects = certstore.all_subjects(); result.end_timer(); @@ -191,9 +165,9 @@ Test::Result find_all_subjects() return result; } -Test::Result no_certificate_matches() +Test::Result no_certificate_matches(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - can deal with no matches (regression test)"); + Test::Result result("System Certificate Store - can deal with no matches (regression test)"); try { @@ -201,8 +175,6 @@ Test::Result no_certificate_matches() auto kid = get_unknown_key_id(); result.start_timer(); - Botan::Certificate_Store_MacOS certstore; - auto certs = certstore.find_all_certs(dn, kid); auto cert = certstore.find_cert(dn, kid); auto pubk_cert = certstore.find_cert_by_pubkey_sha1(kid); @@ -220,17 +192,17 @@ Test::Result no_certificate_matches() return result; } -Test::Result certificate_matching_with_dn_normalization() +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + +Test::Result certificate_matching_with_dn_normalization(Botan::Certificate_Store& certstore) { - Test::Result result("macOS Certificate Store - normalization of X.509 DN (regression test)"); + Test::Result result("System Certificate Store - normalization of X.509 DN (regression test)"); try { - auto dn = get_skewed_dn(); + auto dn = get_skewed_dn(); result.start_timer(); - Botan::Certificate_Store_MacOS certstore; - auto certs = certstore.find_all_certs(dn, std::vector<uint8_t>()); auto cert = certstore.find_cert(dn, std::vector<uint8_t>()); result.end_timer(); @@ -250,27 +222,54 @@ Test::Result certificate_matching_with_dn_normalization() return result; } -class Certstor_macOS_Tests final : public Test +#endif + +class Certstor_System_Tests final : public Test { public: std::vector<Test::Result> run() override { - std::vector<Test::Result> results; + Test::Result open_result("System Certificate Store - Open Keychain"); + + std::unique_ptr<Botan::Certificate_Store> system; + + try + { + open_result.start_timer(); + system.reset(new Botan::System_Certificate_Store); + open_result.end_timer(); + } + catch(Botan::Not_Implemented& e) + { + open_result.test_note("Skipping due to not available in current build"); + return {open_result}; + } + catch(std::exception& e) + { + open_result.test_failure(e.what()); + return {open_result}; + } - results.push_back(open_certificate_store()); - results.push_back(find_certificate_by_pubkey_sha1()); - results.push_back(find_cert_by_subject_dn()); - results.push_back(find_cert_by_subject_dn_and_key_id()); - results.push_back(find_certs_by_subject_dn_and_key_id()); - results.push_back(find_all_subjects()); - results.push_back(no_certificate_matches()); - results.push_back(certificate_matching_with_dn_normalization()); + open_result.test_success(); + + std::vector<Test::Result> results; + results.push_back(open_result); + + results.push_back(find_certificate_by_pubkey_sha1(*system)); + results.push_back(find_cert_by_subject_dn(*system)); + results.push_back(find_cert_by_subject_dn_and_key_id(*system)); + results.push_back(find_certs_by_subject_dn_and_key_id(*system)); + results.push_back(find_all_subjects(*system)); + results.push_back(no_certificate_matches(*system)); +#if defined(BOTAN_HAS_CERTSTOR_MACOS) + results.push_back(certificate_matching_with_dn_normalization(*system)); +#endif return results; } }; -BOTAN_REGISTER_TEST("certstor_macos", Certstor_macOS_Tests); +BOTAN_REGISTER_TEST("certstor_system", Certstor_System_Tests); } |