#include "config.h" #include "image_verify.hpp" #include "utils.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include #include using namespace phosphor::software::manager; using namespace phosphor::software::image; namespace fs = std::filesystem; class VersionTest : public testing::Test { protected: void SetUp() override { char versionDir[] = "./versionXXXXXX"; _directory = mkdtemp(versionDir); if (_directory.empty()) { throw std::bad_alloc(); } } void TearDown() override { fs::remove_all(_directory); } std::string _directory; }; /** @brief Make sure we correctly get the version and purpose from getValue()*/ TEST_F(VersionTest, TestGetValue) { auto manifestFilePath = _directory + "/" + "MANIFEST"; auto version = "test-version"; auto purpose = "BMC"; std::ofstream file; file.open(manifestFilePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); file << "version=" << version << "\n"; file << "purpose=" << purpose << "\n"; file.close(); EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version); EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose); } TEST_F(VersionTest, TestGetRepeatedValue) { auto manifestFilePath = _directory + "/" + "MANIFEST"; const std::vector names = {"foo.bar", "baz.bim"}; std::ofstream file; file.open(manifestFilePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); for (const auto& name : names) { file << "CompatibleName=" << name << "\n"; } file.close(); EXPECT_EQ(Version::getRepeatedValues(manifestFilePath, "CompatibleName"), names); } TEST_F(VersionTest, TestGetValueWithCRLF) { auto manifestFilePath = _directory + "/" + "MANIFEST"; auto version = "test-version"; auto purpose = "BMC"; std::ofstream file; file.open(manifestFilePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); file << "version=" << version << "\r\n"; file << "purpose=" << purpose << "\r\n"; file.close(); EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version); EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose); } TEST_F(VersionTest, TestGetVersionWithQuotes) { auto releasePath = _directory + "/" + "os-release"; auto version = "1.2.3-test-version"; std::ofstream file; file.open(releasePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); file << "VERSION_ID=\"" << version << "\"\n"; file.close(); EXPECT_EQ(Version::getBMCVersion(releasePath), version); } TEST_F(VersionTest, TestGetVersionWithoutQuotes) { auto releasePath = _directory + "/" + "os-release"; auto version = "9.88.1-test-version"; std::ofstream file; file.open(releasePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); file << "VERSION_ID=" << version << "\n"; file.close(); EXPECT_EQ(Version::getBMCVersion(releasePath), version); } /** @brief Make sure we correctly get the Id from getId()*/ TEST_F(VersionTest, TestGetId) { auto version = "test-id"; unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int digest_count = 0; EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free); EVP_DigestInit(ctx.get(), EVP_sha512()); EVP_DigestUpdate(ctx.get(), version, strlen(version)); EVP_DigestFinal(ctx.get(), digest, &digest_count); char mdString[EVP_MAX_MD_SIZE * 2 + 1]; for (decltype(digest_count) i = 0; i < digest_count; i++) { snprintf(&mdString[static_cast(i) * 2], 3, "%02x", (unsigned int)digest[i]); } std::string hexId = std::string(mdString); hexId = hexId.substr(0, 8); EXPECT_EQ(Version::getId(version), hexId); } TEST_F(VersionTest, TestGetExtendedVersion) { auto releasePath = _directory + "/" + "os-release"; auto ExtendedVersion = "9.88.1-test-ExtendedVersion"; std::ofstream file; file.open(releasePath, std::ofstream::out); ASSERT_TRUE(file.is_open()); file << "EXTENDED_VERSION=" << ExtendedVersion << "\n"; file.close(); EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion); } class SignatureTest : public testing::Test { public: static constexpr auto opensslCmd = "openssl dgst -sha256 -sign "; static constexpr auto testPath = "/tmp/_testSig"; protected: static void command(const std::string& cmd) { auto val = std::system(cmd.c_str()); if (val) { std::cout << "COMMAND Error: " << val << std::endl; } } void SetUp() override { // Create test base directory. fs::create_directories(testPath); // Create unique temporary path for images std::string tmpDir(testPath); tmpDir += "/extractXXXXXX"; std::string imageDir = mkdtemp(const_cast(tmpDir.c_str())); // Create unique temporary configuration path std::string tmpConfDir(testPath); tmpConfDir += "/confXXXXXX"; std::string confDir = mkdtemp(const_cast(tmpConfDir.c_str())); extractPath = imageDir; extractPath /= "images"; signedConfPath = confDir; signedConfPath /= "conf"; signedConfOpenBMCPath = confDir; signedConfOpenBMCPath /= "conf"; signedConfOpenBMCPath /= "OpenBMC"; std::cout << "SETUP " << std::endl; command("mkdir " + extractPath.string()); command("mkdir " + signedConfPath.string()); command("mkdir " + signedConfOpenBMCPath.string()); std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc"; command("echo \"HashType=RSA-SHA256\" > " + hashFile); std::string manifestFile = extractPath.string() + "/" + "MANIFEST"; command( "echo \"purpose=xyz.openbmc_project.Software.Version.VersionPurpose.BMC\" > " + manifestFile); command("echo \"HashType=RSA-SHA256\" >> " + manifestFile); command("echo \"KeyType=OpenBMC\" >> " + manifestFile); std::string kernelFile = extractPath.string() + "/" + "image-kernel"; command("echo \"image-kernel file \" > " + kernelFile); std::string rofsFile = extractPath.string() + "/" + "image-rofs"; command("echo \"image-rofs file \" > " + rofsFile); std::string rwfsFile = extractPath.string() + "/" + "image-rwfs"; command("echo \"image-rwfs file \" > " + rwfsFile); std::string ubootFile = extractPath.string() + "/" + "image-u-boot"; command("echo \"image-u-boot file \" > " + ubootFile); std::string pkeyFile = extractPath.string() + "/" + "private.pem"; command("openssl genrsa -out " + pkeyFile + " 4096"); std::string pubkeyFile = extractPath.string() + "/" + "publickey"; command("openssl rsa -in " + pkeyFile + " -outform PEM " + "-pubout -out " + pubkeyFile); command("cp " + pubkeyFile + " " + signedConfOpenBMCPath.string()); command(opensslCmd + pkeyFile + " -out " + kernelFile + ".sig " + kernelFile); command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " + manifestFile); command(opensslCmd + pkeyFile + " -out " + rofsFile + ".sig " + rofsFile); command(opensslCmd + pkeyFile + " -out " + rwfsFile + ".sig " + rwfsFile); command(opensslCmd + pkeyFile + " -out " + ubootFile + ".sig " + ubootFile); command(opensslCmd + pkeyFile + " -out " + pubkeyFile + ".sig " + pubkeyFile); #ifdef WANT_SIGNATURE_VERIFY std::string fullFile = extractPath.string() + "/" + "image-full"; command("cat " + kernelFile + ".sig " + rofsFile + ".sig " + rwfsFile + ".sig " + ubootFile + ".sig " + manifestFile + ".sig " + pubkeyFile + ".sig > " + fullFile); command(opensslCmd + pkeyFile + " -out " + fullFile + ".sig " + fullFile); #endif signature = std::make_unique(extractPath, signedConfPath); } void TearDown() override { command("rm -rf " + std::string(testPath)); } std::unique_ptr signature; fs::path extractPath; fs::path signedConfPath; fs::path signedConfOpenBMCPath; }; /** @brief Test for success scenario*/ TEST_F(SignatureTest, TestSignatureVerify) { EXPECT_TRUE(signature->verify()); } /** @brief Test failure scenario with corrupted signature file*/ TEST_F(SignatureTest, TestCorruptSignatureFile) { // corrupt the image-kernel.sig file and ensure that verification fails std::string kernelFile = extractPath.string() + "/" + "image-kernel"; command("echo \"dummy data\" > " + kernelFile + ".sig "); EXPECT_FALSE(signature->verify()); } /** @brief Test failure scenario with no public key in the image*/ TEST_F(SignatureTest, TestNoPublicKeyInImage) { // Remove publickey file from the image and ensure that verify fails std::string pubkeyFile = extractPath.string() + "/" + "publickey"; command("rm " + pubkeyFile); EXPECT_FALSE(signature->verify()); } /** @brief Test failure scenario with invalid hash function value*/ TEST_F(SignatureTest, TestInvalidHashValue) { // Change the hashfunc value and ensure that verification fails std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc"; command("echo \"HashType=md5\" > " + hashFile); EXPECT_FALSE(signature->verify()); } /** @brief Test for failure scenario with no config file in system*/ TEST_F(SignatureTest, TestNoConfigFileInSystem) { // Remove the conf folder in the system and ensure that verify fails command("rm -rf " + signedConfOpenBMCPath.string()); EXPECT_FALSE(signature->verify()); } #ifdef WANT_SIGNATURE_VERIFY /** @brief Test for failure scenario without full verification */ TEST_F(SignatureTest, TestNoFullSignature) { // Remove the full signature and ensure that verify fails std::string fullFile = extractPath.string() + "/" + "image-full.sig"; command("rm " + fullFile); EXPECT_FALSE(signature->verify()); } /** @brief Test for failure scenario without full verification */ TEST_F(SignatureTest, TestNoFullSignatureForBIOS) { // Remove the full signature std::string fullFile = extractPath.string() + "/" + "image-full.sig"; command("rm " + fullFile); // Change the purpose to BIOS std::string manifestFile = extractPath.string() + "/" + "MANIFEST"; std::string pkeyFile = extractPath.string() + "/" + "private.pem"; command("sed -i s/VersionPurpose.BMC/VersionPurpose.BIOS/ " + manifestFile); command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " + manifestFile); // Re-create signature object and make sure verify succeed. signature = std::make_unique(extractPath, signedConfPath); EXPECT_TRUE(signature->verify()); } #endif class FileTest : public testing::Test { protected: static std::string readFile(const fs::path& path) { std::ifstream f(path, std::ios::in); if (!f.is_open()) { throw "Failed to open file"; } auto sz = static_cast(fs::file_size(path)); std::string result(static_cast(sz), '\0'); f.read(result.data(), sz); return result; } static void command(const std::string& cmd) { auto val = std::system(cmd.c_str()); if (val) { std::cout << "COMMAND Error: " << val << std::endl; } } void SetUp() override { // Create test base directory. tmpDir = fs::temp_directory_path() / "testFileXXXXXX"; if (!mkdtemp(tmpDir.data())) { throw "Failed to create tmp dir"; } std::string file1 = tmpDir + "/file1"; std::string file2 = tmpDir + "/file2"; command("echo \"File Test1\n\n\" > " + file1); command("echo \"FileTe st2\n\nte st2\" > " + file2); srcFiles.push_back(file1); srcFiles.push_back(file2); } void TearDown() override { fs::remove_all(tmpDir); } std::vector srcFiles; std::string tmpDir; }; TEST_F(FileTest, TestMergeFiles) { std::string retFile = tmpDir + "/retFile"; std::stringstream ss; ss << "cat"; for (const auto& file : srcFiles) { ss << " " << file; } ss << " >> " << retFile; std::string catCommand = ss.str(); command(catCommand); std::string dstFile = tmpDir + "/dstFile"; utils::mergeFiles(srcFiles, dstFile); ASSERT_NE(fs::file_size(retFile), static_cast(-1)); ASSERT_NE(fs::file_size(dstFile), static_cast(-1)); ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile)); std::string ssRetFile = readFile(fs::path(retFile)); std::string ssDstFile = readFile(fs::path(dstFile)); ASSERT_EQ(ssRetFile, ssDstFile); } TEST(ExecTest, TestConstructArgv) { auto name = "/bin/ls"; auto arg1 = "-a"; auto arg2 = "-l"; auto arg3 = "-t"; auto arg4 = "-rS"; auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4); char** charArray = argV.data(); EXPECT_EQ(argV.size(), 6); EXPECT_EQ(charArray[0], name); EXPECT_EQ(charArray[1], arg1); EXPECT_EQ(charArray[2], arg2); EXPECT_EQ(charArray[3], arg3); EXPECT_EQ(charArray[4], arg4); EXPECT_EQ(charArray[5], nullptr); name = "/usr/bin/du"; argV = utils::internal::constructArgv(name); charArray = argV.data(); EXPECT_EQ(argV.size(), 2); EXPECT_EQ(charArray[0], name); EXPECT_EQ(charArray[1], nullptr); name = "/usr/bin/hexdump"; arg1 = "-C"; arg2 = "/path/to/filename"; argV = utils::internal::constructArgv(name, arg1, arg2); charArray = argV.data(); EXPECT_EQ(argV.size(), 4); EXPECT_EQ(charArray[0], name); EXPECT_EQ(charArray[1], arg1); EXPECT_EQ(charArray[2], arg2); EXPECT_EQ(charArray[3], nullptr); }