blob: 8f400ffac696dce1c7c184b5f6b27774972be7a5 [file] [log] [blame]
Lei YU6173a072022-05-23 19:18:57 +08001#include "config.h"
2
Gunnar Millsb0ce9962018-09-07 13:39:10 -05003#include "image_verify.hpp"
George Liu0a06e972020-12-17 09:17:04 +08004#include "utils.hpp"
Gunnar Mills01d55c32017-04-20 10:52:15 -05005#include "version.hpp"
Gunnar Millsb0ce9962018-09-07 13:39:10 -05006
Patrick Williams0890ab92021-12-08 10:30:23 -06007#include <openssl/evp.h>
Gunnar Mills01d55c32017-04-20 10:52:15 -05008#include <stdlib.h>
Gunnar Millsb0ce9962018-09-07 13:39:10 -05009
Adriana Kobylakc98d9122020-05-05 10:36:01 -050010#include <filesystem>
Gunnar Mills01d55c32017-04-20 10:52:15 -050011#include <fstream>
12#include <iostream>
13#include <sstream>
14#include <string>
George Liu0a06e972020-12-17 09:17:04 +080015#include <vector>
Gunnar Millsb0ce9962018-09-07 13:39:10 -050016
17#include <gtest/gtest.h>
Gunnar Mills01d55c32017-04-20 10:52:15 -050018
19using namespace phosphor::software::manager;
Jayanth Othayoth6be275b2018-02-27 08:36:38 -060020using namespace phosphor::software::image;
Gunnar Mills01d55c32017-04-20 10:52:15 -050021
Pavithra Barithaya9de4b8c2024-06-22 01:04:37 -050022namespace fs = std::filesystem;
23
Gunnar Mills01d55c32017-04-20 10:52:15 -050024class VersionTest : public testing::Test
25{
Adriana Kobylak2285fe02018-02-27 15:36:59 -060026 protected:
27 virtual void SetUp()
28 {
29 char versionDir[] = "./versionXXXXXX";
30 _directory = mkdtemp(versionDir);
Gunnar Mills01d55c32017-04-20 10:52:15 -050031
Adriana Kobylak2285fe02018-02-27 15:36:59 -060032 if (_directory.empty())
Gunnar Mills01d55c32017-04-20 10:52:15 -050033 {
Adriana Kobylak2285fe02018-02-27 15:36:59 -060034 throw std::bad_alloc();
Gunnar Mills01d55c32017-04-20 10:52:15 -050035 }
Adriana Kobylak2285fe02018-02-27 15:36:59 -060036 }
Gunnar Mills01d55c32017-04-20 10:52:15 -050037
Adriana Kobylak2285fe02018-02-27 15:36:59 -060038 virtual void TearDown()
39 {
40 fs::remove_all(_directory);
41 }
Gunnar Mills01d55c32017-04-20 10:52:15 -050042
Adriana Kobylak2285fe02018-02-27 15:36:59 -060043 std::string _directory;
Gunnar Mills01d55c32017-04-20 10:52:15 -050044};
45
46/** @brief Make sure we correctly get the version and purpose from getValue()*/
47TEST_F(VersionTest, TestGetValue)
48{
49 auto manifestFilePath = _directory + "/" + "MANIFEST";
50 auto version = "test-version";
51 auto purpose = "BMC";
52
53 std::ofstream file;
54 file.open(manifestFilePath, std::ofstream::out);
55 ASSERT_TRUE(file.is_open());
56
Lei YU5a7363b2019-10-18 16:50:59 +080057 file << "version=" << version << "\n";
58 file << "purpose=" << purpose << "\n";
59 file.close();
60
61 EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
62 EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
63}
64
Justin Ledford054bb0b2022-03-15 15:46:58 -070065TEST_F(VersionTest, TestGetRepeatedValue)
66{
67 auto manifestFilePath = _directory + "/" + "MANIFEST";
68 const std::vector<std::string> names = {"foo.bar", "baz.bim"};
69
70 std::ofstream file;
71 file.open(manifestFilePath, std::ofstream::out);
72 ASSERT_TRUE(file.is_open());
73
74 for (const auto& name : names)
75 {
76 file << "CompatibleName=" << name << "\n";
77 }
78 file.close();
79
80 EXPECT_EQ(Version::getRepeatedValues(manifestFilePath, "CompatibleName"),
81 names);
82}
83
Lei YU5a7363b2019-10-18 16:50:59 +080084TEST_F(VersionTest, TestGetValueWithCRLF)
85{
86 auto manifestFilePath = _directory + "/" + "MANIFEST";
87 auto version = "test-version";
88 auto purpose = "BMC";
89
90 std::ofstream file;
91 file.open(manifestFilePath, std::ofstream::out);
92 ASSERT_TRUE(file.is_open());
93
94 file << "version=" << version << "\r\n";
95 file << "purpose=" << purpose << "\r\n";
Gunnar Mills01d55c32017-04-20 10:52:15 -050096 file.close();
97
98 EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
99 EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
100}
101
Adriana Kobylak2a3d9f52020-05-06 10:46:32 -0500102TEST_F(VersionTest, TestGetVersionWithQuotes)
103{
104 auto releasePath = _directory + "/" + "os-release";
105 auto version = "1.2.3-test-version";
106
107 std::ofstream file;
108 file.open(releasePath, std::ofstream::out);
109 ASSERT_TRUE(file.is_open());
110
111 file << "VERSION_ID=\"" << version << "\"\n";
112 file.close();
113
114 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
115}
116
117TEST_F(VersionTest, TestGetVersionWithoutQuotes)
118{
119 auto releasePath = _directory + "/" + "os-release";
120 auto version = "9.88.1-test-version";
121
122 std::ofstream file;
123 file.open(releasePath, std::ofstream::out);
124 ASSERT_TRUE(file.is_open());
125
126 file << "VERSION_ID=" << version << "\n";
127 file.close();
128
129 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
130}
131
Gunnar Mills01d55c32017-04-20 10:52:15 -0500132/** @brief Make sure we correctly get the Id from getId()*/
133TEST_F(VersionTest, TestGetId)
134{
Gunnar Mills01d55c32017-04-20 10:52:15 -0500135 auto version = "test-id";
Patrick Williams0890ab92021-12-08 10:30:23 -0600136 unsigned char digest[EVP_MAX_MD_SIZE];
137 unsigned int digest_count = 0;
138
139 EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
140
141 EVP_DigestInit(ctx.get(), EVP_sha512());
142 EVP_DigestUpdate(ctx.get(), version, strlen(version));
143 EVP_DigestFinal(ctx.get(), digest, &digest_count);
144
145 char mdString[EVP_MAX_MD_SIZE * 2 + 1];
146 for (decltype(digest_count) i = 0; i < digest_count; i++)
Saqib Khan26a960d2017-09-19 14:23:28 -0500147 {
Pavithra Barithaya5b2e89a2024-06-22 00:50:41 -0500148 snprintf(&mdString[static_cast<size_t>(i) * 2], 3, "%02x",
149 (unsigned int)digest[i]);
Saqib Khan26a960d2017-09-19 14:23:28 -0500150 }
151 std::string hexId = std::string(mdString);
152 hexId = hexId.substr(0, 8);
153 EXPECT_EQ(Version::getId(version), hexId);
Gunnar Mills01d55c32017-04-20 10:52:15 -0500154}
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600155
Chanh Nguyen1fd6ddd2021-01-06 11:09:09 +0700156TEST_F(VersionTest, TestGetExtendedVersion)
157{
158 auto releasePath = _directory + "/" + "os-release";
159 auto ExtendedVersion = "9.88.1-test-ExtendedVersion";
160
161 std::ofstream file;
162 file.open(releasePath, std::ofstream::out);
163 ASSERT_TRUE(file.is_open());
164
165 file << "EXTENDED_VERSION=" << ExtendedVersion << "\n";
166 file.close();
167
168 EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion);
169}
170
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600171class SignatureTest : public testing::Test
172{
Lei YU6173a072022-05-23 19:18:57 +0800173 public:
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600174 static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
175 static constexpr auto testPath = "/tmp/_testSig";
176
177 protected:
178 void command(const std::string& cmd)
179 {
180 auto val = std::system(cmd.c_str());
181 if (val)
182 {
183 std::cout << "COMMAND Error: " << val << std::endl;
184 }
185 }
186 virtual void SetUp()
187 {
188 // Create test base directory.
189 fs::create_directories(testPath);
190
191 // Create unique temporary path for images
192 std::string tmpDir(testPath);
193 tmpDir += "/extractXXXXXX";
194 std::string imageDir = mkdtemp(const_cast<char*>(tmpDir.c_str()));
195
196 // Create unique temporary configuration path
197 std::string tmpConfDir(testPath);
198 tmpConfDir += "/confXXXXXX";
199 std::string confDir = mkdtemp(const_cast<char*>(tmpConfDir.c_str()));
200
201 extractPath = imageDir;
202 extractPath /= "images";
203
204 signedConfPath = confDir;
205 signedConfPath /= "conf";
206
207 signedConfOpenBMCPath = confDir;
208 signedConfOpenBMCPath /= "conf";
209 signedConfOpenBMCPath /= "OpenBMC";
210
211 std::cout << "SETUP " << std::endl;
212
213 command("mkdir " + extractPath.string());
214 command("mkdir " + signedConfPath.string());
215 command("mkdir " + signedConfOpenBMCPath.string());
216
217 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
218 command("echo \"HashType=RSA-SHA256\" > " + hashFile);
219
220 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
Lei YU6173a072022-05-23 19:18:57 +0800221 command(
222 "echo \"purpose=xyz.openbmc_project.Software.Version.VersionPurpose.BMC\" > " +
223 manifestFile);
224 command("echo \"HashType=RSA-SHA256\" >> " + manifestFile);
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600225 command("echo \"KeyType=OpenBMC\" >> " + manifestFile);
226
227 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
228 command("echo \"image-kernel file \" > " + kernelFile);
229
230 std::string rofsFile = extractPath.string() + "/" + "image-rofs";
231 command("echo \"image-rofs file \" > " + rofsFile);
232
233 std::string rwfsFile = extractPath.string() + "/" + "image-rwfs";
234 command("echo \"image-rwfs file \" > " + rwfsFile);
235
236 std::string ubootFile = extractPath.string() + "/" + "image-u-boot";
237 command("echo \"image-u-boot file \" > " + ubootFile);
238
239 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
240 command("openssl genrsa -out " + pkeyFile + " 2048");
241
242 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
243 command("openssl rsa -in " + pkeyFile + " -outform PEM " +
244 "-pubout -out " + pubkeyFile);
245
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600246 command("cp " + pubkeyFile + " " + signedConfOpenBMCPath.string());
247 command(opensslCmd + pkeyFile + " -out " + kernelFile + ".sig " +
248 kernelFile);
249
250 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
251 manifestFile);
252 command(opensslCmd + pkeyFile + " -out " + rofsFile + ".sig " +
253 rofsFile);
254 command(opensslCmd + pkeyFile + " -out " + rwfsFile + ".sig " +
255 rwfsFile);
256 command(opensslCmd + pkeyFile + " -out " + ubootFile + ".sig " +
257 ubootFile);
258 command(opensslCmd + pkeyFile + " -out " + pubkeyFile + ".sig " +
259 pubkeyFile);
260
Konstantin Aladyshev294991a2023-04-19 15:24:20 +0300261#ifdef WANT_SIGNATURE_VERIFY
George Liu1c8781f2021-08-26 16:09:18 +0800262 std::string fullFile = extractPath.string() + "/" + "image-full";
263 command("cat " + kernelFile + ".sig " + rofsFile + ".sig " + rwfsFile +
264 ".sig " + ubootFile + ".sig " + manifestFile + ".sig " +
265 pubkeyFile + ".sig > " + fullFile);
266 command(opensslCmd + pkeyFile + " -out " + fullFile + ".sig " +
267 fullFile);
268#endif
269
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600270 signature = std::make_unique<Signature>(extractPath, signedConfPath);
271 }
272 virtual void TearDown()
273 {
274 command("rm -rf " + std::string(testPath));
275 }
276
277 std::unique_ptr<Signature> signature;
278 fs::path extractPath;
279 fs::path signedConfPath;
280 fs::path signedConfOpenBMCPath;
281};
282
Gunnar Mills2bcba022018-04-08 15:02:04 -0500283/** @brief Test for success scenario*/
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600284TEST_F(SignatureTest, TestSignatureVerify)
285{
286 EXPECT_TRUE(signature->verify());
287}
288
289/** @brief Test failure scenario with corrupted signature file*/
290TEST_F(SignatureTest, TestCorruptSignatureFile)
291{
292 // corrupt the image-kernel.sig file and ensure that verification fails
293 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
294 command("echo \"dummy data\" > " + kernelFile + ".sig ");
295 EXPECT_FALSE(signature->verify());
296}
297
298/** @brief Test failure scenario with no public key in the image*/
299TEST_F(SignatureTest, TestNoPublicKeyInImage)
300{
301 // Remove publickey file from the image and ensure that verify fails
302 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
303 command("rm " + pubkeyFile);
304 EXPECT_FALSE(signature->verify());
305}
306
307/** @brief Test failure scenario with invalid hash function value*/
308TEST_F(SignatureTest, TestInvalidHashValue)
309{
310 // Change the hashfunc value and ensure that verification fails
311 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
312 command("echo \"HashType=md5\" > " + hashFile);
313 EXPECT_FALSE(signature->verify());
314}
315
316/** @brief Test for failure scenario with no config file in system*/
317TEST_F(SignatureTest, TestNoConfigFileInSystem)
318{
319 // Remove the conf folder in the system and ensure that verify fails
320 command("rm -rf " + signedConfOpenBMCPath.string());
321 EXPECT_FALSE(signature->verify());
322}
George Liu0a06e972020-12-17 09:17:04 +0800323
Konstantin Aladyshev294991a2023-04-19 15:24:20 +0300324#ifdef WANT_SIGNATURE_VERIFY
Lei YU6173a072022-05-23 19:18:57 +0800325/** @brief Test for failure scenario without full verification */
326TEST_F(SignatureTest, TestNoFullSignature)
327{
328 // Remove the full signature and ensure that verify fails
329 std::string fullFile = extractPath.string() + "/" + "image-full.sig";
330 command("rm " + fullFile);
331 EXPECT_FALSE(signature->verify());
332}
333
334/** @brief Test for failure scenario without full verification */
335TEST_F(SignatureTest, TestNoFullSignatureForBIOS)
336{
337 // Remove the full signature
338 std::string fullFile = extractPath.string() + "/" + "image-full.sig";
339 command("rm " + fullFile);
340
341 // Change the purpose to BIOS
342 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
343 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
344 command("sed -i s/VersionPurpose.BMC/VersionPurpose.BIOS/ " + manifestFile);
345 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
346 manifestFile);
347
348 // Re-create signature object and make sure verify succeed.
349 signature = std::make_unique<Signature>(extractPath, signedConfPath);
350 EXPECT_TRUE(signature->verify());
351}
352#endif
353
George Liu0a06e972020-12-17 09:17:04 +0800354class FileTest : public testing::Test
355{
356 protected:
357 std::string readFile(fs::path path)
358 {
359 std::ifstream f(path, std::ios::in);
Pavithra Barithaya9de4b8c2024-06-22 01:04:37 -0500360 if (!f.is_open())
361 {
362 throw "Failed to open file";
363 }
364
365 auto sz = static_cast<std::streamsize>(fs::file_size(path));
366 std::string result(static_cast<size_t>(sz), '\0');
George Liu0a06e972020-12-17 09:17:04 +0800367 f.read(result.data(), sz);
368
369 return result;
370 }
371
372 void command(const std::string& cmd)
373 {
374 auto val = std::system(cmd.c_str());
375 if (val)
376 {
377 std::cout << "COMMAND Error: " << val << std::endl;
378 }
379 }
380
381 virtual void SetUp()
382 {
383 // Create test base directory.
384 tmpDir = fs::temp_directory_path() / "testFileXXXXXX";
385 if (!mkdtemp(tmpDir.data()))
386 {
387 throw "Failed to create tmp dir";
388 }
389
390 std::string file1 = tmpDir + "/file1";
391 std::string file2 = tmpDir + "/file2";
392 command("echo \"File Test1\n\n\" > " + file1);
393 command("echo \"FileTe st2\n\nte st2\" > " + file2);
394
395 srcFiles.push_back(file1);
396 srcFiles.push_back(file2);
397 }
398
399 virtual void TearDown()
400 {
401 fs::remove_all(tmpDir);
402 }
403
404 std::vector<std::string> srcFiles;
405 std::string tmpDir;
406};
407
408TEST_F(FileTest, TestMergeFiles)
409{
410 std::string retFile = tmpDir + "/retFile";
411 for (auto file : srcFiles)
412 {
413 command("cat " + file + " >> " + retFile);
414 }
415
416 std::string dstFile = tmpDir + "/dstFile";
417 utils::mergeFiles(srcFiles, dstFile);
418
419 ASSERT_NE(fs::file_size(retFile), static_cast<uintmax_t>(-1));
420 ASSERT_NE(fs::file_size(dstFile), static_cast<uintmax_t>(-1));
421 ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile));
422
423 std::string ssRetFile = readFile(fs::path(retFile));
424 std::string ssDstFile = readFile(fs::path(dstFile));
425 ASSERT_EQ(ssRetFile, ssDstFile);
Adriana Kobylak8a5ccbb2021-01-20 10:57:05 -0600426}
427
428TEST(ExecTest, TestConstructArgv)
429{
430 auto name = "/bin/ls";
431 auto arg1 = "-a";
432 auto arg2 = "-l";
433 auto arg3 = "-t";
434 auto arg4 = "-rS";
435 auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
436 char** charArray = argV.data();
437 EXPECT_EQ(argV.size(), 6);
438 EXPECT_EQ(charArray[0], name);
439 EXPECT_EQ(charArray[1], arg1);
440 EXPECT_EQ(charArray[2], arg2);
441 EXPECT_EQ(charArray[3], arg3);
442 EXPECT_EQ(charArray[4], arg4);
443 EXPECT_EQ(charArray[5], nullptr);
444
445 name = "/usr/bin/du";
446 argV = utils::internal::constructArgv(name);
447 charArray = argV.data();
448 EXPECT_EQ(argV.size(), 2);
449 EXPECT_EQ(charArray[0], name);
450 EXPECT_EQ(charArray[1], nullptr);
451
452 name = "/usr/bin/hexdump";
453 arg1 = "-C";
454 arg2 = "/path/to/filename";
455 argV = utils::internal::constructArgv(name, arg1, arg2);
456 charArray = argV.data();
457 EXPECT_EQ(argV.size(), 4);
458 EXPECT_EQ(charArray[0], name);
459 EXPECT_EQ(charArray[1], arg1);
460 EXPECT_EQ(charArray[2], arg2);
461 EXPECT_EQ(charArray[3], nullptr);
462}