Add Generate Key and Certificate Signing Request (CSR)

Generates Private key and CSR file, at present supporing
only RSA algorithm type.

-The generateCSR method defined in Create interface is implemented
by manager class to Create CSR and PrivateKey files.

-The cSR method defined in View interface is implemented by CSR
class to view CSR file.

- Generate CSR is time consuming operation and it might time-out
the D-Bus call. Forking process and performing CSR generation in
the child process, adding the process ID of the child process to the
SD Event loop so that callback is received when the chid process
is done with the CSR generation.

- As the GenerateCSR method returns immediately, caller need
to wait on InterfacesAdded signal that is generated after completion
of the CSR request. The caller then invokes cSR method of
CSR interface to read the CSR.

- For any failure in Generate CSR CSR object is created with error
status.

- CSR object raises exception if error is set else CSR data is
returned to the caller.

- To cater for failure cases caller need to start a timer, which
will be terminated after getting InterfaceAdded signal or upon timeout.

-Added Unit tests.
Tested:
1) Added unit tests to verify CSR generation
2) Tested with Redfish to generate and view CSR
curl -c cjar -b cjar -k -H "X-Auth-Token: $bmc_token" -X POST
https://${bmc}/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/
-d @generate.jon

{
  "CSRString": "-----BEGIN CERTIFICATE REQUEST---7E=\n-----END CERTIFICATE
REQUEST-----\n",
  "CertificateCollection": {
    "@odata.id": "/redfish/v1/AccountService/LDAP/Certificates/"
  }
}
Change-Id: I1e3ae8df45f87bfd8903f552d93c4df1af7c569f
Signed-off-by: Marri Devender Rao <devenrao@in.ibm.com>
Signed-off-by: Nagaraju Goruganti <ngorugan@in.ibm.com>
diff --git a/certs_manager.cpp b/certs_manager.cpp
index 2a90589..3f7a17e 100644
--- a/certs_manager.cpp
+++ b/certs_manager.cpp
@@ -1,5 +1,8 @@
 #include "certs_manager.hpp"
 
+#include <openssl/pem.h>
+#include <unistd.h>
+
 #include <phosphor-logging/elog-errors.hpp>
 #include <xyz/openbmc_project/Certs/error.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
