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/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