blob: d67cfacd7a9815f045e9840b2391e22ab24e1b5e [file] [log] [blame]
#include "config.h"
#include "image_verify.hpp"
#include "images.hpp"
#include "utils.hpp"
#include "version.hpp"
#include <fcntl.h>
#include <openssl/err.h>
#include <sys/stat.h>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/lg2.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
#include <cassert>
#include <fstream>
#include <set>
namespace phosphor
{
namespace software
{
namespace image
{
PHOSPHOR_LOG2_USING;
using namespace phosphor::logging;
using namespace phosphor::software::manager;
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_NAME);
keyType = Version::getValue(file, keyTypeTag);
hashType = Version::getValue(file, hashFunctionTag);
}
AvailableKeyTypes Signature::getAvailableKeyTypesFromSystem() const
{
AvailableKeyTypes keyTypes{};
// Find the path of all the files
if (!fs::is_directory(signedConfPath))
{
error("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/OpenBMC/publickey
// /etc/activationdata/OpenBMC/hashfunc
// /etc/activationdata/GA/publickey
// /etc/activationdata/GA/hashfunc
// Set will have OpenBMC, 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/OpenBMC/ -> get OpenBMC 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::verifyFullImage()
{
bool ret = true;
#ifdef WANT_SIGNATURE_FULL_VERIFY
std::vector<std::string> fullImages = {
fs::path(imageDirPath) / "image-bmc.sig",
fs::path(imageDirPath) / "image-hostfw.sig",
fs::path(imageDirPath) / "image-kernel.sig",
fs::path(imageDirPath) / "image-rofs.sig",
fs::path(imageDirPath) / "image-rwfs.sig",
fs::path(imageDirPath) / "image-u-boot.sig",
fs::path(imageDirPath) / "MANIFEST.sig",
fs::path(imageDirPath) / "publickey.sig"};
// Merge files
std::string tmpFullFile = "/tmp/image-full";
utils::mergeFiles(fullImages, tmpFullFile);
// Validate the full image files
fs::path pkeyFullFile(tmpFullFile);
std::string imageFullSig = "image-full.sig";
fs::path pkeyFullFileSig(imageDirPath / imageFullSig);
pkeyFullFileSig.replace_extension(SIGNATURE_FILE_EXT);
// image specific publickey file name.
fs::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
ret = verifyFile(pkeyFullFile, pkeyFullFileSig, publicKeyFile, hashType);
fs::remove(tmpFullFile);
#endif
return ret;
}
bool Signature::verify()
{
try
{
bool valid;
// Verify the MANIFEST and publickey file using available
// public keys and hash on the system.
if (false == systemLevelVerify())
{
error("System level Signature Validation failed");
return false;
}
bool bmcFilesFound = false;
// image specific publickey file name.
fs::path publicKeyFile(imageDirPath / PUBLICKEY_FILE_NAME);
// Record the images which are being updated
// First check and Validate for the fullimage, then check and Validate
// for images with partitions
std::vector<std::string> imageUpdateList = {bmcFullImages};
valid = checkAndVerifyImage(imageDirPath, publicKeyFile,
imageUpdateList, bmcFilesFound);
if (bmcFilesFound && !valid)
{
return false;
}
if (!valid)
{
// Validate bmcImages
imageUpdateList.clear();
imageUpdateList.assign(bmcImages.begin(), bmcImages.end());
valid = checkAndVerifyImage(imageDirPath, publicKeyFile,
imageUpdateList, bmcFilesFound);
if (bmcFilesFound && !valid)
{
return false;
}
}
// Validate the optional image files.
auto optionalImages = getOptionalImages();
bool optionalFilesFound = false;
bool optionalImagesValid = false;
for (const auto& optionalImage : optionalImages)
{
// Build Image File name
fs::path file(imageDirPath);
file /= optionalImage;
if (fs::exists(file))
{
optionalFilesFound = true;
// Build Signature File name
fs::path sigFile(file);
sigFile += SIGNATURE_FILE_EXT;
// Verify the signature.
optionalImagesValid =
verifyFile(file, sigFile, publicKeyFile, hashType);
if (!optionalImagesValid)
{
error("Image file Signature Validation failed on {IMAGE}",
"IMAGE", optionalImage);
return false;
}
}
}
if (verifyFullImage() == false)
{
error("Image full file Signature Validation failed");
return false;
}
if (!bmcFilesFound && !optionalFilesFound)
{
error("Unable to find files to verify");
return false;
}
// Either BMC images or optional images shall be valid
assert(valid || optionalImagesValid);
debug("Successfully completed Signature vaildation.");
return true;
}
catch (const InternalFailure& e)
{
return false;
}
catch (const std::exception& e)
{
error("Error during processing: {ERROR}", "ERROR", e);
return false;
}
}
bool Signature::systemLevelVerify()
{
// Get available key types from the system.
auto keyTypes = getAvailableKeyTypesFromSystem();
if (keyTypes.empty())
{
error("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_NAME);
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 hashFunc = Version::getValue(keyHashPair.first, 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)))
{
error("Failed to find the Data or signature file {PATH}.", "PATH",
file);
elog<InternalFailure>();
}
// Create RSA.
auto publicRSA = createPublicRSA(publicKey);
if (!publicRSA)
{
error("Failed to create RSA from {PATH}", "PATH", publicKey);
elog<InternalFailure>();
}
// Initializes a digest context.
EVP_MD_CTX_Ptr rsaVerifyCtx(EVP_MD_CTX_new(), ::EVP_MD_CTX_free);
// Adds all digest algorithms to the internal table
OpenSSL_add_all_digests();
// Create Hash structure.
auto hashStruct = EVP_get_digestbyname(hashFunc.c_str());
if (!hashStruct)
{
error("EVP_get_digestbynam: Unknown message digest: {HASH}", "HASH",
hashFunc);
elog<InternalFailure>();
}
auto result = EVP_DigestVerifyInit(rsaVerifyCtx.get(), nullptr, hashStruct,
nullptr, publicRSA.get());
if (result <= 0)
{
error("Error ({RC}) occurred during EVP_DigestVerifyInit", "RC",
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)
{
error("Error ({RC}) occurred during EVP_DigestVerifyUpdate", "RC",
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)
{
error("Error ({RC}) occurred during EVP_DigestVerifyFinal", "RC",
ERR_get_error());
elog<InternalFailure>();
}
if (result == 0)
{
error("EVP_DigestVerifyFinal:Signature validation failed on {PATH}",
"PATH", sigFile);
return false;
}
return true;
}
inline EVP_PKEY_Ptr Signature::createPublicRSA(const fs::path& publicKey)
{
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)
{
error("Failed to create new BIO Memory buffer");
elog<InternalFailure>();
}
return {PEM_read_bio_PUBKEY(keyBio.get(), nullptr, nullptr, nullptr),
&::EVP_PKEY_free};
}
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);
}
bool Signature::checkAndVerifyImage(const std::string& filePath,
const std::string& publicKeyPath,
const std::vector<std::string>& imageList,
bool& fileFound)
{
bool valid = true;
fileFound = false;
for (auto& bmcImage : imageList)
{
fs::path file(filePath);
file /= bmcImage;
if (!fs::exists(file))
{
valid = false;
break;
}
fileFound = true;
fs::path sigFile(file);
sigFile += SIGNATURE_FILE_EXT;
// Verify the signature.
auto valid = verifyFile(file, sigFile, publicKeyPath, hashType);
if (valid == false)
{
error("Image file Signature Validation failed on {PATH}", "PATH",
bmcImage);
return false;
}
}
return valid;
}
} // namespace image
} // namespace software
} // namespace phosphor