Manage certificates created by applications
Added watch on certificate path to watch on certificates
created/updated by apps.
As part of watch notification, create new D-Bus new certificate
and for existing D-Bus object update the properties.
Tested:
Test case 1
1) Ensure no certificate is present
2) Restart certificate service
3) Restart bmcweb service
4) Verified that certificate object is created for the
self-signed certificate created by bmcweb.
Test case 2
1) After a certificate is present
2) Modify the bmcweb certificate by replacing it
with a valid certificate manually.
3) Verified that certificate manager is notified
and certificate objects properties are updated.
Test case 3
1) Upload CSR based certificate file
2) Verified that private key is appended to the file
Test case 4
1) Create a dummy file in certificate folder
2) Verified that notification is received and file is ignored
Test case 5
1) Verified install, replace, generate csr.
Change-Id: I7d1e3624958e4b68e5ba7bc6150c19b11fca501a
Signed-off-by: Marri Devender Rao <devenrao@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 8808069..49983bc 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -5,7 +5,8 @@
certs_manager.hpp \
argument.hpp \
certificate.hpp \
- csr.hpp
+ csr.hpp \
+ watch.hpp
bin_PROGRAMS = \
phosphor-certificate-manager
@@ -15,7 +16,8 @@
certs_manager.cpp \
argument.cpp \
certificate.cpp \
- csr.cpp
+ csr.cpp \
+ watch.cpp
phosphor_certificate_manager_LDFLAGS = \
$(SDBUSPLUS_LIBS) \
diff --git a/certificate.cpp b/certificate.cpp
index 467f888..af8635e 100644
--- a/certificate.cpp
+++ b/certificate.cpp
@@ -72,10 +72,11 @@
const UnitsToRestart& unit,
const CertInstallPath& installPath,
const CertUploadPath& uploadPath,
- bool isSkipUnitReload) :
+ bool isSkipUnitReload,
+ const CertWatchPtr& certWatchPtr) :
CertIfaces(bus, objPath.c_str(), true),
bus(bus), objectPath(objPath), certType(type), unitToRestart(unit),
- certInstallPath(installPath)
+ certInstallPath(installPath), certWatchPtr(certWatchPtr)
{
auto installHelper = [this](const auto& filePath) {
if (!compareKeys(filePath))
@@ -115,6 +116,12 @@
entry("FILEPATH=%s", filePath.c_str()));
auto errCode = X509_V_OK;
+ // stop watch for user initiated certificate install
+ if (certWatchPtr)
+ {
+ certWatchPtr->stopWatch();
+ }
+
// Verify the certificate file
fs::path file(filePath);
if (!fs::exists(file))
@@ -239,25 +246,35 @@
}
iter->second(filePath);
- // Copy thecertificate to the installation path
- auto path = fs::path(certInstallPath).parent_path();
- try
+ // Copy the certificate to the installation path
+ // During bootup will be parsing existing file so no need to
+ // copy it.
+ if (filePath != certInstallPath)
{
- fs::create_directories(path);
- // During bootup will be parsing existing file so no need to
- // copy it.
- if (filePath != certInstallPath)
+ 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);
+ try
{
- fs::copy_file(filePath, certInstallPath,
- fs::copy_options::overwrite_existing);
+ inputCertFileStream.open(filePath);
+ outputCertFileStream.open(certInstallPath, std::ios::out);
+ outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
+ inputCertFileStream.close();
+ outputCertFileStream.close();
}
- }
- catch (fs::filesystem_error& e)
- {
- log<level::ERR>("Failed to copy certificate", entry("ERR=%s", e.what()),
- entry("SRC=%s", filePath.c_str()),
- entry("DST=%s", certInstallPath.c_str()));
- elog<InternalFailure>();
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Failed to copy certificate",
+ entry("ERR=%s", e.what()),
+ entry("SRC=%s", filePath.c_str()),
+ entry("DST=%s", certInstallPath.c_str()));
+ elog<InternalFailure>();
+ }
}
if (!isSkipUnitReload)
@@ -271,6 +288,12 @@
// Parse the certificate file and populate properties
populateProperties();
+
+ // restart watch
+ if (certWatchPtr)
+ {
+ certWatchPtr->startWatch();
+ }
}
void Certificate::populateProperties()
diff --git a/certificate.hpp b/certificate.hpp
index 46371bb..70ee522 100644
--- a/certificate.hpp
+++ b/certificate.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "watch.hpp"
+
#include <openssl/x509.h>
#include <filesystem>
@@ -23,7 +25,7 @@
using CertUploadPath = std::string;
using InputType = std::string;
using InstallFunc = std::function<void(const std::string&)>;
-
+using CertWatchPtr = std::unique_ptr<Watch>;
using namespace phosphor::logging;
// for placeholders
@@ -62,17 +64,24 @@
* @param[in] installPath - Path of the certificate to install
* @param[in] uploadPath - Path of the certificate file to upload
* @param[in] isSkipUnitReload - If true do not restart units
+ * @param[in] watchPtr - watch on self signed certificate pointer
*/
Certificate(sdbusplus::bus::bus& bus, const std::string& objPath,
const CertificateType& type, const UnitsToRestart& unit,
const CertInstallPath& installPath,
- const CertUploadPath& uploadPath, bool isSkipUnitReload);
+ const CertUploadPath& uploadPath, bool isSkipUnitReload,
+ const CertWatchPtr& watchPtr);
/** @brief Validate certificate and replace the existing certificate
* @param[in] filePath - Certificate file path.
*/
void replace(const std::string filePath) override;
+ /** @brief Populate certificate properties by parsing certificate file
+ * @return void
+ */
+ void populateProperties();
+
private:
/** @brief Validate and Replace/Install the certificate file
* Install/Replace the existing certificate file with another
@@ -88,11 +97,6 @@
*/
X509_Ptr loadCert(const std::string& filePath);
- /** @brief Populate certificate properties by parsing certificate file
- * @return void
- */
- void populateProperties();
-
/** @brief Public/Private key compare function.
* Comparing private key against certificate public key
* from input .pem file.
@@ -124,6 +128,9 @@
/** @brief Certificate file installation path **/
CertInstallPath certInstallPath;
+
+ /** @brief Certificate file create/update watch */
+ const CertWatchPtr& certWatchPtr;
};
} // namespace certs
diff --git a/certs_manager.cpp b/certs_manager.cpp
index 38f77c2..b189bb9 100644
--- a/certs_manager.cpp
+++ b/certs_manager.cpp
@@ -12,6 +12,9 @@
{
using InternalFailure =
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidCertificate =
+ sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
+using Reason = xyz::openbmc_project::Certs::InvalidCertificate::REASON;
using X509_REQ_Ptr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
using BIGNUM_Ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
@@ -21,33 +24,45 @@
UnitsToRestart&& unit, CertInstallPath&& installPath) :
Ifaces(bus, path),
bus(bus), event(event), objectPath(path), certType(type),
- unitToRestart(std::move(unit)), certInstallPath(std::move(installPath)),
- childPtr(nullptr)
+ unitToRestart(std::move(unit)), certInstallPath(std::move(installPath))
{
- using InvalidCertificate =
- sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
- using Reason = xyz::openbmc_project::Certs::InvalidCertificate::REASON;
+ // restore any existing certificates
if (fs::exists(certInstallPath))
{
- try
- {
- // TODO: Issue#3 At present supporting only one certificate to be
- // uploaded this need to be revisited to support multiple
- // certificates
- auto certObjectPath = objectPath + '/' + '1';
- certificatePtr = std::make_unique<Certificate>(
- bus, certObjectPath, certType, unitToRestart, certInstallPath,
- certInstallPath, true);
- }
- catch (const InternalFailure& e)
- {
- report<InternalFailure>();
- }
- catch (const InvalidCertificate& e)
- {
- report<InvalidCertificate>(
- Reason("Existing certificate file is corrupted"));
- }
+ createCertificate();
+ }
+
+ // watch is not required for authority certificates
+ if (certType != AUTHORITY)
+ {
+ // watch for certificate file create/replace
+ certWatchPtr = std::make_unique<
+ Watch>(event, certInstallPath, [this]() {
+ try
+ {
+ // if certificate file existing update it
+ if (certificatePtr != nullptr)
+ {
+ log<level::INFO>(
+ "Inotify callback to update certificate properties");
+ certificatePtr->populateProperties();
+ }
+ else
+ {
+ log<level::INFO>(
+ "Inotify callback to create certificate object");
+ createCertificate();
+ }
+ }
+ catch (const InternalFailure& e)
+ {
+ commit<InternalFailure>();
+ }
+ catch (const InvalidCertificate& e)
+ {
+ commit<InvalidCertificate>();
+ }
+ });
}
}
@@ -63,10 +78,11 @@
{
elog<NotAllowed>(Reason("Certificate already exist"));
}
+
auto certObjectPath = objectPath + '/' + '1';
certificatePtr = std::make_unique<Certificate>(
bus, certObjectPath, certType, unitToRestart, certInstallPath, filePath,
- false);
+ false, certWatchPtr);
}
void Manager::delete_()
@@ -170,6 +186,11 @@
return csrObjectPath;
}
+CertificatePtr& Manager::getCertificate()
+{
+ return certificatePtr;
+}
+
void Manager::generateCSRHelper(
std::vector<std::string> alternativeNames, std::string challengePassword,
std::string city, std::string commonName, std::string contactPerson,
@@ -460,5 +481,27 @@
std::fclose(fp);
}
+void Manager::createCertificate()
+{
+ try
+ {
+ // TODO: Issue#3 At present supporting only one certificate to be
+ // uploaded this need to be revisited to support multiple
+ // certificates
+ auto certObjectPath = objectPath + '/' + '1';
+ certificatePtr = std::make_unique<Certificate>(
+ bus, certObjectPath, certType, unitToRestart, certInstallPath,
+ certInstallPath, true, certWatchPtr);
+ }
+ catch (const InternalFailure& e)
+ {
+ report<InternalFailure>();
+ }
+ catch (const InvalidCertificate& e)
+ {
+ report<InvalidCertificate>(
+ Reason("Existing certificate file is corrupted"));
+ }
+}
} // namespace certs
} // namespace phosphor
diff --git a/certs_manager.hpp b/certs_manager.hpp
index 236f6f1..1e6ca81 100644
--- a/certs_manager.hpp
+++ b/certs_manager.hpp
@@ -3,6 +3,7 @@
#include "certificate.hpp"
#include "csr.hpp"
+#include "watch.hpp"
#include <sdeventplus/source/child.hpp>
#include <sdeventplus/source/event.hpp>
@@ -21,6 +22,7 @@
using X509_REQ_Ptr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
+using CertificatePtr = std::unique_ptr<Certificate>;
class Manager : public Ifaces
{
@@ -149,6 +151,12 @@
std::string organizationalUnit, std::string state, std::string surname,
std::string unstructuredName) override;
+ /** @brief Get reference to certificate
+ *
+ * @return Reference to certificate
+ */
+ CertificatePtr& getCertificate();
+
private:
void generateCSRHelper(std::vector<std::string> alternativeNames,
std::string challengePassword, std::string city,
@@ -200,6 +208,11 @@
*/
void writeCSR(const std::string& filePath, const X509_REQ_Ptr& x509Req);
+ /** @brief Load certifiate
+ * Load certificate and create certificate object
+ */
+ void createCertificate();
+
/** @brief sdbusplus handler */
sdbusplus::bus::bus& bus;
@@ -219,13 +232,16 @@
CertInstallPath certInstallPath;
/** @brief pointer to certificate */
- std::unique_ptr<Certificate> certificatePtr = nullptr;
+ CertificatePtr certificatePtr = nullptr;
/** @brief pointer to CSR */
std::unique_ptr<CSR> csrPtr = nullptr;
/** @brief SDEventPlus child pointer added to event loop */
- std::unique_ptr<sdeventplus::source::Child> childPtr;
+ std::unique_ptr<sdeventplus::source::Child> childPtr = nullptr;
+
+ /** @brief Watch on self signed certificates */
+ std::unique_ptr<Watch> certWatchPtr = nullptr;
};
} // namespace certs
} // namespace phosphor
diff --git a/test/Makefile.am b/test/Makefile.am
index e498a16..ff20da0 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -32,3 +32,4 @@
certs_manager_test_LDADD = $(top_builddir)/certs_manager.o
certs_manager_test_LDADD += $(top_builddir)/certificate.o
certs_manager_test_LDADD += $(top_builddir)/csr.o
+certs_manager_test_LDADD += $(top_builddir)/watch.o
diff --git a/test/certs_manager_test.cpp b/test/certs_manager_test.cpp
index a783ba3..d596234 100644
--- a/test/certs_manager_test.cpp
+++ b/test/certs_manager_test.cpp
@@ -145,8 +145,13 @@
std::string verifyPath(installPath);
UnitsToRestart verifyUnit(unit);
auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
@@ -161,8 +166,13 @@
std::string verifyPath(installPath);
UnitsToRestart verifyUnit(unit);
auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
@@ -177,8 +187,13 @@
std::string verifyPath(installPath);
UnitsToRestart verifyUnit(unit);
auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
}
@@ -193,8 +208,13 @@
std::string verifyPath(installPath);
UnitsToRestart verifyUnit(unit);
auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
EXPECT_TRUE(fs::exists(verifyPath));
EXPECT_TRUE(compareFiles(verifyPath, certificateFile));
}
@@ -215,8 +235,13 @@
{
try
{
- Certificate certificate(bus, objPath, type, unit, installPath,
- uploadFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(uploadFile);
}
catch (const InternalFailure& e)
{
@@ -227,6 +252,31 @@
EXPECT_FALSE(fs::exists(verifyPath));
}
+/** @brief Test replacing existing certificate
+ */
+TEST_F(TestCertificates, TestReplaceCertificate)
+{
+ std::string endpoint("ldap");
+ std::string unit("");
+ std::string type("server");
+ std::string installPath(certDir + "/" + certificateFile);
+ std::string verifyPath(installPath);
+ 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(installPath));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
+ EXPECT_TRUE(fs::exists(verifyPath));
+ EXPECT_TRUE(fs::exists(verifyPath));
+ CertificatePtr& ptr = manager.getCertificate();
+ EXPECT_NE(ptr, nullptr);
+ ptr->replace(certificateFile);
+ EXPECT_TRUE(fs::exists(verifyPath));
+}
+
/** @brief Check if install fails if certificate file is empty
*/
TEST_F(TestCertificates, TestEmptyCertificateFile)
@@ -246,8 +296,13 @@
{
try
{
- Certificate certificate(bus, objPath, type, unit, installPath,
- emptyFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(emptyFile);
}
catch (const InvalidCertificate& e)
{
@@ -282,8 +337,13 @@
{
try
{
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
}
catch (const InvalidCertificate& e)
{
@@ -294,46 +354,6 @@
EXPECT_FALSE(fs::exists(verifyPath));
}
-/** @brief check certificate delete at manager level
- */
-TEST_F(TestCertificates, TestCertManagerDelete)
-{
- std::string endpoint("ldap");
- std::string unit("");
- std::string type("client");
- std::string installPath(certDir + "/" + certificateFile);
- std::string verifyPath(installPath);
- std::string verifyUnit(unit);
- // Get default event loop
- auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- auto event = sdeventplus::Event::get_default();
- Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
- std::move(installPath));
- MainApp mainApp(&manager);
- // delete certificate file and verify file is deleted
- mainApp.delete_();
- EXPECT_FALSE(fs::exists(verifyPath));
-}
-
-/** @brief check certificate install at manager level
- */
-TEST_F(TestCertificates, TestCertManagerInstall)
-{
- std::string endpoint("ldap");
- std::string unit("");
- std::string type("client");
- std::string installPath(certDir + "/" + certificateFile);
- std::string verifyPath(installPath);
- std::string verifyUnit(unit);
- auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- auto event = sdeventplus::Event::get_default();
- Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
- std::move(installPath));
- MainApp mainApp(&manager);
- mainApp.install(certificateFile);
- EXPECT_TRUE(fs::exists(verifyPath));
-}
-
/**
* Class to generate private and certificate only file and test verification
*/
@@ -395,8 +415,13 @@
{
try
{
- Certificate certificate(bus, objPath, type, unit, installPath,
- certificateFile, false);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(certificateFile);
}
catch (const InvalidCertificate& e)
{
@@ -417,44 +442,18 @@
std::string installPath(certDir + "/" + keyFile);
std::string verifyPath(installPath);
std::string verifyUnit(unit);
-
auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
EXPECT_THROW(
{
try
{
- Certificate certificate(bus, objPath, type, unit, installPath,
- keyFile, false);
- }
- catch (const InvalidCertificate& e)
- {
- throw;
- }
- },
- InvalidCertificate);
- EXPECT_FALSE(fs::exists(verifyPath));
-}
-
-/** @brief Check if Manager install method fails for invalid certificate file
- */
-TEST_F(TestInvalidCertificate, TestCertManagerInstall)
-{
- std::string endpoint("ldap");
- std::string unit("");
- std::string type("client");
- std::string installPath(certDir + "/" + certificateFile);
- std::string verifyPath(installPath);
- std::string verifyUnit(unit);
- auto objPath = std::string(OBJPATH) + '/' + type + '/' + endpoint;
- auto event = sdeventplus::Event::get_default();
- Manager manager(bus, event, objPath.c_str(), type, std::move(unit),
- std::move(installPath));
- MainApp mainApp(&manager);
- EXPECT_THROW(
- {
- try
- {
- mainApp.install(certificateFile);
+ 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));
+ MainApp mainApp(&manager);
+ mainApp.install(keyFile);
}
catch (const InvalidCertificate& e)
{
@@ -479,6 +478,8 @@
std::string verifyPath(installPath);
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(installPath));
MainApp mainApp(&manager);
@@ -528,6 +529,8 @@
std::string unstructuredName("unstructuredName");
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(installPath));
Status status;
diff --git a/watch.cpp b/watch.cpp
new file mode 100644
index 0000000..c1ac789
--- /dev/null
+++ b/watch.cpp
@@ -0,0 +1,112 @@
+#include "watch.hpp"
+
+#include <sys/inotify.h>
+#include <unistd.h>
+
+#include <cstring>
+#include <filesystem>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+namespace phosphor
+{
+namespace certs
+{
+using namespace phosphor::logging;
+namespace fs = std::filesystem;
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+Watch::Watch(sdeventplus::Event& event, std::string& certFile, Callback cb) :
+ event(event), callback(cb)
+{
+ // get parent directory of certificate file to watch
+ fs::path path = std::move(fs::path(certFile).parent_path());
+ try
+ {
+ if (!fs::exists(path))
+ {
+ fs::create_directories(path);
+ }
+ }
+ catch (fs::filesystem_error& e)
+ {
+ log<level::ERR>("Failed to create directory", entry("ERR=%s", e.what()),
+ entry("DIRECTORY=%s", path.c_str()));
+ elog<InternalFailure>();
+ }
+ watchDir = path;
+ watchFile = fs::path(certFile).filename();
+ startWatch();
+}
+
+Watch::~Watch()
+{
+ stopWatch();
+}
+
+void Watch::startWatch()
+{
+ // stop any existing watch
+ stopWatch();
+
+ fd = inotify_init1(IN_NONBLOCK);
+ if (-1 == fd)
+ {
+ log<level::ERR>("inotify_init1 failed,",
+ entry("ERR=%s", std::strerror(errno)));
+ elog<InternalFailure>();
+ }
+ wd = inotify_add_watch(fd, watchDir.c_str(), IN_CLOSE_WRITE);
+ if (-1 == wd)
+ {
+ close(fd);
+ log<level::ERR>("inotify_add_watch failed,",
+ entry("ERR=%s", std::strerror(errno)),
+ entry("WATCH=%s", watchDir.c_str()));
+ elog<InternalFailure>();
+ }
+
+ ioPtr = std::make_unique<sdeventplus::source::IO>(
+ event, fd, EPOLLIN,
+ [this](sdeventplus::source::IO&, int fd, uint32_t revents) {
+ const int size = sizeof(struct inotify_event) + NAME_MAX + 1;
+ std::array<char, size> buffer;
+ int length = read(fd, buffer.data(), buffer.size());
+ if (length >= static_cast<int>(sizeof(struct inotify_event)))
+ {
+ struct inotify_event* notifyEvent =
+ reinterpret_cast<struct inotify_event*>(&buffer[0]);
+ if (notifyEvent->len)
+ {
+ if (watchFile == notifyEvent->name)
+ {
+ callback();
+ }
+ }
+ }
+ else
+ {
+ log<level::ERR>("Failed to read inotify event");
+ }
+ });
+}
+
+void Watch::stopWatch()
+{
+ if (-1 != fd)
+ {
+ if (-1 != wd)
+ {
+ inotify_rm_watch(fd, wd);
+ }
+ close(fd);
+ }
+ if (ioPtr)
+ {
+ ioPtr.reset();
+ }
+}
+
+} // namespace certs
+} // namespace phosphor
diff --git a/watch.hpp b/watch.hpp
new file mode 100644
index 0000000..bf16742
--- /dev/null
+++ b/watch.hpp
@@ -0,0 +1,72 @@
+#pragma once
+#include "watch.hpp"
+
+#include <memory>
+#include <sdeventplus/source/event.hpp>
+#include <sdeventplus/source/io.hpp>
+namespace phosphor
+{
+namespace certs
+{
+/** @class Watch
+ *
+ * @brief Adds inotify watch on certificate directory
+ *
+ * The inotify watch is hooked up with sd-event, so that on call back,
+ * appropriate actions related to a certificate upload can be taken.
+ */
+class Watch
+{
+ public:
+ using Callback = std::function<void()>;
+ /** @brief ctor - hook inotify watch with sd-event
+ *
+ * @param[in] loop - sd-event object
+ * @param[in] cb - The callback function for processing
+ * certificate upload
+ */
+ Watch(sdeventplus::Event& event, std::string& certFile, Callback cb);
+ Watch(const Watch&) = delete;
+ Watch& operator=(const Watch&) = delete;
+ Watch(Watch&&) = delete;
+ Watch& operator=(Watch&&) = delete;
+
+ /** @brief dtor - remove inotify watch and close fd's
+ */
+ ~Watch();
+
+ /** @brief start watch on the specified path
+ */
+ void startWatch();
+
+ /** @brief stop watch on the specified path
+ */
+ void stopWatch();
+
+ private:
+ /** @brief certificate upload directory watch descriptor */
+ int wd = -1;
+
+ /** @brief inotify file descriptor */
+ int fd = -1;
+
+ /** @brief SDEventPlus IO pointer added to event loop */
+ std::unique_ptr<sdeventplus::source::IO> ioPtr = nullptr;
+
+ /** @brief sd-event object */
+ sdeventplus::Event& event;
+
+ /** @brief callback method to be called */
+ Callback callback;
+
+ /** @brief Certificate directory to watch */
+ std::string watchDir;
+
+ /** @brief Certificate file to watch */
+ std::string watchFile;
+
+ /** @brief Certificate file with path */
+ std::string certFile;
+};
+} // namespace certs
+} // namespace phosphor