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
diff --git a/certificate.hpp b/certificate.hpp
index fa3191a..4e90ea1 100644
--- a/certificate.hpp
+++ b/certificate.hpp
@@ -101,6 +101,25 @@
CertificateType type, const std::string& installPath,
const std::string& uploadPath, Watch* watch, Manager& parent);
+ /** @brief Constructor for the Certificate Object; a variant for authorities
+ * list install
+ * @param[in] bus - Bus to attach to.
+ * @param[in] objPath - Object path to attach to
+ * @param[in] type - Type of the certificate
+ * @param[in] installPath - Path of the certificate to install
+ * @param[in] x509Store - an initialized X509 store used for certificate
+ * validation; Certificate object doesn't own it
+ * @param[in] pem - Content of the certificate file to upload; it shall be
+ * a single PEM encoded x509 certificate
+ * @param[in] watchPtr - watch on self signed certificate
+ * @param[in] parent - Pointer to the manager which owns the constructed
+ * Certificate object
+ */
+ 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);
+
/** @brief Validate and Replace/Install the certificate file
* Install/Replace the existing certificate file with another
* (possibly CA signed) Certificate file.
@@ -108,6 +127,15 @@
*/
void install(const std::string& filePath);
+ /** @brief Validate and Replace/Install the certificate file
+ * Install/Replace the existing certificate file with another
+ * (possibly CA signed) Certificate file.
+ * @param[in] x509Store - an initialized X509 store used for certificate
+ * validation; Certificate object doesn't own it
+ * @param[in] pem - a string buffer which stores a PEM encoded certificate.
+ */
+ void install(X509_STORE& x509Store, const std::string& pem);
+
/** @brief Validate certificate and replace the existing certificate
* @param[in] filePath - Certificate file path.
*/
@@ -144,6 +172,44 @@
*/
void delete_() override;
+ /**
+ * @brief Generate file name which is unique in the provided directory.
+ *
+ * @param[in] directoryPath - Directory path.
+ *
+ * @return File path.
+ */
+ static std::string generateUniqueFilePath(const std::string& directoryPath);
+
+ /**
+ * @brief Copies the certificate from sourceFilePath to installFilePath
+ *
+ * @param[in] sourceFilePath - Path to the source file.
+ * @param[in] certFilePath - Path to the destination file.
+ *
+ * @return void
+ */
+ static void copyCertificate(const std::string& certSrcFilePath,
+ const std::string& certFilePath);
+
+ /**
+ * @brief Returns the associated dbus object path.
+ */
+ std::string getObjectPath();
+
+ /**
+ * @brief Returns the associated cert file path.
+ */
+ std::string getCertFilePath();
+
+ /** @brief: Set the data member |certFilePath| to |path|
+ */
+ void setCertFilePath(const std::string& path);
+
+ /** @brief: Set the data member |certInstallPath| to |path|
+ */
+ void setCertInstallPath(const std::string& path);
+
private:
/**
* @brief Populate certificate properties by parsing given certificate
@@ -173,15 +239,6 @@
bool compareKeys(const std::string& filePath);
/**
- * @brief Generate file name which is unique in the provided directory.
- *
- * @param[in] directoryPath - Directory path.
- *
- * @return File path.
- */
- std::string generateUniqueFilePath(const std::string& directoryPath);
-
- /**
* @brief Generate authority certificate file path corresponding with
* OpenSSL requirements.
*
diff --git a/certs_manager.cpp b/certs_manager.cpp
index 831928f..d8e99b7 100644
--- a/certs_manager.cpp
+++ b/certs_manager.cpp
@@ -2,6 +2,8 @@
#include "certs_manager.hpp"
+#include "x509_utils.hpp"
+
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <openssl/ec.h>
@@ -22,6 +24,7 @@
#include <cstdlib>
#include <cstring>
#include <exception>
+#include <fstream>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/log.hpp>
@@ -61,11 +64,70 @@
using X509ReqPtr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
using EVPPkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BignumPtr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
+using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
constexpr int supportedKeyBitLength = 2048;
constexpr int defaultKeyBitLength = 2048;
// secp224r1 is equal to RSA 2048 KeyBitLength. Refer RFC 5349
constexpr auto defaultKeyCurveID = "secp224r1";
+// PEM certificate block markers, defined in go/rfc/7468.
+constexpr std::string_view beginCertificate = "-----BEGIN CERTIFICATE-----";
+constexpr std::string_view endCertificate = "-----END CERTIFICATE-----";
+
+/**
+ * @brief Splits the given authorities list file and returns an array of
+ * individual PEM encoded x509 certificate.
+ *
+ * @param[in] sourceFilePath - Path to the authorities list file.
+ *
+ * @return An array of individual PEM encoded x509 certificate
+ */
+std::vector<std::string> splitCertificates(const std::string& sourceFilePath)
+{
+ std::ifstream inputCertFileStream;
+ inputCertFileStream.exceptions(
+ std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit);
+
+ std::stringstream pemStream;
+ std::vector<std::string> certificatesList;
+ try
+ {
+ inputCertFileStream.open(sourceFilePath);
+ pemStream << inputCertFileStream.rdbuf();
+ inputCertFileStream.close();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Failed to read certificates list",
+ entry("ERR=%s", e.what()),
+ entry("SRC=%s", sourceFilePath.c_str()));
+ elog<InternalFailure>();
+ }
+ std::string pem = pemStream.str();
+ size_t begin = 0;
+ // |begin| points to the current start position for searching the next
+ // |beginCertificate| block. When we find the beginning of the certificate,
+ // we extract the content between the beginning and the end of the current
+ // certificate. And finally we move |begin| to the end of the current
+ // certificate to start searching the next potential certificate.
+ for (begin = pem.find(beginCertificate, begin); begin != std::string::npos;
+ begin = pem.find(beginCertificate, begin))
+ {
+ size_t end = pem.find(endCertificate, begin);
+ if (end == std::string::npos)
+ {
+ log<level::ERR>(
+ "invalid PEM contains a BEGIN identifier without an END");
+ elog<InvalidCertificate>(InvalidCertificateReason(
+ "invalid PEM contains a BEGIN identifier without an END"));
+ }
+ end += endCertificate.size();
+ certificatesList.emplace_back(pem.substr(begin, end - begin));
+ begin = end;
+ }
+ return certificatesList;
+}
+
} // namespace
Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
@@ -217,6 +279,100 @@
return certObjectPath;
}
+std::vector<sdbusplus::message::object_path>
+ Manager::installAll(const std::string filePath)
+{
+ if (certType != CertificateType::Authority)
+ {
+ elog<NotAllowed>(
+ NotAllowedReason("The InstallAll interface is only allowed for "
+ "Authority certificates"));
+ }
+
+ if (!installedCerts.empty())
+ {
+ elog<NotAllowed>(NotAllowedReason(
+ "There are already root certificates; Call DeleteAll then "
+ "InstallAll, or use ReplaceAll"));
+ }
+
+ fs::path sourceFile(filePath);
+ if (!fs::exists(sourceFile))
+ {
+ log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+ std::vector<std::string> authorities = splitCertificates(sourceFile);
+ if (authorities.size() > maxNumAuthorityCertificates)
+ {
+ elog<NotAllowed>(NotAllowedReason("Certificates limit reached"));
+ }
+
+ fs::path authorityStore(certInstallPath);
+ fs::path authoritiesListFile =
+ authorityStore / defaultAuthoritiesListFileName;
+
+ // Atomically install all the certificates
+ fs::path tempPath = Certificate::generateUniqueFilePath(authorityStore);
+ fs::create_directory(tempPath);
+ // Copies the authorities list
+ Certificate::copyCertificate(sourceFile,
+ tempPath / defaultAuthoritiesListFileName);
+ std::vector<std::unique_ptr<Certificate>> tempCertificates;
+ uint64_t tempCertIdCounter = certIdCounter;
+ X509StorePtr x509Store = getX509Store(sourceFile);
+ for (const auto& authority : authorities)
+ {
+ std::string certObjectPath =
+ objectPath + '/' + std::to_string(tempCertIdCounter);
+ tempCertificates.emplace_back(std::make_unique<Certificate>(
+ bus, certObjectPath, certType, tempPath, *x509Store, authority,
+ certWatchPtr.get(), *this));
+ tempCertIdCounter++;
+ }
+
+ // We are good now, issue swap
+ installedCerts = std::move(tempCertificates);
+ certIdCounter = tempCertIdCounter;
+ // Rename all the certificates including the authorities list
+ for (const fs::path& f : fs::directory_iterator(tempPath))
+ {
+ if (fs::is_symlink(f))
+ {
+ continue;
+ }
+ fs::rename(/*from=*/f, /*to=*/certInstallPath / f.filename());
+ }
+ // Update file locations and create symbol links
+ for (const auto& cert : installedCerts)
+ {
+ cert->setCertInstallPath(certInstallPath);
+ cert->setCertFilePath(certInstallPath /
+ fs::path(cert->getCertFilePath()).filename());
+ cert->storageUpdate();
+ }
+ // Remove the temporary folder
+ fs::remove_all(tempPath);
+
+ std::vector<sdbusplus::message::object_path> objects;
+ for (const auto& certificate : installedCerts)
+ {
+ objects.emplace_back(certificate->getObjectPath());
+ }
+
+ reloadOrReset(unitToRestart);
+ return objects;
+}
+
+std::vector<sdbusplus::message::object_path>
+ Manager::replaceAll(std::string filePath)
+{
+ installedCerts.clear();
+ certIdCounter = 1;
+ storageUpdate();
+ return installAll(std::move(filePath));
+}
+
void Manager::deleteAll()
{
// TODO: #Issue 4 when a certificate is deleted system auto generates
@@ -225,6 +381,17 @@
// deletion if only applicable for REST server and Bmcweb does not allow
// deletion of certificates
installedCerts.clear();
+ // If the authorities list exists, delete it as well
+ if (certType == CertificateType::Authority)
+ {
+ if (fs::path authoritiesList =
+ fs::path(certInstallPath) / defaultAuthoritiesListFileName;
+ fs::exists(authoritiesList))
+ {
+ fs::remove(authoritiesList);
+ }
+ }
+ certIdCounter = 1;
storageUpdate();
reloadOrReset(unitToRestart);
}
@@ -757,6 +924,22 @@
log<level::ERR>("Certificate installation path exists and it is "
"not a directory");
elog<InternalFailure>();
+ }
+
+ // If the authorities list exists, recover from it and return
+ if (fs::path authoritiesListFilePath =
+ fs::path(certInstallPath) / defaultAuthoritiesListFileName;
+ fs::exists(authoritiesListFilePath))
+ {
+ // remove all other files and directories
+ for (auto& path : fs::directory_iterator(certInstallPath))
+ {
+ if (path.path() != authoritiesListFilePath)
+ {
+ fs::remove_all(path);
+ }
+ }
+ installAll(authoritiesListFilePath);
return;
}
diff --git a/certs_manager.hpp b/certs_manager.hpp
index 89887f7..fe35a3f 100644
--- a/certs_manager.hpp
+++ b/certs_manager.hpp
@@ -18,6 +18,8 @@
#include <vector>
#include <xyz/openbmc_project/Certs/CSR/Create/server.hpp>
#include <xyz/openbmc_project/Certs/Install/server.hpp>
+#include <xyz/openbmc_project/Certs/InstallAll/server.hpp>
+#include <xyz/openbmc_project/Certs/ReplaceAll/server.hpp>
#include <xyz/openbmc_project/Collection/DeleteAll/server.hpp>
namespace phosphor::certs
@@ -28,7 +30,9 @@
using ManagerInterface = sdbusplus::server::object_t<
sdbusplus::xyz::openbmc_project::Certs::server::Install,
sdbusplus::xyz::openbmc_project::Certs::CSR::server::Create,
- sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll>;
+ sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll,
+ sdbusplus::xyz::openbmc_project::Certs::server::InstallAll,
+ sdbusplus::xyz::openbmc_project::Certs::server::ReplaceAll>;
}
class Manager : public internal::ManagerInterface
@@ -73,6 +77,27 @@
*/
std::string install(const std::string filePath) override;
+ /** @brief Implementation for InstallAll
+ * Install the authority list and restart the associated services.
+ *
+ * @param[in] path - Path of the file that contains a list of root
+ * certificates.
+ *
+ * @return D-Bus object path to created objects.
+ */
+ std::vector<sdbusplus::message::object_path>
+ installAll(std::string path) override;
+
+ /** @brief Implementation for ReplaceAll
+ * Replace the current authority lists and restart the associated services.
+ *
+ * @param[in] path - Path of file that contains multiple root certificates.
+ *
+ * @return D-Bus object path to created objects.
+ */
+ std::vector<sdbusplus::message::object_path>
+ replaceAll(std::string filePath) override;
+
/** @brief Implementation for DeleteAll
* Delete all objects in the collection.
*/
@@ -175,6 +200,12 @@
*/
std::vector<std::unique_ptr<Certificate>>& getCertificates();
+ /** @brief Systemd unit reload or reset helper function
+ * Reload if the unit supports it and use a restart otherwise.
+ * @param[in] unit - service need to reload.
+ */
+ virtual void reloadOrReset(const std::string& unit);
+
private:
void generateCSRHelper(std::vector<std::string> alternativeNames,
std::string challengePassword, std::string city,
@@ -262,12 +293,6 @@
*/
void storageUpdate();
- /** @brief Systemd unit reload or reset helper function
- * Reload if the unit supports it and use a restart otherwise.
- * @param[in] unit - service need to reload.
- */
- void reloadOrReset(const std::string& unit);
-
/** @brief Check if provided certificate is unique across all certificates
* on the internal list.
* @param[in] certFilePath - Path to the file with certificate for
diff --git a/config.h.in b/config.h.in
index 6e6a3e7..b363fd3 100644
--- a/config.h.in
+++ b/config.h.in
@@ -18,3 +18,6 @@
/* The maximum number of Authority certificates the service allows. */
inline constexpr size_t maxNumAuthorityCertificates = @authority_limit@;
+
+/* The default name of the authorities list file. */
+inline constexpr char defaultAuthoritiesListFileName[] = "@authorities_list_name@";
diff --git a/meson.build b/meson.build
index 68c56c6..c647bc8 100644
--- a/meson.build
+++ b/meson.build
@@ -43,6 +43,10 @@
'authority_limit',
get_option('authority-limit')
)
+config_data.set(
+ 'authorities_list_name',
+ get_option('authorities-list-name')
+)
configure_file(
input: 'config.h.in',
diff --git a/meson_options.txt b/meson_options.txt
index c08b9eb..18190e3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -20,3 +20,9 @@
type: 'feature',
description: 'Install nslcd authority cert configs',
)
+
+option('authorities-list-name',
+ type: 'string',
+ value: 'trust_bundle',
+ description: 'File name of the authorities list',
+)
diff --git a/test/certs_manager_test.cpp b/test/certs_manager_test.cpp
index 6e4c37f..b3a1563 100644
--- a/test/certs_manager_test.cpp
+++ b/test/certs_manager_test.cpp
@@ -23,11 +23,13 @@
#include <sdbusplus/bus.hpp>
#include <sdeventplus/event.hpp>
#include <string>
+#include <unordered_set>
#include <utility>
#include <vector>
#include <xyz/openbmc_project/Certs/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace phosphor::certs
@@ -37,6 +39,31 @@
namespace fs = std::filesystem;
using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using ::testing::Eq;
+using ::testing::Return;
+// Compares two files; returns true only if the two are the same
+bool compareFiles(const std::string& file1, const std::string& file2)
+{
+ std::ifstream f1(file1, std::ifstream::binary | std::ifstream::ate);
+ std::ifstream f2(file2, std::ifstream::binary | std::ifstream::ate);
+
+ if (f1.fail() || f2.fail())
+ {
+ return false; // file problem
+ }
+
+ if (f1.tellg() != f2.tellg())
+ {
+ return false; // size mismatch
+ }
+
+ // seek back to beginning and use std::equal to compare contents
+ f1.seekg(0, std::ifstream::beg);
+ f2.seekg(0, std::ifstream::beg);
+ return std::equal(std::istreambuf_iterator<char>(f1.rdbuf()),
+ std::istreambuf_iterator<char>(),
+ std::istreambuf_iterator<char>(f2.rdbuf()));
+}
/**
* Class to generate certificate file and test verification of certificate file
@@ -228,23 +255,39 @@
phosphor::certs::CSR* csr_;
};
+class ManagerInTest : public phosphor::certs::Manager
+{
+ public:
+ static constexpr std::string_view unitToRestartInTest =
+ "xyz.openbmc_project.awesome-service";
+ ManagerInTest(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
+ const char* path, CertificateType type,
+ const std::string& unit, const std::string& installPath) :
+ Manager(bus, event, path, type, unit, installPath)
+ {
+ }
+
+ MOCK_METHOD(void, reloadOrReset, (const std::string&), (override));
+};
+
/** @brief Check if server install routine is invoked for server setup
*/
TEST_F(TestCertificates, InvokeServerInstall)
{
std::string endpoint("https");
- std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ installPath);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
@@ -255,18 +298,19 @@
TEST_F(TestCertificates, InvokeClientInstall)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ installPath);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
@@ -277,17 +321,18 @@
TEST_F(TestCertificates, InvokeAuthorityInstall)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ verifyDir);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
// install the default certificate that's valid from today to 100 years
// later
@@ -319,17 +364,18 @@
TEST_F(TestCertificates, InvokeAuthorityInstallNeverExpiredRootCert)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ certDir);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
// install the certificate that's valid from the Unix Epoch to Dec 31, 9999
@@ -359,17 +405,18 @@
TEST_F(TestCertificates, InvokeInstallSameCertTwice)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ std::move(certDir));
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
@@ -414,17 +461,19 @@
TEST_F(TestCertificates, InvokeInstallSameSubjectTwice)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ certDir);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return())
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
@@ -470,17 +519,18 @@
TEST_F(TestCertificates, InvokeInstallAuthCertLimit)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ certDir);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillRepeatedly(Return());
MainApp mainApp(&manager);
std::vector<std::unique_ptr<Certificate>>& certs =
@@ -545,19 +595,19 @@
TEST_F(TestCertificates, CompareInstalledCertificate)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ installPath);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
@@ -569,12 +619,10 @@
TEST_F(TestCertificates, TestNoCertificateFile)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
std::string uploadFile = "nofile.pem";
@@ -585,8 +633,8 @@
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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type,
+ verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(uploadFile);
}
@@ -604,17 +652,20 @@
TEST_F(TestCertificates, TestReplaceCertificate)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ std::move(installPath));
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return())
+ .WillOnce(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
@@ -631,23 +682,25 @@
TEST_F(TestCertificates, TestAuthorityReplaceCertificate)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ certDir);
+ constexpr const unsigned int REPLACE_ITERATIONS = 10;
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .Times(REPLACE_ITERATIONS + 1)
+ .WillRepeatedly(Return());
MainApp mainApp(&manager);
mainApp.install(certificateFile);
std::vector<std::unique_ptr<Certificate>>& certs =
manager.getCertificates();
- constexpr const unsigned int REPLACE_ITERATIONS = 10;
for (unsigned int i = 0; i < REPLACE_ITERATIONS; i++)
{
@@ -679,17 +732,18 @@
TEST_F(TestCertificates, TestStorageDeleteCertificate)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Authority;
std::string verifyDir(certDir);
- std::string verifyUnit(unit);
+ std::string verifyUnit((ManagerInTest::unitToRestartInTest));
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ certDir);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillRepeatedly(Return());
MainApp mainApp(&manager);
// Check if certificate placeholder dir is empty
@@ -731,12 +785,10 @@
TEST_F(TestCertificates, TestEmptyCertificateFile)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
std::string emptyFile("emptycert.pem");
@@ -750,8 +802,8 @@
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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type,
+ verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(emptyFile);
}
@@ -770,9 +822,7 @@
TEST_F(TestCertificates, TestInvalidCertificateFile)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::ofstream ofs;
ofs.open(certificateFile, std::ofstream::out);
@@ -783,7 +833,7 @@
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
@@ -793,8 +843,8 @@
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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type,
+ verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
}
@@ -859,12 +909,10 @@
TEST_F(TestInvalidCertificate, TestMissingPrivateKey)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
@@ -874,8 +922,8 @@
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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type,
+ verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
}
@@ -893,12 +941,10 @@
TEST_F(TestInvalidCertificate, TestMissingCeritificate)
{
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + keyFile);
std::string verifyPath(installPath);
- std::string verifyUnit(unit);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
EXPECT_THROW(
@@ -908,8 +954,8 @@
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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type,
+ verifyUnit, installPath);
MainApp mainApp(&manager);
mainApp.install(keyFile);
}
@@ -930,18 +976,17 @@
using NotAllowed =
sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed;
std::string endpoint("ldap");
- std::string unit;
CertificateType type = CertificateType::Client;
- ;
std::string installPath(certDir + "/" + certificateFile);
std::string verifyPath(installPath);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(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(installPath));
+ ManagerInTest manager(bus, event, objPath.c_str(), type, verifyUnit,
+ installPath);
MainApp mainApp(&manager);
mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
@@ -1413,17 +1458,407 @@
TEST_F(TestCertificates, TestGenerateRSAPrivateKeyFile)
{
std::string endpoint("https");
- std::string unit;
CertificateType type = CertificateType::Server;
std::string installPath(certDir + "/" + certificateFile);
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
auto objPath = std::string(objectNamePrefix) + '/' +
certificateTypeToString(type) + '/' + endpoint;
auto event = sdeventplus::Event::get_default();
EXPECT_FALSE(fs::exists(rsaPrivateKeyFilePath));
- Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
- std::move(installPath));
+ Manager manager(bus, event, objPath.c_str(), type, verifyUnit, installPath);
EXPECT_TRUE(fs::exists(rsaPrivateKeyFilePath));
}
+
+/**
+ * Class to test Authorities List installation and replacement
+ */
+class AuthoritiesListTest : public testing::Test
+{
+ public:
+ AuthoritiesListTest() :
+ bus(sdbusplus::bus::new_default()),
+ authoritiesListFolder(
+ Certificate::generateUniqueFilePath(fs::temp_directory_path()))
+ {
+ fs::create_directory(authoritiesListFolder);
+ createAuthoritiesList(maxNumAuthorityCertificates);
+ }
+ ~AuthoritiesListTest() override
+ {
+ fs::remove_all(authoritiesListFolder);
+ }
+
+ protected:
+ // Creates a testing authorities list which consists of |count| root
+ // certificates
+ void createAuthoritiesList(int count)
+ {
+ fs::path srcFolder = fs::temp_directory_path();
+ srcFolder = Certificate::generateUniqueFilePath(srcFolder);
+ fs::create_directory(srcFolder);
+ createSingleAuthority(srcFolder, "root_0");
+ sourceAuthoritiesListFile = srcFolder / "root_0_cert";
+ for (int i = 1; i < count; ++i)
+ {
+ std::string name = "root_" + std::to_string(i);
+ createSingleAuthority(srcFolder, name);
+ appendContentFromFile(sourceAuthoritiesListFile,
+ srcFolder / (name + "_cert"));
+ }
+ }
+
+ // Creates a single self-signed root certificate in given |path|; the key
+ // will be |path|/|cn|_key, the cert will be |path|/|cn|_cert, and the cn
+ // will be "/O=openbmc-project.xyz/C=US/ST=CA/CN=|cn|"
+ static void createSingleAuthority(const std::string& path,
+ const std::string& cn)
+ {
+ std::string key = fs::path(path) / (cn + "_key");
+ std::string cert = fs::path(path) / (cn + "_cert");
+ std::string cmd = "openssl req -x509 -sha256 -newkey rsa:2048 -keyout ";
+ cmd += key + " -out " + cert + " -nodes --days 365000 ";
+ cmd += "-subj /O=openbmc-project.xyz/CN=" + cn;
+ ASSERT_EQ(std::system(cmd.c_str()), 0);
+ }
+
+ // Appends the content of the |from| file to the |to| file.
+ static void appendContentFromFile(const std::string& to,
+ const std::string& from)
+ {
+ ASSERT_NO_THROW({
+ std::ifstream inputCertFileStream;
+ std::ofstream outputCertFileStream;
+ inputCertFileStream.exceptions(std::ifstream::failbit |
+ std::ifstream::badbit |
+ std::ifstream::eofbit);
+ outputCertFileStream.exceptions(std::ofstream::failbit |
+ std::ofstream::badbit |
+ std::ofstream::eofbit);
+ inputCertFileStream.open(from);
+ outputCertFileStream.open(to, std::ios::app);
+ outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
+ inputCertFileStream.close();
+ outputCertFileStream.close();
+ });
+ }
+
+ // Appends the content of the |from| buffer to the |to| file.
+ static void setContentFromString(const std::string& to,
+ const std::string& from)
+ {
+ ASSERT_NO_THROW({
+ std::ofstream outputCertFileStream;
+ outputCertFileStream.exceptions(std::ofstream::failbit |
+ std::ofstream::badbit |
+ std::ofstream::eofbit);
+ outputCertFileStream.open(to, std::ios::out);
+ outputCertFileStream << from << std::flush;
+ outputCertFileStream.close();
+ });
+ }
+
+ // Verifies the effect of InstallAll or ReplaceAll
+ void verifyCertificates(std::vector<std::unique_ptr<Certificate>>& certs)
+ {
+ // The trust bundle file has been copied over
+ EXPECT_FALSE(fs::is_empty(authoritiesListFolder));
+ EXPECT_TRUE(
+ compareFiles(authoritiesListFolder / defaultAuthoritiesListFileName,
+ sourceAuthoritiesListFile));
+
+ ASSERT_EQ(certs.size(), maxNumAuthorityCertificates);
+ // Check attributes and alias
+ for (size_t i = 0; i < certs.size(); ++i)
+ {
+ std::string name = "root_" + std::to_string(i);
+ EXPECT_EQ(certs[i]->subject(), "O=openbmc-project.xyz,CN=" + name);
+ EXPECT_EQ(certs[i]->issuer(), "O=openbmc-project.xyz,CN=" + name);
+ std::string symbolLink =
+ authoritiesListFolder /
+ (certs[i]->getCertId().substr(0, 8) + ".0");
+ ASSERT_TRUE(fs::exists(symbolLink));
+ compareFileAgainstString(symbolLink, certs[i]->certificateString());
+ }
+ }
+
+ // Expects that the content of |path| file is |buffer|.
+ static void compareFileAgainstString(const std::string& path,
+ const std::string& buffer)
+ {
+ ASSERT_NO_THROW({
+ std::ifstream inputCertFileStream;
+ inputCertFileStream.exceptions(std::ifstream::failbit |
+ std::ifstream::badbit |
+ std::ifstream::eofbit);
+ inputCertFileStream.open(path);
+ std::stringstream read;
+ read << inputCertFileStream.rdbuf();
+ inputCertFileStream.close();
+ EXPECT_EQ(read.str(), buffer);
+ });
+ };
+
+ sdbusplus::bus::bus bus;
+ fs::path authoritiesListFolder;
+ fs::path sourceAuthoritiesListFile;
+};
+
+// Tests that the Authority Manager installs all the certificates in an
+// authorities list
+TEST_F(AuthoritiesListTest, InstallAll)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
+ ASSERT_TRUE(manager.getCertificates().empty());
+
+ std::vector<sdbusplus::message::object_path> objects =
+ manager.installAll(sourceAuthoritiesListFile);
+ for (size_t i = 0; i < manager.getCertificates().size(); ++i)
+ {
+ EXPECT_EQ(manager.getCertificates()[i]->getObjectPath(), objects[i]);
+ }
+ verifyCertificates(manager.getCertificates());
+}
+
+// Tests that the Authority Manager recovers from the authorities list persisted
+// in the installation path at boot up
+TEST_F(AuthoritiesListTest, RecoverAtBootUp)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+
+ // Copy the trust bundle into the installation path before creating an
+ // Authority Manager
+ fs::copy_file(/*from=*/sourceAuthoritiesListFile,
+ authoritiesListFolder / defaultAuthoritiesListFileName);
+ // Create some noise as well
+ fs::copy_file(/*from=*/sourceAuthoritiesListFile,
+ authoritiesListFolder / "should_be_deleted");
+
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+
+ ASSERT_EQ(manager.getCertificates().size(), maxNumAuthorityCertificates);
+
+ // Check attributes and alias
+ std::unordered_set<std::string> expectedFiles = {authoritiesListFolder /
+ "trust_bundle"};
+ std::vector<std::unique_ptr<Certificate>>& certs =
+ manager.getCertificates();
+ for (size_t i = 0; i < certs.size(); ++i)
+ {
+ std::string name = "root_" + std::to_string(i);
+ EXPECT_EQ(certs[i]->subject(), "O=openbmc-project.xyz,CN=" + name);
+ EXPECT_EQ(certs[i]->issuer(), "O=openbmc-project.xyz,CN=" + name);
+ std::string symbolLink =
+ authoritiesListFolder / (certs[i]->getCertId().substr(0, 8) + ".0");
+ expectedFiles.insert(symbolLink);
+ expectedFiles.insert(certs[i]->getCertFilePath());
+ ASSERT_TRUE(fs::exists(symbolLink));
+ compareFileAgainstString(symbolLink, certs[i]->certificateString());
+ }
+
+ // Check folder content
+ for (auto& path : fs::directory_iterator(authoritiesListFolder))
+ {
+ EXPECT_NE(path, authoritiesListFolder / "should_be_deleted");
+ expectedFiles.erase(path.path());
+ }
+ EXPECT_TRUE(expectedFiles.empty());
+}
+
+TEST_F(AuthoritiesListTest, InstallAndDelete)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return())
+ .WillOnce(Return());
+ ASSERT_TRUE(manager.getCertificates().empty());
+ ASSERT_EQ(manager.installAll(sourceAuthoritiesListFile).size(),
+ maxNumAuthorityCertificates);
+ manager.deleteAll();
+ EXPECT_TRUE(manager.getCertificates().empty());
+ // Check folder content
+ for (const fs::path& f : fs::directory_iterator(authoritiesListFolder))
+ {
+ EXPECT_THAT(f.filename(), testing::AnyOf(".", ".."));
+ }
+}
+
+TEST_F(AuthoritiesListTest, InstallAllWrongManagerType)
+{
+ std::string endpoint("ldap");
+ CertificateType type = CertificateType::Server;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest serverManager(bus, event, object.c_str(), type, "",
+ authoritiesListFolder);
+ EXPECT_THROW(serverManager.installAll(sourceAuthoritiesListFile),
+ sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
+
+ type = CertificateType::Client;
+ object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(type) + '/' + endpoint;
+ ManagerInTest clientManager(bus, event, object.c_str(), type, "",
+ authoritiesListFolder);
+ EXPECT_THROW(clientManager.installAll(sourceAuthoritiesListFile),
+ sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
+}
+
+TEST_F(AuthoritiesListTest, InstallAllTwice)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return());
+ ASSERT_TRUE(manager.getCertificates().empty());
+
+ ASSERT_EQ(manager.installAll(sourceAuthoritiesListFile).size(),
+ maxNumAuthorityCertificates);
+ EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile).size(),
+ sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
+}
+
+TEST_F(AuthoritiesListTest, InstallAllMissSourceFile)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+
+ EXPECT_THROW(manager.installAll(authoritiesListFolder / "trust_bundle"),
+ InternalFailure);
+}
+
+TEST_F(AuthoritiesListTest, TooManyRootCertificates)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+ createAuthoritiesList(maxNumAuthorityCertificates + 1);
+ EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
+ sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed);
+}
+
+TEST_F(AuthoritiesListTest, CertInWrongFormat)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+
+ // Replace the authorities list with non-valid PEM encoded x509 certificate
+ setContentFromString(sourceAuthoritiesListFile, "blah-blah");
+ EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
+ InvalidCertificate);
+ setContentFromString(sourceAuthoritiesListFile,
+ "-----BEGIN CERTIFICATE-----");
+ EXPECT_THROW(manager.installAll(sourceAuthoritiesListFile),
+ InvalidCertificate);
+}
+
+TEST_F(AuthoritiesListTest, ReplaceAll)
+{
+ std::string endpoint("ldap");
+ std::string verifyUnit(ManagerInTest::unitToRestartInTest);
+ CertificateType type = CertificateType::Authority;
+
+ std::string object = std::string(objectNamePrefix) + '/' +
+ certificateTypeToString(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);
+ ManagerInTest manager(bus, event, object.c_str(), type, verifyUnit,
+ authoritiesListFolder);
+ EXPECT_CALL(manager, reloadOrReset(Eq(ManagerInTest::unitToRestartInTest)))
+ .WillOnce(Return())
+ .WillOnce(Return());
+ manager.installAll(sourceAuthoritiesListFile);
+
+ // Replace the current list with a different list
+ fs::remove_all(sourceAuthoritiesListFile.parent_path());
+ createAuthoritiesList(maxNumAuthorityCertificates);
+ std::vector<sdbusplus::message::object_path> objects =
+ manager.replaceAll(sourceAuthoritiesListFile);
+
+ for (size_t i = 0; i < manager.getCertificates().size(); ++i)
+ {
+ EXPECT_EQ(manager.getCertificates()[i]->getObjectPath(), objects[i]);
+ }
+ verifyCertificates(manager.getCertificates());
+}
+
} // namespace
} // namespace phosphor::certs
diff --git a/x509_utils.cpp b/x509_utils.cpp
index 9a589dd..ba1a2ef 100644
--- a/x509_utils.cpp
+++ b/x509_utils.cpp
@@ -225,4 +225,31 @@
return {idBuff};
}
+
+std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem)
+{
+ if (pem.size() > INT_MAX)
+ {
+ log<level::ERR>("Error occurred during parseCert: PEM is too long");
+ elog<InvalidCertificate>(Reason("Invalid PEM: too long"));
+ }
+ X509Ptr cert(X509_new(), ::X509_free);
+ if (!cert)
+ {
+ log<level::ERR>("Error occurred during X509_new call",
+ entry("ERRCODE=%lu", ERR_get_error()));
+ elog<InternalFailure>();
+ }
+
+ BIOMemPtr bioCert(BIO_new_mem_buf(pem.data(), static_cast<int>(pem.size())),
+ ::BIO_free);
+ X509* x509 = cert.get();
+ if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
+ {
+ log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
+ entry("PEM=%s", pem.c_str()));
+ elog<InternalFailure>();
+ }
+ return cert;
+}
} // namespace phosphor::certs
diff --git a/x509_utils.hpp b/x509_utils.hpp
index e439aad..6e193fc 100644
--- a/x509_utils.hpp
+++ b/x509_utils.hpp
@@ -58,4 +58,9 @@
*/
std::string generateCertId(X509& cert);
+/** @brief Parses PEM string into the X509 structure.
+ * @param[in] pem - PEM encoded X509 certificate buffer.
+ * @return pointer to the X509 structure.
+ */
+std::unique_ptr<X509, decltype(&::X509_free)> parseCert(const std::string& pem);
} // namespace phosphor::certs