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