diff options
author | Jayanth Othayoth <ojayanth@in.ibm.com> | 2018-03-20 06:31:59 -0500 |
---|---|---|
committer | Adriana Kobylak <anoo@us.ibm.com> | 2018-03-23 11:15:35 -0500 |
commit | 70804dcd6b6d4f2d5c2eda4dd80741da0471510e (patch) | |
tree | ab2e663eb2d305ec515144493a76b0b104f992ea | |
parent | 643e730e3b9818bdd878eebee209b268c234fc65 (diff) | |
download | openpower-pnor-code-mgmt-70804dcd6b6d4f2d5c2eda4dd80741da0471510e.tar.gz openpower-pnor-code-mgmt-70804dcd6b6d4f2d5c2eda4dd80741da0471510e.zip |
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>
-rwxr-xr-x | Makefile.am | 4 | ||||
-rwxr-xr-x | configure.ac | 23 | ||||
-rw-r--r-- | image_verify.cpp | 312 | ||||
-rw-r--r-- | image_verify.hpp | 217 |
4 files changed, 556 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index cada5a269..cb1271e04 100755 --- a/Makefile.am +++ b/Makefile.am @@ -13,6 +13,10 @@ openpower_update_manager_SOURCES = \ 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 0c95c5701..f9f8d37bd 100755 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,29 @@ AC_ARG_VAR(MANIFEST_FILE, [The path to the MANIFEST file]) 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 000000000..4df7a5e5c --- /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 000000000..77fa6f45a --- /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 |