Authorities list: implement InstallAll & ReplaceAll

This change implements the design in
https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/49317.

InstallAll: enumerate all certs in the input file and install all of
them;
ReplaceAll: replace all certs with the new authorities list
Atomic: implemented via creating temporary folder and issuing swap.

Added ability to unit test service reload as well.

Tested:
1. Unit tests
2. Tested loading/deleting authorities list in QEMU.

```
root@xxx:~# busctl call xyz.openbmc_project.Certs.Manager.Authority.Ldap \
> /xyz/openbmc_project/certs/authority/ldap \
> xyz.openbmc_project.Certs.InstallAll \
> InstallAll s /tmp/trust_bundle.pem
as 3 "/xyz/openbmc_project/certs/authority/ldap/1"
"/xyz/openbmc_project/certs/authority/ldap/2"
"/xyz/openbmc_project/certs/authority/ldap/3"
root@xxx:~# ls /etc/ssl/certs/authority/
10a5d8b0.0  5b49ceaa.0	f3ddaa86.0  file0qmgPV	fileDbjTzW  fileR4TtjO
trust_bundle
root@xxx:~# busctl call
xyz.openbmc_project.Certs.Manager.Authority.Ldap
/xyz/openbmc_project/certs/authority/ldap
xyz.openbmc_project.Certs.ReplaceAll ReplaceAll s /tmp/trust_bundle.pem
root@xxx:~# ls /etc/ssl/certs/authority/
10a5d8b0.0  5b49ceaa.0	f3ddaa86.0  file1obsEZ	fileOqVoaC  filerUBZCj
trust_bundle

root@xxx:~# wget -qO- http://localhost/redfish/v1/Managers/bmc/Truststore/Certificates/
{
  "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates/",
  "@odata.type": "#CertificateCollection.CertificateCollection",
  "Description": "A Collection of TrustStore certificate instances",
  "Members": [
    {
      "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates/1"
    },
    {
      "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates/2"
    },
    {
      "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates/3"
    }
  ],
  "Members@odata.count": 3,
  "Name": "TrustStore Certificates Collection"
}
root@xxx:~# wget -qO- http://localhost/redfish/v1/Managers/bmc/Truststore/Certificates/1
{
  "@odata.id": "/redfish/v1/Managers/bmc/Truststore/Certificates/1",
  "@odata.type": "#Certificate.v1_0_0.Certificate",
  "CertificateString": "-----BEGIN CERTIFICATE-----\nMIICZTCCAgugAwIBAgIUANIf0jvaRNq1MdwxrXPnk25VrmYwCgYIKoZIzj0EAwIw\nVTETMBEGA1UEChMKY2FtcHVzLWFzaDENMAsGA1UECxMEcm9vdDEvMC0GA1UEAwwm\ne2QyZWQ1MGJkLTczMTQtNDgxZC04OWE0LTVkMjkxMmYyMGQ5NH0wIBcNNzAwMTAx\nMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMFUxEzARBgNVBAoTCmNhbXB1cy1hc2gx\nDTALBgNVBAsTBHJvb3QxLzAtBgNVBAMMJntkMmVkNTBiZC03MzE0LTQ4MWQtODlh\nNC01ZDI5MTJmMjBkOTR9MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7lp/J3Gj\nc4TKubuYtzpxu2D3STlwTwEjgFbTaLZnQ0KXt7pBrcYc3yY1t74WBluvzM9iok6Q\nDcEFX5aIYcoaAKOBtjCBszAOBgNVHQ8BAf8EBAMCAQYwKQYDVR0lBCIwIAYIKwYB\nBQUHAwEGCCsGAQUFBwMCBgorBgEEAdZ5AgcBMA8GA1UdEwEB/wQFMAMBAf8wHQYD\nVR0OBBYEFIPrX7lbeJhvHHcQ7iYOry50aYKYMBcGA1UdIAQQMA4wDAYKKwYBBAHW\neQIFBDAtBgNVHR4BAf8EIzAhoB8wHYYbLmNhbXB1cy1hc2gucHJvZC5nb29nbGUu\nY29tMAoGCCqGSM49BAMCA0gAMEUCIAS/ZrMPBj992vVVplwzH9DWDCSMu1rCgvqw\nam3byOT1AiEAyrr3FAP+7js7z+h8d94hTyy1kTn+4NOvUWrVzHUmJI8=\n-----END CERTIFICATE-----\n",
  "Description": "TrustStore Certificate",
  "Id": "1",
  "Issuer": {
    "CommonName": "{d2ed50bd-7314-481d-89a4-5d2912f20d94}",
    "Organization": "campus-ash",
    "OrganizationalUnit": "root"
  },
  "KeyUsage": [
    "CRLSigning",
    "ServerAuthentication",
    "ClientAuthentication",
    ""
  ],
  "Name": "TrustStore Certificate",
  "Subject": {
    "CommonName": "{d2ed50bd-7314-481d-89a4-5d2912f20d94}",
    "Organization": "campus-ash",
    "OrganizationalUnit": "root"
  },
  "ValidNotAfter": "9999-12-31T23:59:59+00:00",
  "ValidNotBefore": "1970-01-01T00:00:00+00:00"
}
```

Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: I495f5c1c1c4a2ac880dd3233be31b84a78d79a43
diff --git a/certificate.cpp b/certificate.cpp
index 66ea698..419f626 100644
--- a/certificate.cpp
+++ b/certificate.cpp
@@ -84,15 +84,39 @@
     {NID_code_sign, "CodeSigning"}};
 
 /**
- * @brief Copies the certificate from sourceFilePath to installFilePath
+ * @brief Dumps the PEM encoded certificate to installFilePath
  *
- * @param[in] sourceFilePath - Path to the source file.
- * @param[in] sourceFilePath - Path to the destination file.
+ * @param[in] pem - PEM encoded X509 certificate buffer.
+ * @param[in] certFilePath - Path to the destination file.
  *
  * @return void
  */
