Implementation of BMC VMI certificate manager

This manager is to create and manage entries
for each host CSR request which needs to shared
with host.

this commits implements dbus interfaces
https://gerrit.openbmc-project.xyz/c/openbmc/phosphor-dbus-interfaces/+/31808

This feature can be enabled by using below feature flag
"--enable-ca-cert-extension"

Testby:
Creating CSR entries
Deleting  entries
Setting properties

Signed-off-by: Ravi Teja <raviteja28031990@gmail.com>
Change-Id: I24829b839feac6264f32053b9be63daef6599379
diff --git a/Makefile.am b/Makefile.am
index f179262..b4bca0f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -51,6 +51,10 @@
 
 SUBDIRS = test
 
+if CA_CERT_EXTENSION
+SUBDIRS += bmc-vmi-ca
+endif
+
 check_PROGRAMS =
 XFAIL_TESTS =
 
diff --git a/bmc-vmi-ca/Makefile.am b/bmc-vmi-ca/Makefile.am
new file mode 100644
index 0000000..c33f499
--- /dev/null
+++ b/bmc-vmi-ca/Makefile.am
@@ -0,0 +1,29 @@
+bin_PROGRAMS = \
+	bmc-vmi-ca
+
+noinst_HEADERS = \
+	ca_cert_entry.hpp \
+	ca_certs_manager.hpp
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = \
+	bmc-vmi-ca-manager.service
+endif
+
+bmc_vmi_ca_SOURCES = \
+	mainapp.cpp \
+	ca_cert_entry.cpp \
+	ca_certs_manager.cpp
+
+bmc_vmi_ca_LDFLAGS = \
+	$(SDBUSPLUS_LIBS) \
+	$(SDEVENTPLUS_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+	$(PHOSPHOR_LOGGING_LIBS) \
+	-lstdc++fs
+
+bmc_vmi_ca_CXXFLAGS = \
+	$(SYSTEMD_CFLAGS) \
+	$(SDEVENTPLUS_LIBS) \
+	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+	$(PHOSPHOR_LOGGING_CFLAGS)
diff --git a/bmc-vmi-ca/bmc-vmi-ca-manager.service b/bmc-vmi-ca/bmc-vmi-ca-manager.service
new file mode 100644
index 0000000..52bba44
--- /dev/null
+++ b/bmc-vmi-ca/bmc-vmi-ca-manager.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=BMC VMI CA authority manager
+
+[Service]
+ExecStart=/usr/bin/env bmc-vmi-ca
+SyslogIdentifier=bmc-vmi-ca
+Restart=always
+
+Type=dbus
+BusName=xyz.openbmc_project.Certs.ca.authority.Manager
+
+[Install]
+WantedBy=multi-user.target
diff --git a/bmc-vmi-ca/ca_cert_entry.cpp b/bmc-vmi-ca/ca_cert_entry.cpp
new file mode 100644
index 0000000..94c0a25
--- /dev/null
+++ b/bmc-vmi-ca/ca_cert_entry.cpp
@@ -0,0 +1,18 @@
+#include "config.h"

+

+#include "ca_cert_entry.hpp"

+

+#include "ca_certs_manager.hpp"

+

+namespace ca

+{

+namespace cert

+{

+

+void Entry::delete_()

+{

+    // Remove entry D-bus object

+    manager.erase(id);

+}

+} // namespace cert

+} // namespace ca

diff --git a/bmc-vmi-ca/ca_cert_entry.hpp b/bmc-vmi-ca/ca_cert_entry.hpp
new file mode 100644
index 0000000..245a5cc
--- /dev/null
+++ b/bmc-vmi-ca/ca_cert_entry.hpp
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "xyz/openbmc_project/Certs/Entry/server.hpp"
+#include "xyz/openbmc_project/Object/Delete/server.hpp"
+#include "xyz/openbmc_project/PLDM/Provider/Certs/Authority/CSR/server.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+
+namespace ca
+{
+namespace cert
+{
+
+using Delete = sdbusplus::xyz::openbmc_project::Object::server::Delete;
+
+using CertEntry = sdbusplus::xyz::openbmc_project::Certs::server::Entry;
+using CSREntry = sdbusplus::xyz::openbmc_project::PLDM::Provider::Certs::
+    Authority::server::CSR;
+using Ifaces = sdbusplus::server::object::object<CSREntry, CertEntry, Delete>;
+
+class CACertMgr;
+
+/** @class Entry
+ *  @brief CA authority certificate Entry implementation.
+ *  @details A concrete implementation for the
+ *           xyz.openbmc_project.Certs.Entry DBus API
+ */
+class Entry : public Ifaces
+{
+  public:
+    Entry() = delete;
+    Entry(const Entry&) = delete;
+    Entry& operator=(const Entry&) = delete;
+    Entry(Entry&&) = delete;
+    Entry& operator=(Entry&&) = delete;
+    ~Entry() = default;
+
+    /** @brief Constructor to put object onto bus at a D-Bus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] objPath - The D-Bus object path to attach at.
+     *  @param[in] entryId - Entry id
+     *  @param[in] csr     - csr string
+     *  @param[in] cert    - client certificate
+     */
+    Entry(sdbusplus::bus::bus& bus, const std::string& objPath,
+          uint32_t entryId, std::string& csr, std::string& cert,
+          CACertMgr& manager) :
+        Ifaces(bus, objPath.c_str(), true),
+        bus(bus), id(entryId), manager(manager)
+
+    {
+        cSR(csr);
+        clientCertificate(cert);
+
+        // Emit deferred signal.
+        this->emit_object_added();
+    };
+
+    void delete_() override;
+
+  protected:
+    /** @brief sdbusplus handler */
+    sdbusplus::bus::bus& bus;
+    uint32_t id;
+    /** @brief object path */
+    std::string objectPath;
+    /** @brief Reference to Certificate Manager */
+    CACertMgr& manager;
+};
+} // namespace cert
+} // namespace ca
diff --git a/bmc-vmi-ca/ca_certs_manager.cpp b/bmc-vmi-ca/ca_certs_manager.cpp
new file mode 100644
index 0000000..f6622ff
--- /dev/null
+++ b/bmc-vmi-ca/ca_certs_manager.cpp
@@ -0,0 +1,71 @@
+#include "config.h"
+
+#include "ca_certs_manager.hpp"
+
+#include <filesystem>
+#include <fstream>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace ca
+{
+namespace cert
+{
+static constexpr auto objectEntry = "/xyz/openbmc_project/certs/entry";
+static constexpr auto maxCertSize = 4096;
+namespace fs = std::filesystem;
+using namespace phosphor::logging;
+using InvalidArgument =
+    sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+sdbusplus::message::object_path CACertMgr::signCSR(std::string csr)
+{
+    std::string objPath;
+    try
+    {
+        if (csr.size() > maxCertSize)
+        {
+            log<level::ERR>("Invalid CSR size");
+            elog<InvalidArgument>(Argument::ARGUMENT_NAME("CSR"),
+                                  Argument::ARGUMENT_VALUE(csr.c_str()));
+        }
+        auto id = lastEntryId + 1;
+        objPath = fs::path(objectEntry) / std::to_string(id);
+        std::string cert;
+        // Creating the dbus object here with the empty certificate string
+        // actual signing is being done by the hypervisor, once it signs then
+        // the certificate string would be updated with actual certificate.
+        entries.insert(std::make_pair(
+            id, std::make_unique<Entry>(bus, objPath, id, csr, cert, *this)));
+        lastEntryId++;
+    }
+    catch (const std::invalid_argument& e)
+    {
+        log<level::ERR>(e.what());
+        elog<InvalidArgument>(Argument::ARGUMENT_NAME("csr"),
+                              Argument::ARGUMENT_VALUE(csr.c_str()));
+    }
+    return objPath;
+}
+
+void CACertMgr::erase(uint32_t entryId)
+{
+    entries.erase(entryId);
+}
+
+void CACertMgr::deleteAll()
+{
+    auto iter = entries.begin();
+    while (iter != entries.end())
+    {
+        auto& entry = iter->second;
+        ++iter;
+        entry->delete_();
+    }
+}
+
+} // namespace cert
+} // namespace ca
diff --git a/bmc-vmi-ca/ca_certs_manager.hpp b/bmc-vmi-ca/ca_certs_manager.hpp
new file mode 100644
index 0000000..9d6231e
--- /dev/null
+++ b/bmc-vmi-ca/ca_certs_manager.hpp
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "ca_cert_entry.hpp"
+#include "xyz/openbmc_project/Certs/Authority/server.hpp"
+#include "xyz/openbmc_project/Collection/DeleteAll/server.hpp"
+
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/server/object.hpp>
+#include <sdeventplus/source/event.hpp>
+
+namespace ca
+{
+namespace cert
+{
+
+class CACertMgr;
+
+using CreateIface = sdbusplus::server::object::object<
+    sdbusplus::xyz::openbmc_project::Certs::server::Authority,
+    sdbusplus::xyz::openbmc_project::Collection::server::DeleteAll>;
+using Mgr = ca::cert::CACertMgr;
+
+/** @class Manager
+ *  @brief Implementation for the
+ *         xyz.openbmc_project.Certs.ca.authority.Manager DBus API.
+ */
+class CACertMgr : public CreateIface
+{
+  public:
+    CACertMgr() = delete;
+    CACertMgr(const CACertMgr&) = delete;
+    CACertMgr& operator=(const CACertMgr&) = delete;
+    CACertMgr(CACertMgr&&) = delete;
+    CACertMgr& operator=(CACertMgr&&) = delete;
+    virtual ~CACertMgr() = default;
+
+    /** @brief Constructor to put object onto bus at a dbus path.
+     *  @param[in] bus - Bus to attach to.
+     *  @param[in] path - Path to attach at.
+     */
+    CACertMgr(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
+              const char* path) :
+        CreateIface(bus, path),
+        bus(bus), event(event), objectPath(path), lastEntryId(0){};
+
+    /** @brief This method provides signing authority functionality.
+               It signs the certificate and creates the CSR request entry Dbus
+     Object.
+     *  @param[in] csr - csr string
+     *  @return Object path
+     */
+    sdbusplus::message::object_path signCSR(std::string csr) override;
+
+    /** @brief Erase specified entry d-bus object
+     *  @param[in] entryId - unique identifier of the entry
+     */
+    void erase(uint32_t entryId);
+
+    /** @brief  Erase all entries
+     */
+    void deleteAll() override;
+
+  private:
+    /** @brief sdbusplus DBus bus connection. */
+    sdbusplus::bus::bus& bus;
+    // sdevent Event handle
+    sdeventplus::Event& event;
+
+    std::map<uint32_t, std::unique_ptr<Entry>> entries;
+    /** @brief object path */
+    std::string objectPath;
+    /** @brief Id of the last certificate entry */
+    uint32_t lastEntryId;
+};
+
+} // namespace cert
+} // namespace ca
diff --git a/bmc-vmi-ca/mainapp.cpp b/bmc-vmi-ca/mainapp.cpp
new file mode 100644
index 0000000..8c1aa06
--- /dev/null
+++ b/bmc-vmi-ca/mainapp.cpp
@@ -0,0 +1,28 @@
+#include "config.h"
+
+#include "ca_certs_manager.hpp"
+
+#include <sdeventplus/event.hpp>
+#include <string>
+
+int main(int argc, char** argv)
+{
+    auto bus = sdbusplus::bus::new_default();
+    std::string objPath = "/xyz/openbmc_project/certs/ca";
+
+    // Add sdbusplus ObjectManager
+    sdbusplus::server::manager::manager objManager(bus, objPath.c_str());
+
+    // Get default event loop
+    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);
+
+    ca::cert::CACertMgr manager(bus, event, objPath.c_str());
+
+    std::string busName = "xyz.openbmc_project.Certs.ca.authority.Manager";
+    bus.request_name(busName.c_str());
+    event.loop();
+    return 0;
+}
diff --git a/configure.ac b/configure.ac
index afbdc8f..29a3485 100644
--- a/configure.ac
+++ b/configure.ac
@@ -19,6 +19,27 @@
 AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory])
 AX_APPEND_COMPILE_FLAGS([-Wall -Werror], [CXXFLAGS])
 
