Repository: randombit/botan
Pull request #1893 (2019-04-12 22:48:10)

Add a wrapper class that wraps macOS or Linux certificate stores.
@alnkpa @reneme can you review?

randombit: It seems my assumption in #1885 that the certificate store would never include non-CA certificates was not well founded; Ubuntu (and presumably then also Debian) include such certs in their trust stores. Who knows how this came about. I added an option so for the system cert store, we just ignore such certs, while by default we continue to throw an error. (Thus ignoring a problem we can do nothing about, but still sanity checking when someone uses the flatfile store with their own custom roots).

Patch:
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;


Test patch:
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')
@@ -782,7 +797,6 @@ def main(args=None):
     start_time = time.time()
 
     test_fns = [
-        cli_asn1_tests,
         cli_asn1_tests,
         cli_base58_tests,
         cli_base64_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
similarity index 62%
rename from src/tests/test_certstor_macos.cpp
rename to 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);
 
 }