Refactoring of certificates managing and storing

This commit is about third stage code refactoring proposed by Zbigniew
Kurzynski (zbigniew.kurzynski@intel.com) on the mailing list
("phosphor-certificate-manager refactoring"): "Changing the way of
managing and storing TrustStore certificates".

Following changes are being implemented:
 - each certificate has its own and unique ID,
 - authority certificates are kept in files with random names under
/etc/ssl/certs/authority and symlinks (based on subject name hash) are
created to satisfy OpenSSL library,
 - restarting bmcweb was moved from certificate class to certs_manager
class
 - certificate uniqueness is based on certificate ID and checked while
installing and replacing operation in certs_manager class.

Tested by doing installing/replacing/removing operations on certificate
storage using RedFish API.

Signed-off-by: Zbigniew Lukwinski <zbigniew.lukwinski@linux.intel.com>
Change-Id: I0b02a10b940279c46ad9ee07925794262133b1b0
diff --git a/test/certs_manager_test.cpp b/test/certs_manager_test.cpp
index 4a3c6a4..30f0dd1 100644
--- a/test/certs_manager_test.cpp
+++ b/test/certs_manager_test.cpp
@@ -3,6 +3,13 @@
 #include "certificate.hpp"
 #include "certs_manager.hpp"
 
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
 #include <algorithm>
 #include <filesystem>
 #include <fstream>
@@ -96,6 +103,35 @@
                           std::istreambuf_iterator<char>(f2.rdbuf()));
     }
 
+    std::string getCertSubjectNameHash(const std::string& certFilePath)
+    {
+        std::unique_ptr<X509, decltype(&::X509_free)> cert(X509_new(),
+                                                           ::X509_free);
+        if (!cert)
+        {
+            std::string();
+        }
+
+        std::unique_ptr<BIO, decltype(&::BIO_free)> bioCert(
+            BIO_new_file(certFilePath.c_str(), "rb"), ::BIO_free);
+        if (!bioCert)
+        {
+            std::string();
+        }
+
+        X509* x509 = cert.get();
+        if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
+        {
+            std::string();
+        }
+
+        unsigned long hash = X509_subject_name_hash(cert.get());
+        static constexpr auto AUTH_CERT_HASH_LENGTH = 9;
+        char hashBuf[AUTH_CERT_HASH_LENGTH];
+        sprintf(hashBuf, "%08lx", hash);
+        return std::string(hashBuf);
+    }
+
   protected:
     sdbusplus::bus::bus bus;
     std::string certificateFile, CSRFile, privateKeyFile, rsaPrivateKeyFilePath;
@@ -213,7 +249,8 @@
 
     EXPECT_FALSE(certs.empty());
 
-    std::string verifyPath = verifyDir + "/" + certs[0]->getHash() + ".0";
+    std::string verifyPath =
+        verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
 
     // Check that certificate has been created at installation directory
     EXPECT_FALSE(fs::is_empty(verifyDir));
@@ -223,14 +260,11 @@
     EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
 }
 
-/** @brief Check if in athority mode user can install a certificate with certain
- * subject hash once, but cannot install another one with the same hash
+/** @brief Check if in authority mode user can't install the same
+ * certificate twice.
  */