+PKG_PROG_PKG_CONFIG
+AC_ARG_WITH([systemdsystemunitdir],
+     [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],
+     [],
+     [with_systemdsystemunitdir=auto]
+)
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"],
+    [def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+     AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+           [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+                  [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])]
+            )
+            with_systemdsystemunitdir=no],
+           [with_systemdsystemunitdir="$def_systemdsystemunitdir"]
+     )]
+)
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+      [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])]
+)
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
 # Check for libraries
 AX_CHECK_OPENSSL([], [AC_MSG_ERROR(["openssl required and not found"])])
 AC_CHECK_HEADER(experimental/filesystem, [],
@@ -164,6 +185,18 @@
 AS_IF([test "x$AUTHORITY_CERTIFICATES_LIMIT" == "x"], [AUTHORITY_CERTIFICATES_LIMIT=10])
 AC_DEFINE_UNQUOTED([AUTHORITY_CERTIFICATES_LIMIT], [$AUTHORITY_CERTIFICATES_LIMIT], [Authority certificates limit])
 
+AC_ARG_ENABLE([ca-cert-extension],
+              AS_HELP_STRING([--enable-ca-cert-extension],
+                   [enable CA certificate manager \
+                    Only IBM specific])
+)
+
+AM_CONDITIONAL([CA_CERT_EXTENSION], [test "x$enable_ca_cert_extension" == "xyes"])
+
+AS_IF([test "x$enable_ca_cert_extension" == "xyes"],
+      [AX_APPEND_COMPILE_FLAGS([-DCA_CERT_EXTENSION])],
+      [AC_CONFIG_FILES([bmc-vmi-ca/Makefile])])
+
 # Create configured output
 AC_CONFIG_FILES([Makefile test/Makefile])
 AC_OUTPUT