@@ -7,23 +10,19 @@
 {
 namespace certs
 {
-
 using InternalFailure =
     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 
-/** @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.
- *  @param[in] type - Type of the certificate.
- *  @param[in] unit - Unit consumed by this certificate.
- *  @param[in] installPath - Certificate installation path.
- */
-Manager::Manager(sdbusplus::bus::bus& bus, const char* path,
-                 const CertificateType& type, UnitsToRestart&& unit,
-                 CertInstallPath&& installPath) :
+using X509_REQ_Ptr = std::unique_ptr<X509_REQ, decltype(&::X509_REQ_free)>;
+using BIGNUM_Ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
+
+Manager::Manager(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
+                 const char* path, const CertificateType& type,
+                 UnitsToRestart&& unit, CertInstallPath&& installPath) :
     Ifaces(bus, path),
-    bus(bus), objectPath(path), certType(type), unitToRestart(std::move(unit)),
-    certInstallPath(std::move(installPath))
+    bus(bus), event(event), objectPath(path), certType(type),
+    unitToRestart(std::move(unit)), certInstallPath(std::move(installPath)),
+    childPtr(nullptr)
 {
     using InvalidCertificate =
         sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
@@ -82,5 +81,286 @@
         certificatePtr.reset(nullptr);
     }
 }
+
+std::string Manager::generateCSR(
+    std::vector<std::string> alternativeNames, std::string challengePassword,
+    std::string city, std::string commonName, std::string contactPerson,
+    std::string country, std::string email, std::string givenName,
+    std::string initials, int64_t keyBitLength, std::string keyCurveId,
+    std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
+    std::string organization, std::string organizationalUnit, std::string state,
+    std::string surname, std::string unstructuredName)
+{
+    // We support only one CSR.
+    csrPtr.reset(nullptr);
+    auto pid = fork();
+    if (pid == -1)
+    {
+        log<level::ERR>("Error occurred during forking process");
+        report<InternalFailure>();
+    }
+    else if (pid == 0)
+    {
+        try
+        {
+            generateCSRHelper(alternativeNames, challengePassword, city,
+                              commonName, contactPerson, country, email,
+                              givenName, initials, keyBitLength, keyCurveId,
+                              keyPairAlgorithm, keyUsage, organization,
+                              organizationalUnit, state, surname,
+                              unstructuredName);
+            exit(EXIT_SUCCESS);
+        }
+        catch (const InternalFailure& e)
+        {
+            // commit the error reported in child process and exit
+            // Callback method from SDEvent Loop looks for exit status
+            exit(EXIT_FAILURE);
+            commit<InternalFailure>();
+        }
+    }
+    else
+    {
+        using namespace sdeventplus::source;
+        Child::Callback callback = [this](Child& eventSource,
+                                          const siginfo_t* si) {
+            eventSource.set_enabled(Enabled::On);
+            if (si->si_status != 0)
+            {
+                this->createCSRObject(Status::FAILURE);
+            }
+            else
+            {
+                this->createCSRObject(Status::SUCCESS);
+            }
+        };
+        try
+        {
+            sigset_t ss;
+            if (sigemptyset(&ss) < 0)
+            {
+                log<level::ERR>("Unable to initialize signal set");
+                elog<InternalFailure>();
+            }
+            if (sigaddset(&ss, SIGCHLD) < 0)
+            {
+                log<level::ERR>("Unable to add signal to signal set");
+                elog<InternalFailure>();
+            }
+
+            // Block SIGCHLD first, so that the event loop can handle it
+            if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0)
+            {
+                log<level::ERR>("Unable to block signal");
+                elog<InternalFailure>();
+            }
+            if (childPtr)
+            {
+                childPtr.reset();
+            }
+            childPtr = std::make_unique<Child>(event, pid, WEXITED | WSTOPPED,
+                                               std::move(callback));
+        }
+        catch (const InternalFailure& e)
+        {
+            commit<InternalFailure>();
+        }
+    }
+    auto csrObjectPath = objectPath + '/' + "csr";
+    return csrObjectPath;
+}
+
+void Manager::generateCSRHelper(
+    std::vector<std::string> alternativeNames, std::string challengePassword,
+    std::string city, std::string commonName, std::string contactPerson,
+    std::string country, std::string email, std::string givenName,
+    std::string initials, int64_t keyBitLength, std::string keyCurveId,
+    std::string keyPairAlgorithm, std::vector<std::string> keyUsage,
+    std::string organization, std::string organizationalUnit, std::string state,
+    std::string surname, std::string unstructuredName)
+{
+    int ret = 0;
+
+    // set version of x509 req
+    int nVersion = 1;
+    // TODO: Issue#6 need to make version number configurable
+    X509_REQ_Ptr x509Req(X509_REQ_new(), ::X509_REQ_free);
+    ret = X509_REQ_set_version(x509Req.get(), nVersion);
+    if (ret == 0)
+    {
+        log<level::ERR>("Error occured during X509_REQ_set_version call");
+        elog<InternalFailure>();
+    }
+
+    // set subject of x509 req
+    X509_NAME* x509Name = X509_REQ_get_subject_name(x509Req.get());
+
+    if (!alternativeNames.empty())
+    {
+        for (auto& name : alternativeNames)
+        {
+            addEntry(x509Name, "subjectAltName", name);
+        }
+    }
+    addEntry(x509Name, "challengePassword", challengePassword);
+    addEntry(x509Name, "L", city);
+    addEntry(x509Name, "CN", commonName);
+    addEntry(x509Name, "name", contactPerson);
+    addEntry(x509Name, "C", country);
+    addEntry(x509Name, "emailAddress", email);
+    addEntry(x509Name, "GN", givenName);
+    addEntry(x509Name, "initials", initials);
+    addEntry(x509Name, "algorithm", keyPairAlgorithm);
+    if (!keyUsage.empty())
+    {
+        for (auto& usage : keyUsage)
+        {
+            addEntry(x509Name, "keyUsage", usage);
+        }
+    }
+    addEntry(x509Name, "O", organization);
+    addEntry(x509Name, "ST", state);
+    addEntry(x509Name, "SN", surname);
+    addEntry(x509Name, "unstructuredName", unstructuredName);
+
+    // Generate private key and write to file
+    EVP_PKEY_Ptr pKey = writePrivateKey(keyBitLength, x509Req);
+
+    // set sign key of x509 req
+    ret = X509_REQ_sign(x509Req.get(), pKey.get(), EVP_sha256());
+    if (ret <= 0)
+    {
+        log<level::ERR>("Error occured while signing key of x509");
+        elog<InternalFailure>();
+    }
+    log<level::INFO>("Writing CSR to file");
+    std::string path = fs::path(certInstallPath).parent_path();
+    std::string csrFilePath = path + '/' + CSR_FILE_NAME;
+    writeCSR(csrFilePath, x509Req);
+}
+
+EVP_PKEY_Ptr Manager::writePrivateKey(int64_t keyBitLength,
+                                      X509_REQ_Ptr& x509Req)
+{
+    int ret = 0;
+    // generate rsa key
+    BIGNUM_Ptr bne(BN_new(), ::BN_free);
+    ret = BN_set_word(bne.get(), RSA_F4);
+    if (ret == 0)
+    {
+        log<level::ERR>("Error occured during BN_set_word call");
+        elog<InternalFailure>();
+    }
+
+    // set keybit length to default value if not set
+    if (keyBitLength <= 0)
+    {
+        keyBitLength = 2048;
+    }
+    RSA* rsa = RSA_new();
+    ret = RSA_generate_key_ex(rsa, keyBitLength, bne.get(), NULL);
+    if (ret != 1)
+    {
+        free(rsa);
+        log<level::ERR>("Error occured during RSA_generate_key_ex call",
+                        entry("KEYBITLENGTH=%PRIu64", keyBitLength));
+        elog<InternalFailure>();
+    }
+
+    // set public key of x509 req
+    EVP_PKEY_Ptr pKey(EVP_PKEY_new(), ::EVP_PKEY_free);
+    EVP_PKEY_assign_RSA(pKey.get(), rsa);
+    ret = X509_REQ_set_pubkey(x509Req.get(), pKey.get());
+    if (ret == 0)
+    {
+        log<level::ERR>("Error occured while setting Public key");
+        elog<InternalFailure>();
+    }
+
+    log<level::ERR>("Writing private key to file");
+    // write private key to file
+    std::string path = fs::path(certInstallPath).parent_path();
+    std::string privKeyPath = path + '/' + PRIV_KEY_FILE_NAME;
+
+    FILE* fp = std::fopen(privKeyPath.c_str(), "w");
+    if (fp == NULL)
+    {
+        ret = -1;
+        log<level::ERR>("Error occured creating private key file");
+        elog<InternalFailure>();
+    }
+    ret = PEM_write_PrivateKey(fp, pKey.get(), NULL, NULL, 0, 0, NULL);
+    std::fclose(fp);
+    if (ret == 0)
+    {
+        log<level::ERR>("Error occured while writing private key to file");
+        elog<InternalFailure>();
+    }
+    return pKey;
+}
+
+void Manager::addEntry(X509_NAME* x509Name, const char* field,
+                       const std::string& bytes)
+{
+    if (bytes.empty())
+    {
+        return;
+    }
+    int ret = X509_NAME_add_entry_by_txt(
+        x509Name, field, MBSTRING_ASC,
+        reinterpret_cast<const unsigned char*>(bytes.c_str()), -1, -1, 0);
+    if (ret != 1)
+    {
+        log<level::ERR>("Unable to set entry", entry("FIELD=%s", field),
+                        entry("VALUE=%s", bytes.c_str()));
+        elog<InternalFailure>();
+    }
+}
+
+void Manager::createCSRObject(const Status& status)
+{
+    if (csrPtr)
+    {
+        csrPtr.reset(nullptr);
+    }
+    auto csrObjectPath = objectPath + '/' + "csr";
+    csrPtr = std::make_unique<CSR>(bus, csrObjectPath.c_str(),
+                                   certInstallPath.c_str(), status);
+}
+
+void Manager::writeCSR(const std::string& filePath, const X509_REQ_Ptr& x509Req)
+{
+    if (fs::exists(filePath))
+    {
+        log<level::INFO>("Removing the existing file",
+                         entry("FILENAME=%s", filePath.c_str()));
+        if (!fs::remove(filePath.c_str()))
+        {
+            log<level::ERR>("Unable to remove the file",
+                            entry("FILENAME=%s", filePath.c_str()));
+            elog<InternalFailure>();
+        }
+    }
+
+    FILE* fp = NULL;
+
+    if ((fp = std::fopen(filePath.c_str(), "w")) == NULL)
+    {
+        log<level::ERR>("Error opening the file to write the CSR",
+                        entry("FILENAME=%s", filePath.c_str()));
+        elog<InternalFailure>();
+    }
+
+    int rc = PEM_write_X509_REQ(fp, x509Req.get());
+    if (!rc)
+    {
+        log<level::ERR>("PEM write routine failed",
+                        entry("FILENAME=%s", filePath.c_str()));
+        std::fclose(fp);
+        elog<InternalFailure>();
+    }
+    std::fclose(fp);
+}
+
 } // namespace certs
 } // namespace phosphor