blob: 279417ab7d605bb1853a4cd2a8c14ff535ea0520 [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 {
Adriana Kobylak2285fe02018-02-27 15:36:59 -0600146 snprintf(&mdString[i * 2], 3, "%02x", (unsigned int)digest[i]);
Saqib Khan26a960d2017-09-19 14:23:28 -0500147 }
148 std::string hexId = std::string(mdString);
149 hexId = hexId.substr(0, 8);
150 EXPECT_EQ(Version::getId(version), hexId);
Gunnar Mills01d55c32017-04-20 10:52:15 -0500151}
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600152
Chanh Nguyen1fd6ddd2021-01-06 11:09:09 +0700153TEST_F(VersionTest, TestGetExtendedVersion)
154{
155 auto releasePath = _directory + "/" + "os-release";
156 auto ExtendedVersion = "9.88.1-test-ExtendedVersion";
157
158 std::ofstream file;
159 file.open(releasePath, std::ofstream::out);
160 ASSERT_TRUE(file.is_open());
161
162 file << "EXTENDED_VERSION=" << ExtendedVersion << "\n";
163 file.close();
164
165 EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion);
166}
167
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600168class SignatureTest : public testing::Test
169{
Lei YU6173a072022-05-23 19:18:57 +0800170 public:
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600171 static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
172 static constexpr auto testPath = "/tmp/_testSig";
173
174 protected:
175 void command(const std::string& cmd)
176 {
177 auto val = std::system(cmd.c_str());
178 if (val)
179 {
180 std::cout << "COMMAND Error: " << val << std::endl;
181 }
182 }
183 virtual void SetUp()
184 {
185 // Create test base directory.
186 fs::create_directories(testPath);
187
188 // Create unique temporary path for images
189 std::string tmpDir(testPath);
190 tmpDir += "/extractXXXXXX";
191 std::string imageDir = mkdtemp(const_cast<char*>(tmpDir.c_str()));
192
193 // Create unique temporary configuration path
194 std::string tmpConfDir(testPath);
195 tmpConfDir += "/confXXXXXX";
196 std::string confDir = mkdtemp(const_cast<char*>(tmpConfDir.c_str()));
197
198 extractPath = imageDir;
199 extractPath /= "images";
200
201 signedConfPath = confDir;
202 signedConfPath /= "conf";
203
204 signedConfOpenBMCPath = confDir;
205 signedConfOpenBMCPath /= "conf";
206 signedConfOpenBMCPath /= "OpenBMC";
207
208 std::cout << "SETUP " << std::endl;
209
210 command("mkdir " + extractPath.string());
211 command("mkdir " + signedConfPath.string());
212 command("mkdir " + signedConfOpenBMCPath.string());
213
214 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
215 command("echo \"HashType=RSA-SHA256\" > " + hashFile);
216
217 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
Lei YU6173a072022-05-23 19:18:57 +0800218 command(
219 "echo \"purpose=xyz.openbmc_project.Software.Version.VersionPurpose.BMC\" > " +
220 manifestFile);
221 command("echo \"HashType=RSA-SHA256\" >> " + manifestFile);
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600222 command("echo \"KeyType=OpenBMC\" >> " + manifestFile);
223
224 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
225 command("echo \"image-kernel file \" > " + kernelFile);
226
227 std::string rofsFile = extractPath.string() + "/" + "image-rofs";
228 command("echo \"image-rofs file \" > " + rofsFile);
229
230 std::string rwfsFile = extractPath.string() + "/" + "image-rwfs";
231 command("echo \"image-rwfs file \" > " + rwfsFile);
232
233 std::string ubootFile = extractPath.string() + "/" + "image-u-boot";
234 command("echo \"image-u-boot file \" > " + ubootFile);
235
236 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
237 command("openssl genrsa -out " + pkeyFile + " 2048");
238
239 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
240 command("openssl rsa -in " + pkeyFile + " -outform PEM " +
241 "-pubout -out " + pubkeyFile);
242
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600243 command("cp " + pubkeyFile + " " + signedConfOpenBMCPath.string());
244 command(opensslCmd + pkeyFile + " -out " + kernelFile + ".sig " +
245 kernelFile);
246
247 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
248 manifestFile);
249 command(opensslCmd + pkeyFile + " -out " + rofsFile + ".sig " +
250 rofsFile);
251 command(opensslCmd + pkeyFile + " -out " + rwfsFile + ".sig " +
252 rwfsFile);
253 command(opensslCmd + pkeyFile + " -out " + ubootFile + ".sig " +
254 ubootFile);
255 command(opensslCmd + pkeyFile + " -out " + pubkeyFile + ".sig " +
256 pubkeyFile);
257
Konstantin Aladyshev294991a2023-04-19 15:24:20 +0300258#ifdef WANT_SIGNATURE_VERIFY
George Liu1c8781f2021-08-26 16:09:18 +0800259 std::string fullFile = extractPath.string() + "/" + "image-full";
260 command("cat " + kernelFile + ".sig " + rofsFile + ".sig " + rwfsFile +
261 ".sig " + ubootFile + ".sig " + manifestFile + ".sig " +
262 pubkeyFile + ".sig > " + fullFile);
263 command(opensslCmd + pkeyFile + " -out " + fullFile + ".sig " +
264 fullFile);
265#endif
266
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600267 signature = std::make_unique<Signature>(extractPath, signedConfPath);
268 }
269 virtual void TearDown()
270 {
271 command("rm -rf " + std::string(testPath));
272 }
273
274 std::unique_ptr<Signature> signature;
275 fs::path extractPath;
276 fs::path signedConfPath;
277 fs::path signedConfOpenBMCPath;
278};
279
Gunnar Mills2bcba022018-04-08 15:02:04 -0500280/** @brief Test for success scenario*/
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600281TEST_F(SignatureTest, TestSignatureVerify)
282{
283 EXPECT_TRUE(signature->verify());
284}
285
286/** @brief Test failure scenario with corrupted signature file*/
287TEST_F(SignatureTest, TestCorruptSignatureFile)
288{
289 // corrupt the image-kernel.sig file and ensure that verification fails
290 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
291 command("echo \"dummy data\" > " + kernelFile + ".sig ");
292 EXPECT_FALSE(signature->verify());
293}
294
295/** @brief Test failure scenario with no public key in the image*/
296TEST_F(SignatureTest, TestNoPublicKeyInImage)
297{
298 // Remove publickey file from the image and ensure that verify fails
299 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
300 command("rm " + pubkeyFile);
301 EXPECT_FALSE(signature->verify());
302}
303
304/** @brief Test failure scenario with invalid hash function value*/
305TEST_F(SignatureTest, TestInvalidHashValue)
306{
307 // Change the hashfunc value and ensure that verification fails
308 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
309 command("echo \"HashType=md5\" > " + hashFile);
310 EXPECT_FALSE(signature->verify());
311}
312
313/** @brief Test for failure scenario with no config file in system*/
314TEST_F(SignatureTest, TestNoConfigFileInSystem)
315{
316 // Remove the conf folder in the system and ensure that verify fails
317 command("rm -rf " + signedConfOpenBMCPath.string());
318 EXPECT_FALSE(signature->verify());
319}
George Liu0a06e972020-12-17 09:17:04 +0800320
Konstantin Aladyshev294991a2023-04-19 15:24:20 +0300321#ifdef WANT_SIGNATURE_VERIFY
Lei YU6173a072022-05-23 19:18:57 +0800322/** @brief Test for failure scenario without full verification */
323TEST_F(SignatureTest, TestNoFullSignature)
324{
325 // Remove the full signature and ensure that verify fails
326 std::string fullFile = extractPath.string() + "/" + "image-full.sig";
327 command("rm " + fullFile);
328 EXPECT_FALSE(signature->verify());
329}
330
331/** @brief Test for failure scenario without full verification */
332TEST_F(SignatureTest, TestNoFullSignatureForBIOS)
333{
334 // Remove the full signature
335 std::string fullFile = extractPath.string() + "/" + "image-full.sig";
336 command("rm " + fullFile);
337
338 // Change the purpose to BIOS
339 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
340 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
341 command("sed -i s/VersionPurpose.BMC/VersionPurpose.BIOS/ " + manifestFile);
342 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
343 manifestFile);
344
345 // Re-create signature object and make sure verify succeed.
346 signature = std::make_unique<Signature>(extractPath, signedConfPath);
347 EXPECT_TRUE(signature->verify());
348}
349#endif
350
George Liu0a06e972020-12-17 09:17:04 +0800351class FileTest : public testing::Test
352{
353 protected:
354 std::string readFile(fs::path path)
355 {
356 std::ifstream f(path, std::ios::in);
357 const auto sz = fs::file_size(path);
358 std::string result(sz, '\0');
359 f.read(result.data(), sz);
360
361 return result;
362 }
363
364 void command(const std::string& cmd)
365 {
366 auto val = std::system(cmd.c_str());
367 if (val)
368 {
369 std::cout << "COMMAND Error: " << val << std::endl;
370 }
371 }
372
373 virtual void SetUp()
374 {
375 // Create test base directory.
376 tmpDir = fs::temp_directory_path() / "testFileXXXXXX";
377 if (!mkdtemp(tmpDir.data()))
378 {
379 throw "Failed to create tmp dir";
380 }
381
382 std::string file1 = tmpDir + "/file1";
383 std::string file2 = tmpDir + "/file2";
384 command("echo \"File Test1\n\n\" > " + file1);
385 command("echo \"FileTe st2\n\nte st2\" > " + file2);
386
387 srcFiles.push_back(file1);
388 srcFiles.push_back(file2);
389 }
390
391 virtual void TearDown()
392 {
393 fs::remove_all(tmpDir);
394 }
395
396 std::vector<std::string> srcFiles;
397 std::string tmpDir;
398};
399
400TEST_F(FileTest, TestMergeFiles)
401{
402 std::string retFile = tmpDir + "/retFile";
403 for (auto file : srcFiles)
404 {
405 command("cat " + file + " >> " + retFile);
406 }
407
408 std::string dstFile = tmpDir + "/dstFile";
409 utils::mergeFiles(srcFiles, dstFile);
410
411 ASSERT_NE(fs::file_size(retFile), static_cast<uintmax_t>(-1));
412 ASSERT_NE(fs::file_size(dstFile), static_cast<uintmax_t>(-1));
413 ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile));
414
415 std::string ssRetFile = readFile(fs::path(retFile));
416 std::string ssDstFile = readFile(fs::path(dstFile));
417 ASSERT_EQ(ssRetFile, ssDstFile);
Adriana Kobylak8a5ccbb2021-01-20 10:57:05 -0600418}
419
420TEST(ExecTest, TestConstructArgv)
421{
422 auto name = "/bin/ls";
423 auto arg1 = "-a";
424 auto arg2 = "-l";
425 auto arg3 = "-t";
426 auto arg4 = "-rS";
427 auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
428 char** charArray = argV.data();
429 EXPECT_EQ(argV.size(), 6);
430 EXPECT_EQ(charArray[0], name);
431 EXPECT_EQ(charArray[1], arg1);
432 EXPECT_EQ(charArray[2], arg2);
433 EXPECT_EQ(charArray[3], arg3);
434 EXPECT_EQ(charArray[4], arg4);
435 EXPECT_EQ(charArray[5], nullptr);
436
437 name = "/usr/bin/du";
438 argV = utils::internal::constructArgv(name);
439 charArray = argV.data();
440 EXPECT_EQ(argV.size(), 2);
441 EXPECT_EQ(charArray[0], name);
442 EXPECT_EQ(charArray[1], nullptr);
443
444 name = "/usr/bin/hexdump";
445 arg1 = "-C";
446 arg2 = "/path/to/filename";
447 argV = utils::internal::constructArgv(name, arg1, arg2);
448 charArray = argV.data();
449 EXPECT_EQ(argV.size(), 4);
450 EXPECT_EQ(charArray[0], name);
451 EXPECT_EQ(charArray[1], arg1);
452 EXPECT_EQ(charArray[2], arg2);
453 EXPECT_EQ(charArray[3], nullptr);
454}