Add support for signature verification routines

Enabled high level logic flow for the PNOR signed image
signature validation routines.

Includes reading hash type, key type from Manifest file.

Change-Id: I00280fff5a61291852c1f2d5f6fd8aec3dd62bf0
Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index cada5a2..cb1271e 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -13,6 +13,10 @@
 	item_updater.cpp \
 	item_updater_main.cpp
 
+if WANT_SIGNATURE_VERIFY_BUILD
+openpower_update_manager_SOURCES += image_verify.cpp
+endif
+
 nodist_openpower_update_manager_SOURCES = \
 	org/openbmc/Associations/server.cpp
 
diff --git a/configure.ac b/configure.ac
index 0c95c57..f9f8d37 100755
--- a/configure.ac
+++ b/configure.ac
@@ -76,6 +76,29 @@
 AS_IF([test "x$MANIFEST_FILE" == "x"], [MANIFEST_FILE="MANIFEST"])
 AC_DEFINE_UNQUOTED([MANIFEST_FILE], ["$MANIFEST_FILE"], [The path to the MANIFEST file])
 
+AC_ARG_VAR(PUBLICKEY_FILE_NAME, [The name of the public key file])
+AS_IF([test "x$PUBLICKEY_FILE_NAME" == "x"], [PUBLICKEY_FILE_NAME="publickey"])
+AC_DEFINE_UNQUOTED([PUBLICKEY_FILE_NAME], ["$PUBLICKEY_FILE_NAME"], [The name of the public key file])
+
+AC_ARG_VAR(HASH_FILE_NAME, [Hash file name])
+AS_IF([test "x$HASH_FILE_NAME" == "x"], [HASH_FILE_NAME="hashfunc"])
+AC_DEFINE_UNQUOTED([HASH_FILE_NAME], ["$HASH_FILE_NAME"], [The name of the hash file])
+
+AC_ARG_VAR(PNOR_SIGNED_IMAGE_CONF_PATH, [Path of PNOR image public key and hash function files])
+AS_IF([test "x$PNOR_SIGNED_IMAGE_CONF_PATH" == "x"], [PNOR_SIGNED_IMAGE_CONF_PATH="/etc/activationdata/"])
+AC_DEFINE_UNQUOTED([PNOR_SIGNED_IMAGE_CONF_PATH], ["$PNOR_SIGNED_IMAGE_CONF_PATH"], [Path of PNOR image public key and hash function files])
+
+AC_ARG_VAR(SIGNATURE_FILE_EXT, [The extension of the Signature file])
+AS_IF([test "x$SIGNATURE_FILE_EXT" == "x"], [SIGNATURE_FILE_EXT=".sig"])
+AC_DEFINE_UNQUOTED([SIGNATURE_FILE_EXT], ["$SIGNATURE_FILE_EXT"], [The extension of the Signature file])
+
+# setup signature verification
+AC_ARG_ENABLE([verify_signature],
+    AS_HELP_STRING([--enable-verify_signature], [Enable image signature validation.]))
+AS_IF([test "x$enable_verify_signature" == "xyes"], \
+    [AC_DEFINE([WANT_SIGNATURE_VERIFY],[],[Enable image signature validation.])])
+AM_CONDITIONAL([WANT_SIGNATURE_VERIFY_BUILD], [test "x$enable_verify_signature" == "xyes"])
+
 AC_DEFINE(CHASSIS_STATE_PATH, "/xyz/openbmc_project/state/chassis0",
     [The chassis state path.])
 AC_DEFINE(CHASSIS_STATE_OBJ, "xyz.openbmc_project.State.Chassis",
diff --git a/image_verify.cpp b/image_verify.cpp
new file mode 100644
index 0000000..4df7a5e
--- /dev/null
+++ b/image_verify.cpp
@@ -0,0 +1,312 @@
+#include <set>
+#include <fstream>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <openssl/err.h>
+
+#include "image_verify.hpp"
+#include "config.h"
+#include "version.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace openpower
+{
+namespace software
+{
+namespace image
+{
+
+using namespace phosphor::logging;
+using namespace openpower::software::updater;
+using InternalFailure =
+    sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+
+constexpr auto keyTypeTag = "KeyType";
+constexpr auto hashFunctionTag = "HashType";
+
+Signature::Signature(const fs::path& imageDirPath,
+                     const fs::path& signedConfPath) :
+    imageDirPath(imageDirPath),
+    signedConfPath(signedConfPath)
+{
+    fs::path file(imageDirPath / MANIFEST_FILE);
+
+    auto keyValues =
+        Version::getValue(file, {{keyTypeTag, " "}, {hashFunctionTag, " "}});
+    keyType = keyValues.at(keyTypeTag);
+    hashType = keyValues.at(hashFunctionTag);
+}
+
+AvailableKeyTypes Signature::getAvailableKeyTypesFromSystem() const
+{
+    AvailableKeyTypes keyTypes{};
+
+    // Find the path of all the files
+    if (!fs::is_directory(signedConfPath))
+    {
+        log<level::ERR>("Signed configuration path not found in the system");
+        elog<InternalFailure>();
+    }
+
+    // Look for all the hash and public key file names get the key value
+    // For example:
+    // /etc/activationdata/OpenPOWER/publickey
+    // /etc/activationdata/OpenPOWER/hashfunc
+    // /etc/activationdata/GA/publickey
+    // /etc/activationdata/GA/hashfunc
+    // Set will have OpenPOWER, GA
+
+    for (const auto& p : fs::recursive_directory_iterator(signedConfPath))
+    {
+        if ((p.path().filename() == HASH_FILE_NAME) ||
+            (p.path().filename() == PUBLICKEY_FILE_NAME))
+        {
+            // extract the key types
+            // /etc/activationdata/GA/  -> get GA from the path
+            auto key = p.path().parent_path();
+            keyTypes.insert(key.filename());
+        }
+    }
+
+    return keyTypes;
+}
+
+inline KeyHashPathPair Signature::getKeyHashFileNames(const Key_t& key) const
+{
+    fs::path hashpath(signedConfPath / key / HASH_FILE_NAME);
+    fs::path keyPath(signedConfPath / key / PUBLICKEY_FILE_NAME);
+
+    return std::make_pair(std::move(hashpath), std::move(keyPath));
+}
+
+bool Signature::verify()
+{
+    try
+    {
+        // Verify the MANIFEST and publickey file using available
+        // public keys and hash on the system.
+        if (false == systemLevelVerify())
+        {
+            log<level::ERR>("System level Signature Validation failed");
+            return false;
+        }
+
+        // image specfic publickey file name.
+        fs::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
+
+        // Validate the PNOR image file.
+        // Build Image File name
+        fs::path file(imageDirPath);
+        file /= squashFSImage;
+
+        // Build Signature File name
+        std::string fileName = file.filename();
+        fs::path sigFile(imageDirPath);
+        sigFile /= fileName + SIGNATURE_FILE_EXT;
+
+        // Verify the signature.
+        auto valid = verifyFile(file, sigFile, publicKeyFile, hashType);
+        if (valid == false)
+        {
+            log<level::ERR>("Image file Signature Validation failed",
+                            entry("IMAGE=%s", squashFSImage.c_str()));
+            return false;
+        }
+
+        log<level::DEBUG>("Sucessfully completed Signature vaildation.");
+
+        return true;
+    }
+    catch (const InternalFailure& e)
+    {
+        return false;
+    }
+    catch (const std::exception& e)
+    {
+        log<level::ERR>(e.what());
+        return false;
+    }
+}
+
+bool Signature::systemLevelVerify()
+{
+    // Get available key types from the system.
+    auto keyTypes = getAvailableKeyTypesFromSystem();
+    if (keyTypes.empty())
+    {
+        log<level::ERR>("Missing Signature configuration data in system");
+        elog<InternalFailure>();
+    }
+
+    // Build publickey and its signature file name.
+    fs::path pkeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
+    fs::path pkeyFileSig(pkeyFile);
+    pkeyFileSig.replace_extension(SIGNATURE_FILE_EXT);
+
+    // Build manifest and its signature file name.
+    fs::path manifestFile(imageDirPath / MANIFEST_FILE);
+    fs::path manifestFileSig(manifestFile);
+    manifestFileSig.replace_extension(SIGNATURE_FILE_EXT);
+
+    auto valid = false;
+
+    // Verify the file signature with available key types
+    // public keys and hash function.
+    // For any internal failure during the key/hash pair specific
+    // validation, should continue the validation with next
+    // available Key/hash pair.
+    for (const auto& keyType : keyTypes)
+    {
+        auto keyHashPair = getKeyHashFileNames(keyType);
+        auto keyValues =
+            Version::getValue(keyHashPair.first, {{hashFunctionTag, " "}});
+        auto hashFunc = keyValues.at(hashFunctionTag);
+
+        try
+        {
+            // Verify manifest file signature
+            valid = verifyFile(manifestFile, manifestFileSig,
+                               keyHashPair.second, hashFunc);
+            if (valid)
+            {
+                // Verify publickey file signature.
+                valid = verifyFile(pkeyFile, pkeyFileSig, keyHashPair.second,
+                                   hashFunc);
+                if (valid)
+                {
+                    break;
+                }
+            }
+        }
+        catch (const InternalFailure& e)
+        {
+            valid = false;
+        }
+    }
+    return valid;
+}
+
+bool Signature::verifyFile(const fs::path& file, const fs::path& sigFile,
+                           const fs::path& publicKey,
+                           const std::string& hashFunc)
+{
+
+    // Check existence of the files in the system.
+    if (!(fs::exists(file) && fs::exists(sigFile)))
+    {
+        log<level::ERR>("Failed to find the Data or signature file.",
+                        entry("FILE=%s", file.c_str()));
+        elog<InternalFailure>();
+    }
+
+    // Create RSA.
+    auto publicRSA = createPublicRSA(publicKey);
+    if (publicRSA == nullptr)
+    {
+        log<level::ERR>("Failed to create RSA",
+                        entry("FILE=%s", publicKey.c_str()));
+        elog<InternalFailure>();
+    }
+
+    // Assign key to RSA.
+    EVP_PKEY_Ptr pKeyPtr(EVP_PKEY_new(), ::EVP_PKEY_free);
+    EVP_PKEY_assign_RSA(pKeyPtr.get(), publicRSA);
+
+    // Initializes a digest context.
+    EVP_MD_CTX_Ptr rsaVerifyCtx(EVP_MD_CTX_create(), ::EVP_MD_CTX_destroy);
+
+    // Adds all digest algorithms to the internal table
+    OpenSSL_add_all_digests();
+
+    // Create Hash structre.
+    auto hashStruct = EVP_get_digestbyname(hashFunc.c_str());
+    if (!hashStruct)
+    {
+        log<level::ERR>("EVP_get_digestbynam: Unknown message digest",
+                        entry("HASH=%s", hashFunc.c_str()));
+        elog<InternalFailure>();
+    }
+
+    auto result = EVP_DigestVerifyInit(rsaVerifyCtx.get(), nullptr, hashStruct,
+                                       nullptr, pKeyPtr.get());
+
+    if (result <= 0)
+    {
+        log<level::ERR>("Error occured during EVP_DigestVerifyInit",
+                        entry("ERRCODE=%lu", ERR_get_error()));
+        elog<InternalFailure>();
+    }
+
+    // Hash the data file and update the verification context
+    auto size = fs::file_size(file);
+    auto dataPtr = mapFile(file, size);
+
+    result = EVP_DigestVerifyUpdate(rsaVerifyCtx.get(), dataPtr(), size);
+    if (result <= 0)
+    {
+        log<level::ERR>("Error occured during EVP_DigestVerifyUpdate",
+                        entry("ERRCODE=%lu", ERR_get_error()));
+        elog<InternalFailure>();
+    }
+
+    // Verify the data with signature.
+    size = fs::file_size(sigFile);
+    auto signature = mapFile(sigFile, size);
+
+    result = EVP_DigestVerifyFinal(
+        rsaVerifyCtx.get(), reinterpret_cast<unsigned char*>(signature()),
+        size);
+
+    // Check the verification result.
+    if (result < 0)
+    {
+        log<level::ERR>("Error occured during EVP_DigestVerifyFinal",
+                        entry("ERRCODE=%lu", ERR_get_error()));
+        elog<InternalFailure>();
+    }
+
+    if (result == 0)
+    {
+        log<level::ERR>("EVP_DigestVerifyFinal:Signature validation failed",
+                        entry("PATH=%s", sigFile.c_str()));
+        return false;
+    }
+    return true;
+}
+
+inline RSA* Signature::createPublicRSA(const fs::path& publicKey)
+{
+    RSA* rsa = nullptr;
+    auto size = fs::file_size(publicKey);
+
+    // Read public key file
+    auto data = mapFile(publicKey, size);
+
+    BIO_MEM_Ptr keyBio(BIO_new_mem_buf(data(), -1), &::BIO_free);
+    if (keyBio.get() == nullptr)
+    {
+        log<level::ERR>("Failed to create new BIO Memory buffer");
+        elog<InternalFailure>();
+    }
+
+    rsa = PEM_read_bio_RSA_PUBKEY(keyBio.get(), &rsa, nullptr, nullptr);
+
+    return rsa;
+}
+
+CustomMap Signature::mapFile(const fs::path& path, size_t size)
+{
+
+    CustomFd fd(open(path.c_str(), O_RDONLY));
+
+    return CustomMap(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd(), 0),
+                     size);
+}
+
+} // namespace image
+} // namespace software
+} // namespace openpower
diff --git a/image_verify.hpp b/image_verify.hpp
new file mode 100644
index 0000000..77fa6f4
--- /dev/null
+++ b/image_verify.hpp
@@ -0,0 +1,217 @@
+#pragma once
+#include <openssl/rsa.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <experimental/filesystem>
+#include <set>
+#include <unistd.h>
+#include <sys/mman.h>
+
+namespace openpower
+{
+namespace software
+{
+namespace image
+{
+
+namespace fs = std::experimental::filesystem;
+using Key_t = std::string;
+using Hash_t = std::string;
+using PublicKeyPath = fs::path;
+using HashFilePath = fs::path;
+using KeyHashPathPair = std::pair<HashFilePath, PublicKeyPath>;
+using AvailableKeyTypes = std::set<Key_t>;
+
+// RAII support for openSSL functions.
+using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
+using EVP_PKEY_Ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
+using EVP_MD_CTX_Ptr =
+    std::unique_ptr<EVP_MD_CTX, decltype(&::EVP_MD_CTX_destroy)>;
+
+// PNOR flash image file name.
+constexpr auto squashFSImage = "pnor.xz.squashfs";
+
+/** @struct CustomFd
+ *
+ *  RAII wrapper for file descriptor.
+ */
+struct CustomFd
+{
+  public:
+    CustomFd() = delete;
+    CustomFd(const CustomFd&) = delete;
+    CustomFd& operator=(const CustomFd&) = delete;
+    CustomFd(CustomFd&&) = default;
+    CustomFd& operator=(CustomFd&&) = default;
+    /** @brief Saves File descriptor and uses it to do file operation
+     *
+     *  @param[in] fd - File descriptor
+     */
+    CustomFd(int fd) : fd(fd)
+    {
+    }
+
+    ~CustomFd()
+    {
+        if (fd >= 0)
+        {
+            close(fd);
+        }
+    }
+
+    int operator()() const
+    {
+        return fd;
+    }
+
+  private:
+    /** @brief File descriptor */
+    int fd = -1;
+};
+
+/** @struct CustomMap
+ *
+ *  RAII wrapper for mmap.
+ */
+struct CustomMap
+{
+  private:
+    /** @brief starting address of the map   */
+    void* addr;
+
+    /** @brief length of the mapping   */
+    size_t length;
+
+  public:
+    CustomMap() = delete;
+    CustomMap(const CustomMap&) = delete;
+    CustomMap& operator=(const CustomMap&) = delete;
+    CustomMap(CustomMap&&) = default;
+    CustomMap& operator=(CustomMap&&) = default;
+
+    /** @brief Saves starting address of the map and
+     *         and length of the file.
+     *  @param[in]  addr - Starting address of the map
+     *  @param[in]  length - length of the map
+     */
+    CustomMap(void* addr, size_t length) : addr(addr), length(length)
+    {
+    }
+
+    ~CustomMap()
+    {
+        munmap(addr, length);
+    }
+
+    void* operator()() const
+    {
+        return addr;
+    }
+};
+
+/** @class Signature
+ *  @brief Contains signature verification functions.
+ *  @details The software image class that contains the signature
+ *           verification functions for signed image.
+ */
+class Signature
+{
+  public:
+    Signature() = delete;
+    Signature(const Signature&) = delete;
+    Signature& operator=(const Signature&) = delete;
+    Signature(Signature&&) = default;
+    Signature& operator=(Signature&&) = default;
+    ~Signature() = default;
+
+    /**
+     * @brief Constructs Signature.
+     * @param[in]  imageDirPath - image path
+     * @param[in]  signedConfPath - Path of public key
+     *                              hash function files
+     */
+    Signature(const fs::path& imageDirPath, const fs::path& signedConfPath);
+
+    /**
+     * @brief Image signature verification function.
+     *        Verify the Manifest and public key file signature using the
+     *        public keys available in the system first. After successful
+     *        validation, continue the whole image files signature
+     *        validation using the image specific public key and the
+     *        hash function.
+     *
+     *        @return true if signature verification was successful,
+     *                     false if not
+     */
+    bool verify();
+
+  private:
+    /**
+     * @brief Function used for system level file signature validation
+     *        of image specific publickey file and manifest file
+     *        using the available public keys and hash functions
+     *        in the system.
+     *        Refer code-update documentation for more details.
+     */
+    bool systemLevelVerify();
+
+    /**
+     *  @brief Return all key types stored in the BMC based on the
+     *         public key and hashfunc files stored in the BMC.
+     *
+     *  @return list
+     */
+    AvailableKeyTypes getAvailableKeyTypesFromSystem() const;
+
+    /**
+     *  @brief Return public key and hash function file names for the
+     *  corresponding key type
+     *
+     *  @param[in]  key - key type
+     *  @return Pair of hash and public key file names
+     */
+    inline KeyHashPathPair getKeyHashFileNames(const Key_t& key) const;
+
+    /**
+     * @brief Verify the file signature using public key and hash function
+     *
+     * @param[in]  - Image file path
+     * @param[in]  - Signature file path
+     * @param[in]  - Public key
+     * @param[in]  - Hash function name
+     * @return true if signature verification was successful, false if not
+     */
+    bool verifyFile(const fs::path& file, const fs::path& signature,
+                    const fs::path& publicKey, const std::string& hashFunc);
+
+    /**
+     * @brief Create RSA object from the public key
+     * @param[in]  - publickey
+     * @param[out] - RSA Object.
+     */
+    inline RSA* createPublicRSA(const fs::path& publicKey);
+
+    /**
+     * @brief Memory map the  file
+     * @param[in]  - file path
+     * @param[in]  - file size
+     * @param[out] - Custom Mmap address
+     */
+    CustomMap mapFile(const fs::path& path, size_t size);
+
+    /** @brief Directory where software images are placed*/
+    fs::path imageDirPath;
+
+    /** @brief Path of public key and hash function files */
+    fs::path signedConfPath;
+
+    /** @brief key type defined in mainfest file */
+    Key_t keyType;
+
+    /** @brief Hash type defined in mainfest file */
+    Hash_t hashType;
+};
+
+} // namespace image
+} // namespace software
+} // namespace openpower