-void copyCertificate(const std::string& certSrcFilePath,
-                     const std::string& certFilePath)
+
+void dumpCertificate(const std::string& pem, const std::string& certFilePath)
+{
+    std::ofstream outputCertFileStream;
+
+    outputCertFileStream.exceptions(
+        std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit);
+
+    try
+    {
+        outputCertFileStream.open(certFilePath, std::ios::out);
+        outputCertFileStream << pem << "\n" << std::flush;
+        outputCertFileStream.close();
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>("Failed to dump certificate", entry("ERR=%s", e.what()),
+                        entry("SRC_PEM=%s", pem.c_str()),
+                        entry("DST=%s", certFilePath.c_str()));
+        elog<InternalFailure>();
+    }
+}
+} // namespace
+
+void Certificate::copyCertificate(const std::string& certSrcFilePath,
+                                  const std::string& certFilePath)
 {
     // Copy the certificate to the installation path
     // During bootup will be parsing existing file so no need to
@@ -125,7 +149,6 @@
         }
     }
 }
-} // namespace
 
 std::string
     Certificate::generateUniqueFilePath(const std::string& directoryPath)
@@ -241,6 +264,24 @@
     this->emit_object_added();
 }
 
+Certificate::Certificate(sdbusplus::bus::bus& bus, const std::string& objPath,
+                         const CertificateType& type,
+                         const std::string& installPath, X509_STORE& x509Store,
+                         const std::string& pem, Watch* watchPtr,
+                         Manager& parent) :
+    internal::CertificateInterface(bus, objPath.c_str(), true),
+    objectPath(objPath), certType(type), certInstallPath(installPath),
+    certWatch(watchPtr), manager(parent)
+{
+    // Generate certificate file path
+    certFilePath = generateUniqueFilePath(installPath);
+
+    // install the certificate
+    install(x509Store, pem);
+
+    this->emit_object_added();
+}
+
 Certificate::~Certificate()
 {
     if (!fs::remove(certFilePath))
@@ -343,6 +384,45 @@
     }
 }
 
+void Certificate::install(X509_STORE& x509Store, const std::string& pem)
+{
+    log<level::INFO>("Certificate install ", entry("PEM_STR=%s", pem.data()));
+
+    if (certType != CertificateType::Authority)
+    {
+        log<level::ERR>("Bulk install error: Unsupported Type; only authority "
+                        "supports bulk install",
+                        entry("TYPE=%s", certificateTypeToString(certType)));
+        elog<InternalFailure>();
+    }
+
+    // stop watch for user initiated certificate install
+    if (certWatch)
+    {
+        certWatch->stopWatch();
+    }
+
+    // Load Certificate file into the X509 structure.
+    internal::X509Ptr cert = parseCert(pem);
+    // Perform validation; no type specific compare keys function
+    validateCertificateAgainstStore(x509Store, *cert);
+    validateCertificateStartDate(*cert);
+    validateCertificateInSSLContext(*cert);
+
+    // Copy the PEM to the installation path
+    dumpCertificate(pem, certFilePath);
+    storageUpdate();
+    // Keep certificate ID
+    certId = generateCertId(*cert);
+    // Parse the certificate file and populate properties
+    populateProperties(*cert);
+    // restart watch
+    if (certWatch)
+    {
+        certWatch->startWatch();
+    }
+}
+
 void Certificate::populateProperties()
 {
     internal::X509Ptr cert = loadCert(certInstallPath);
@@ -595,4 +675,25 @@
 {
     manager.deleteCertificate(this);
 }
+
+std::string Certificate::getObjectPath()
+{
+    return objectPath;
+}
+
+std::string Certificate::getCertFilePath()
+{
+    return certFilePath;
+}
+
+void Certificate::setCertFilePath(const std::string& path)
+{
+    certFilePath = path;
+}
+
+void Certificate::setCertInstallPath(const std::string& path)
+{
+    certInstallPath = path;
+}
+
 } // namespace phosphor::certs