blob: 03dbe26efe0f861cb3e254b39b7276fcc4483292 [file] [log] [blame]
Gunnar Millsb0ce9962018-09-07 13:39:10 -05001#include "image_verify.hpp"
George Liu0a06e972020-12-17 09:17:04 +08002#include "utils.hpp"
Gunnar Mills01d55c32017-04-20 10:52:15 -05003#include "version.hpp"
Gunnar Millsb0ce9962018-09-07 13:39:10 -05004
Patrick Williams0890ab92021-12-08 10:30:23 -06005#include <openssl/evp.h>
Gunnar Mills01d55c32017-04-20 10:52:15 -05006#include <stdlib.h>
Gunnar Millsb0ce9962018-09-07 13:39:10 -05007
Adriana Kobylakc98d9122020-05-05 10:36:01 -05008#include <filesystem>
Gunnar Mills01d55c32017-04-20 10:52:15 -05009#include <fstream>
10#include <iostream>
11#include <sstream>
12#include <string>
George Liu0a06e972020-12-17 09:17:04 +080013#include <vector>
Gunnar Millsb0ce9962018-09-07 13:39:10 -050014
15#include <gtest/gtest.h>
Gunnar Mills01d55c32017-04-20 10:52:15 -050016
17using namespace phosphor::software::manager;
Jayanth Othayoth6be275b2018-02-27 08:36:38 -060018using namespace phosphor::software::image;
Gunnar Mills01d55c32017-04-20 10:52:15 -050019
Gunnar Mills01d55c32017-04-20 10:52:15 -050020class VersionTest : public testing::Test
21{
Adriana Kobylak2285fe02018-02-27 15:36:59 -060022 protected:
23 virtual void SetUp()
24 {
25 char versionDir[] = "./versionXXXXXX";
26 _directory = mkdtemp(versionDir);
Gunnar Mills01d55c32017-04-20 10:52:15 -050027
Adriana Kobylak2285fe02018-02-27 15:36:59 -060028 if (_directory.empty())
Gunnar Mills01d55c32017-04-20 10:52:15 -050029 {
Adriana Kobylak2285fe02018-02-27 15:36:59 -060030 throw std::bad_alloc();
Gunnar Mills01d55c32017-04-20 10:52:15 -050031 }
Adriana Kobylak2285fe02018-02-27 15:36:59 -060032 }
Gunnar Mills01d55c32017-04-20 10:52:15 -050033
Adriana Kobylak2285fe02018-02-27 15:36:59 -060034 virtual void TearDown()
35 {
36 fs::remove_all(_directory);
37 }
Gunnar Mills01d55c32017-04-20 10:52:15 -050038
Adriana Kobylak2285fe02018-02-27 15:36:59 -060039 std::string _directory;
Gunnar Mills01d55c32017-04-20 10:52:15 -050040};
41
42/** @brief Make sure we correctly get the version and purpose from getValue()*/
43TEST_F(VersionTest, TestGetValue)
44{
45 auto manifestFilePath = _directory + "/" + "MANIFEST";
46 auto version = "test-version";
47 auto purpose = "BMC";
48
49 std::ofstream file;
50 file.open(manifestFilePath, std::ofstream::out);
51 ASSERT_TRUE(file.is_open());
52
Lei YU5a7363b2019-10-18 16:50:59 +080053 file << "version=" << version << "\n";
54 file << "purpose=" << purpose << "\n";
55 file.close();
56
57 EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
58 EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
59}
60
Justin Ledford054bb0b2022-03-15 15:46:58 -070061TEST_F(VersionTest, TestGetRepeatedValue)
62{
63 auto manifestFilePath = _directory + "/" + "MANIFEST";
64 const std::vector<std::string> names = {"foo.bar", "baz.bim"};
65
66 std::ofstream file;
67 file.open(manifestFilePath, std::ofstream::out);
68 ASSERT_TRUE(file.is_open());
69
70 for (const auto& name : names)
71 {
72 file << "CompatibleName=" << name << "\n";
73 }
74 file.close();
75
76 EXPECT_EQ(Version::getRepeatedValues(manifestFilePath, "CompatibleName"),
77 names);
78}
79
Lei YU5a7363b2019-10-18 16:50:59 +080080TEST_F(VersionTest, TestGetValueWithCRLF)
81{
82 auto manifestFilePath = _directory + "/" + "MANIFEST";
83 auto version = "test-version";
84 auto purpose = "BMC";
85
86 std::ofstream file;
87 file.open(manifestFilePath, std::ofstream::out);
88 ASSERT_TRUE(file.is_open());
89
90 file << "version=" << version << "\r\n";
91 file << "purpose=" << purpose << "\r\n";
Gunnar Mills01d55c32017-04-20 10:52:15 -050092 file.close();
93
94 EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
95 EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
96}
97
Adriana Kobylak2a3d9f52020-05-06 10:46:32 -050098TEST_F(VersionTest, TestGetVersionWithQuotes)
99{
100 auto releasePath = _directory + "/" + "os-release";
101 auto version = "1.2.3-test-version";
102
103 std::ofstream file;
104 file.open(releasePath, std::ofstream::out);
105 ASSERT_TRUE(file.is_open());
106
107 file << "VERSION_ID=\"" << version << "\"\n";
108 file.close();
109
110 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
111}
112
113TEST_F(VersionTest, TestGetVersionWithoutQuotes)
114{
115 auto releasePath = _directory + "/" + "os-release";
116 auto version = "9.88.1-test-version";
117
118 std::ofstream file;
119 file.open(releasePath, std::ofstream::out);
120 ASSERT_TRUE(file.is_open());
121
122 file << "VERSION_ID=" << version << "\n";
123 file.close();
124
125 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
126}
127
Gunnar Mills01d55c32017-04-20 10:52:15 -0500128/** @brief Make sure we correctly get the Id from getId()*/
129TEST_F(VersionTest, TestGetId)
130{
Gunnar Mills01d55c32017-04-20 10:52:15 -0500131 auto version = "test-id";
Patrick Williams0890ab92021-12-08 10:30:23 -0600132 unsigned char digest[EVP_MAX_MD_SIZE];
133 unsigned int digest_count = 0;
134
135 EVP_MD_CTX_Ptr ctx(EVP_MD_CTX_new(), &::EVP_MD_CTX_free);
136
137 EVP_DigestInit(ctx.get(), EVP_sha512());
138 EVP_DigestUpdate(ctx.get(), version, strlen(version));
139 EVP_DigestFinal(ctx.get(), digest, &digest_count);
140
141 char mdString[EVP_MAX_MD_SIZE * 2 + 1];
142 for (decltype(digest_count) i = 0; i < digest_count; i++)
Saqib Khan26a960d2017-09-19 14:23:28 -0500143 {
Adriana Kobylak2285fe02018-02-27 15:36:59 -0600144 snprintf(&mdString[i * 2], 3, "%02x", (unsigned int)digest[i]);
Saqib Khan26a960d2017-09-19 14:23:28 -0500145 }
146 std::string hexId = std::string(mdString);
147 hexId = hexId.substr(0, 8);
148 EXPECT_EQ(Version::getId(version), hexId);
Gunnar Mills01d55c32017-04-20 10:52:15 -0500149}
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600150
Chanh Nguyen1fd6ddd2021-01-06 11:09:09 +0700151TEST_F(VersionTest, TestGetExtendedVersion)
152{
153 auto releasePath = _directory + "/" + "os-release";
154 auto ExtendedVersion = "9.88.1-test-ExtendedVersion";
155
156 std::ofstream file;
157 file.open(releasePath, std::ofstream::out);
158 ASSERT_TRUE(file.is_open());
159
160 file << "EXTENDED_VERSION=" << ExtendedVersion << "\n";
161 file.close();
162
163 EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion);
164}
165
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600166class SignatureTest : public testing::Test
167{
168 static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
169 static constexpr auto testPath = "/tmp/_testSig";
170
171 protected:
172 void command(const std::string& cmd)
173 {
174 auto val = std::system(cmd.c_str());
175 if (val)
176 {
177 std::cout << "COMMAND Error: " << val << std::endl;
178 }
179 }
180 virtual void SetUp()
181 {
182 // Create test base directory.
183 fs::create_directories(testPath);
184
185 // Create unique temporary path for images
186 std::string tmpDir(testPath);
187 tmpDir += "/extractXXXXXX";
188 std::string imageDir = mkdtemp(const_cast<char*>(tmpDir.c_str()));
189
190 // Create unique temporary configuration path
191 std::string tmpConfDir(testPath);
192 tmpConfDir += "/confXXXXXX";
193 std::string confDir = mkdtemp(const_cast<char*>(tmpConfDir.c_str()));
194
195 extractPath = imageDir;
196 extractPath /= "images";
197
198 signedConfPath = confDir;
199 signedConfPath /= "conf";
200
201 signedConfOpenBMCPath = confDir;
202 signedConfOpenBMCPath /= "conf";
203 signedConfOpenBMCPath /= "OpenBMC";
204
205 std::cout << "SETUP " << std::endl;
206
207 command("mkdir " + extractPath.string());
208 command("mkdir " + signedConfPath.string());
209 command("mkdir " + signedConfOpenBMCPath.string());
210
211 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
212 command("echo \"HashType=RSA-SHA256\" > " + hashFile);
213
214 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
215 command("echo \"HashType=RSA-SHA256\" > " + manifestFile);
216 command("echo \"KeyType=OpenBMC\" >> " + manifestFile);
217
218 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
219 command("echo \"image-kernel file \" > " + kernelFile);
220
221 std::string rofsFile = extractPath.string() + "/" + "image-rofs";
222 command("echo \"image-rofs file \" > " + rofsFile);
223
224 std::string rwfsFile = extractPath.string() + "/" + "image-rwfs";
225 command("echo \"image-rwfs file \" > " + rwfsFile);
226
227 std::string ubootFile = extractPath.string() + "/" + "image-u-boot";
228 command("echo \"image-u-boot file \" > " + ubootFile);
229
230 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
231 command("openssl genrsa -out " + pkeyFile + " 2048");
232
233 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
234 command("openssl rsa -in " + pkeyFile + " -outform PEM " +
235 "-pubout -out " + pubkeyFile);
236
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600237 command("cp " + pubkeyFile + " " + signedConfOpenBMCPath.string());
238 command(opensslCmd + pkeyFile + " -out " + kernelFile + ".sig " +
239 kernelFile);
240
241 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
242 manifestFile);
243 command(opensslCmd + pkeyFile + " -out " + rofsFile + ".sig " +
244 rofsFile);
245 command(opensslCmd + pkeyFile + " -out " + rwfsFile + ".sig " +
246 rwfsFile);
247 command(opensslCmd + pkeyFile + " -out " + ubootFile + ".sig " +
248 ubootFile);
249 command(opensslCmd + pkeyFile + " -out " + pubkeyFile + ".sig " +
250 pubkeyFile);
251
George Liu1c8781f2021-08-26 16:09:18 +0800252#ifdef WANT_SIGNATURE_FULL_VERIFY
253 std::string fullFile = extractPath.string() + "/" + "image-full";
254 command("cat " + kernelFile + ".sig " + rofsFile + ".sig " + rwfsFile +
255 ".sig " + ubootFile + ".sig " + manifestFile + ".sig " +
256 pubkeyFile + ".sig > " + fullFile);
257 command(opensslCmd + pkeyFile + " -out " + fullFile + ".sig " +
258 fullFile);
259#endif
260
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600261 signature = std::make_unique<Signature>(extractPath, signedConfPath);
262 }
263 virtual void TearDown()
264 {
265 command("rm -rf " + std::string(testPath));
266 }
267
268 std::unique_ptr<Signature> signature;
269 fs::path extractPath;
270 fs::path signedConfPath;
271 fs::path signedConfOpenBMCPath;
272};
273
Gunnar Mills2bcba022018-04-08 15:02:04 -0500274/** @brief Test for success scenario*/
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600275TEST_F(SignatureTest, TestSignatureVerify)
276{
277 EXPECT_TRUE(signature->verify());
278}
279
280/** @brief Test failure scenario with corrupted signature file*/
281TEST_F(SignatureTest, TestCorruptSignatureFile)
282{
283 // corrupt the image-kernel.sig file and ensure that verification fails
284 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
285 command("echo \"dummy data\" > " + kernelFile + ".sig ");
286 EXPECT_FALSE(signature->verify());
287}
288
289/** @brief Test failure scenario with no public key in the image*/
290TEST_F(SignatureTest, TestNoPublicKeyInImage)
291{
292 // Remove publickey file from the image and ensure that verify fails
293 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
294 command("rm " + pubkeyFile);
295 EXPECT_FALSE(signature->verify());
296}
297
298/** @brief Test failure scenario with invalid hash function value*/
299TEST_F(SignatureTest, TestInvalidHashValue)
300{
301 // Change the hashfunc value and ensure that verification fails
302 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
303 command("echo \"HashType=md5\" > " + hashFile);
304 EXPECT_FALSE(signature->verify());
305}
306
307/** @brief Test for failure scenario with no config file in system*/
308TEST_F(SignatureTest, TestNoConfigFileInSystem)
309{
310 // Remove the conf folder in the system and ensure that verify fails
311 command("rm -rf " + signedConfOpenBMCPath.string());
312 EXPECT_FALSE(signature->verify());
313}
George Liu0a06e972020-12-17 09:17:04 +0800314
315class FileTest : public testing::Test
316{
317 protected:
318 std::string readFile(fs::path path)
319 {
320 std::ifstream f(path, std::ios::in);
321 const auto sz = fs::file_size(path);
322 std::string result(sz, '\0');
323 f.read(result.data(), sz);
324
325 return result;
326 }
327
328 void command(const std::string& cmd)
329 {
330 auto val = std::system(cmd.c_str());
331 if (val)
332 {
333 std::cout << "COMMAND Error: " << val << std::endl;
334 }
335 }
336
337 virtual void SetUp()
338 {
339 // Create test base directory.
340 tmpDir = fs::temp_directory_path() / "testFileXXXXXX";
341 if (!mkdtemp(tmpDir.data()))
342 {
343 throw "Failed to create tmp dir";
344 }
345
346 std::string file1 = tmpDir + "/file1";
347 std::string file2 = tmpDir + "/file2";
348 command("echo \"File Test1\n\n\" > " + file1);
349 command("echo \"FileTe st2\n\nte st2\" > " + file2);
350
351 srcFiles.push_back(file1);
352 srcFiles.push_back(file2);
353 }
354
355 virtual void TearDown()
356 {
357 fs::remove_all(tmpDir);
358 }
359
360 std::vector<std::string> srcFiles;
361 std::string tmpDir;
362};
363
364TEST_F(FileTest, TestMergeFiles)
365{
366 std::string retFile = tmpDir + "/retFile";
367 for (auto file : srcFiles)
368 {
369 command("cat " + file + " >> " + retFile);
370 }
371
372 std::string dstFile = tmpDir + "/dstFile";
373 utils::mergeFiles(srcFiles, dstFile);
374
375 ASSERT_NE(fs::file_size(retFile), static_cast<uintmax_t>(-1));
376 ASSERT_NE(fs::file_size(dstFile), static_cast<uintmax_t>(-1));
377 ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile));
378
379 std::string ssRetFile = readFile(fs::path(retFile));
380 std::string ssDstFile = readFile(fs::path(dstFile));
381 ASSERT_EQ(ssRetFile, ssDstFile);
Adriana Kobylak8a5ccbb2021-01-20 10:57:05 -0600382}
383
384TEST(ExecTest, TestConstructArgv)
385{
386 auto name = "/bin/ls";
387 auto arg1 = "-a";
388 auto arg2 = "-l";
389 auto arg3 = "-t";
390 auto arg4 = "-rS";
391 auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
392 char** charArray = argV.data();
393 EXPECT_EQ(argV.size(), 6);
394 EXPECT_EQ(charArray[0], name);
395 EXPECT_EQ(charArray[1], arg1);
396 EXPECT_EQ(charArray[2], arg2);
397 EXPECT_EQ(charArray[3], arg3);
398 EXPECT_EQ(charArray[4], arg4);
399 EXPECT_EQ(charArray[5], nullptr);
400
401 name = "/usr/bin/du";
402 argV = utils::internal::constructArgv(name);
403 charArray = argV.data();
404 EXPECT_EQ(argV.size(), 2);
405 EXPECT_EQ(charArray[0], name);
406 EXPECT_EQ(charArray[1], nullptr);
407
408 name = "/usr/bin/hexdump";
409 arg1 = "-C";
410 arg2 = "/path/to/filename";
411 argV = utils::internal::constructArgv(name, arg1, arg2);
412 charArray = argV.data();
413 EXPECT_EQ(argV.size(), 4);
414 EXPECT_EQ(charArray[0], name);
415 EXPECT_EQ(charArray[1], arg1);
416 EXPECT_EQ(charArray[2], arg2);
417 EXPECT_EQ(charArray[3], nullptr);
418}