#include "config.h" #include "image_verify.hpp" #include "images.hpp" #include "utils.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include #include #include #include namespace phosphor { namespace software { namespace image { PHOSPHOR_LOG2_USING; using namespace phosphor::logging; using namespace phosphor::software::manager; using InternalFailure = sdbusplus::error::xyz::openbmc_project::common::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); // Get purpose auto purposeString = Version::getValue(file, "purpose"); auto convertedPurpose = sdbusplus::message::convert_from_string(purposeString); purpose = convertedPurpose.value_or(Version::VersionPurpose::Unknown); } AvailableKeyTypes Signature::getAvailableKeyTypesFromSystem() const { AvailableKeyTypes keyTypes{}; // Find the path of all the files std::error_code ec; if (!fs::is_directory(signedConfPath, ec)) { error("Signed configuration path not found in the system: {ERROR_MSG}", "ERROR_MSG", ec.message()); elog(); } // 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_VERIFY // Only verify full image for BMC if (purpose != VersionPurpose::BMC) { return ret; } std::vector 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); std::error_code ec; fs::remove(tmpFullFile, ec); #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 (!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 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; std::error_code ec; if (fs::exists(file, ec)) { 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()) { 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 validation."); 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(); } // 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. std::error_code ec; if (!(fs::exists(file, ec) && fs::exists(sigFile, ec))) { error("Failed to find the Data or signature file {PATH}", "PATH", file); if (ec) { error("Error message: {ERROR_MSG}", "ERROR_MSG", ec.message()); } elog(); } // Create RSA. auto publicRSA = createPublicRSA(publicKey); if (!publicRSA) { error("Failed to create RSA from {PATH}", "PATH", publicKey); elog(); } // 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(); } 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(); } // Hash the data file and update the verification context auto size = fs::file_size(file, ec); 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(); } // Verify the data with signature. size = fs::file_size(sigFile, ec); auto signature = mapFile(sigFile, size); result = EVP_DigestVerifyFinal( rsaVerifyCtx.get(), reinterpret_cast(signature()), size); // Check the verification result. if (result < 0) { error("Error ({RC}) occurred during EVP_DigestVerifyFinal", "RC", ERR_get_error()); elog(); } 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) { std::error_code ec; auto size = fs::file_size(publicKey, ec); // 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(); } 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& imageList, bool& fileFound) { bool valid = true; fileFound = false; for (auto& bmcImage : imageList) { fs::path file(filePath); file /= bmcImage; std::error_code ec; if (!fs::exists(file, ec)) { valid = false; break; } fileFound = true; fs::path sigFile(file); sigFile += SIGNATURE_FILE_EXT; // Verify the signature. valid = verifyFile(file, sigFile, publicKeyPath, hashType); if (!valid) { error("Image file Signature Validation failed on {PATH}", "PATH", bmcImage); return false; } } return valid; } } // namespace image } // namespace software } // namespace phosphor