-TEST_F(TestCertificates, InvokeInstallSameSubjectTwice)
+TEST_F(TestCertificates, InvokeInstallSameCertTwice)
 {
-    using NotAllowed =
-        sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
-
     std::string endpoint("ldap");
     std::string unit("");
     std::string type("authority");
@@ -250,23 +284,22 @@
 
     EXPECT_FALSE(certs.empty());
 
-    std::string verifyPath = verifyDir + "/" + certs[0]->getHash() + ".0";
-
     // Check that certificate has been created at installation directory
+    std::string verifyPath =
+        verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
     EXPECT_FALSE(fs::is_empty(verifyDir));
     EXPECT_TRUE(fs::exists(verifyPath));
 
     // Check that installed cert is identical to input one
     EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
 
-    // Try to install another one with the same subject
-    createNewCertificate(false);
-
+    using NotAllowed =
+        sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
     EXPECT_THROW(
         {
             try
             {
-                // install second certificate
+                // Try to install the same certificate second time
                 mainApp.install(certificateFile);
             }
             catch (const NotAllowed& e)
@@ -281,6 +314,141 @@
     EXPECT_TRUE(fs::exists(verifyPath));
 }
 
+/** @brief Check if in authority mode user cannot install a certificate with
+ * certain subject hash twice.
+ */
+TEST_F(TestCertificates, InvokeInstallSameSubjectTwice)
+{
+    std::string endpoint("ldap");
+    std::string unit("");
+    std::string type("authority");
+    std::string verifyDir(certDir);
+    UnitsToRestart verifyUnit(unit);
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(certDir));
+    MainApp mainApp(&manager);
+    mainApp.install(certificateFile);
+
+    std::vector<std::unique_ptr<Certificate>>& certs =
+        manager.getCertificates();
+
+    EXPECT_FALSE(certs.empty());
+
+    // Check that certificate has been created at installation directory
+    std::string verifyPath0 =
+        verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
+    EXPECT_FALSE(fs::is_empty(verifyDir));
+    EXPECT_TRUE(fs::exists(verifyPath0));
+
+    // Check that installed cert is identical to input one
+    EXPECT_TRUE(compareFiles(certificateFile, verifyPath0));
+
+    // Prepare second certificate with the same subject
+    createNewCertificate();
+
+    using NotAllowed =
+        sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
+    EXPECT_THROW(
+        {
+            try
+            {
+                // Install second certificate
+                mainApp.install(certificateFile);
+            }
+            catch (const NotAllowed& e)
+            {
+                throw;
+            }
+        },
+        NotAllowed);
+
+    // Expect there are exactly two certificates in the collection
+    EXPECT_EQ(certs.size(), 1);
+
+    // Check that the original/first certificate has been not removed
+    EXPECT_FALSE(fs::is_empty(verifyDir));
+    EXPECT_TRUE(fs::exists(verifyPath0));
+}
+
+/** @brief Check if in authority mode user can't install more than
+ * AUTHORITY_CERTIFICATES_LIMIT certificates.
+ */
+TEST_F(TestCertificates, InvokeInstallAuthCertLimit)
+{
+    std::string endpoint("ldap");
+    std::string unit("");
+    std::string type("authority");
+    std::string verifyDir(certDir);
+    UnitsToRestart verifyUnit(unit);
+    auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
+    auto event = sdeventplus::Event::get_default();
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+    Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
+                    std::move(certDir));
+    MainApp mainApp(&manager);
+
+    std::vector<std::unique_ptr<Certificate>>& certs =
+        manager.getCertificates();
+
+    std::vector<std::string> verifyPaths;
+
+    // Prepare maximum number of ceritificates
+    for (std::size_t i = 0; i < AUTHORITY_CERTIFICATES_LIMIT; ++i)
+    {
+        // Prepare new certificatate
+        createNewCertificate(true);
+
+        // Install ceritificate
+        mainApp.install(certificateFile);
+
+        // Check number of certificates in the collection
+        EXPECT_EQ(certs.size(), i + 1);
+
+        // Check that certificate has been created at installation directory
+        std::string verifyPath =
+            verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
+        EXPECT_FALSE(fs::is_empty(verifyDir));
+        EXPECT_TRUE(fs::exists(verifyPath));
+
+        // Check that installed cert is identical to input one
+        EXPECT_TRUE(compareFiles(certificateFile, verifyPath));
+
+        // Save current certificate file for later check
+        verifyPaths.push_back(verifyPath);
+    }
+
+    // Prepare new certificatate
+    createNewCertificate(true);
+
+    using NotAllowed =
+        sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
+    EXPECT_THROW(
+        {
+            try
+            {
+                // Try to install one more certificate
+                mainApp.install(certificateFile);
+            }
+            catch (const NotAllowed& e)
+            {
+                throw;
+            }
+        },
+        NotAllowed);
+
+    // Check that the original certificate has been not removed
+    EXPECT_FALSE(fs::is_empty(verifyDir));
+    for (int i = 0; i < AUTHORITY_CERTIFICATES_LIMIT; ++i)
+    {
+        EXPECT_TRUE(fs::exists(verifyPaths[i]));
+    }
+}
+
 /** @brief Compare the installed certificate with the copied certificate
  */
 TEST_F(TestCertificates, CompareInstalledCertificate)
@@ -389,7 +557,8 @@
         // Certificate successfully installed
         EXPECT_FALSE(certs.empty());
 
-        std::string verifyPath = verifyDir + "/" + certs[0]->getHash() + ".0";
+        std::string verifyPath =
+            verifyDir + "/" + getCertSubjectNameHash(certificateFile) + ".0";
 
         // Check that certificate has been created at installation directory
         EXPECT_FALSE(fs::is_empty(verifyDir));