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