blob: 40f7ea83cefdcb59f7f12cefe7fc23059a2f71c1 [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
5#include <openssl/sha.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
61TEST_F(VersionTest, TestGetValueWithCRLF)
62{
63 auto manifestFilePath = _directory + "/" + "MANIFEST";
64 auto version = "test-version";
65 auto purpose = "BMC";
66
67 std::ofstream file;
68 file.open(manifestFilePath, std::ofstream::out);
69 ASSERT_TRUE(file.is_open());
70
71 file << "version=" << version << "\r\n";
72 file << "purpose=" << purpose << "\r\n";
Gunnar Mills01d55c32017-04-20 10:52:15 -050073 file.close();
74
75 EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
76 EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
77}
78
Adriana Kobylak2a3d9f52020-05-06 10:46:32 -050079TEST_F(VersionTest, TestGetVersionWithQuotes)
80{
81 auto releasePath = _directory + "/" + "os-release";
82 auto version = "1.2.3-test-version";
83
84 std::ofstream file;
85 file.open(releasePath, std::ofstream::out);
86 ASSERT_TRUE(file.is_open());
87
88 file << "VERSION_ID=\"" << version << "\"\n";
89 file.close();
90
91 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
92}
93
94TEST_F(VersionTest, TestGetVersionWithoutQuotes)
95{
96 auto releasePath = _directory + "/" + "os-release";
97 auto version = "9.88.1-test-version";
98
99 std::ofstream file;
100 file.open(releasePath, std::ofstream::out);
101 ASSERT_TRUE(file.is_open());
102
103 file << "VERSION_ID=" << version << "\n";
104 file.close();
105
106 EXPECT_EQ(Version::getBMCVersion(releasePath), version);
107}
108
Gunnar Mills01d55c32017-04-20 10:52:15 -0500109/** @brief Make sure we correctly get the Id from getId()*/
110TEST_F(VersionTest, TestGetId)
111{
Gunnar Mills01d55c32017-04-20 10:52:15 -0500112 auto version = "test-id";
Saqib Khan26a960d2017-09-19 14:23:28 -0500113 unsigned char digest[SHA512_DIGEST_LENGTH];
114 SHA512_CTX ctx;
115 SHA512_Init(&ctx);
116 SHA512_Update(&ctx, version, strlen(version));
117 SHA512_Final(digest, &ctx);
Adriana Kobylak2285fe02018-02-27 15:36:59 -0600118 char mdString[SHA512_DIGEST_LENGTH * 2 + 1];
Saqib Khan26a960d2017-09-19 14:23:28 -0500119 for (int i = 0; i < SHA512_DIGEST_LENGTH; i++)
120 {
Adriana Kobylak2285fe02018-02-27 15:36:59 -0600121 snprintf(&mdString[i * 2], 3, "%02x", (unsigned int)digest[i]);
Saqib Khan26a960d2017-09-19 14:23:28 -0500122 }
123 std::string hexId = std::string(mdString);
124 hexId = hexId.substr(0, 8);
125 EXPECT_EQ(Version::getId(version), hexId);
Gunnar Mills01d55c32017-04-20 10:52:15 -0500126}
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600127
Chanh Nguyen1fd6ddd2021-01-06 11:09:09 +0700128TEST_F(VersionTest, TestGetExtendedVersion)
129{
130 auto releasePath = _directory + "/" + "os-release";
131 auto ExtendedVersion = "9.88.1-test-ExtendedVersion";
132
133 std::ofstream file;
134 file.open(releasePath, std::ofstream::out);
135 ASSERT_TRUE(file.is_open());
136
137 file << "EXTENDED_VERSION=" << ExtendedVersion << "\n";
138 file.close();
139
140 EXPECT_EQ(Version::getBMCExtendedVersion(releasePath), ExtendedVersion);
141}
142
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600143class SignatureTest : public testing::Test
144{
145 static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
146 static constexpr auto testPath = "/tmp/_testSig";
147
148 protected:
149 void command(const std::string& cmd)
150 {
151 auto val = std::system(cmd.c_str());
152 if (val)
153 {
154 std::cout << "COMMAND Error: " << val << std::endl;
155 }
156 }
157 virtual void SetUp()
158 {
159 // Create test base directory.
160 fs::create_directories(testPath);
161
162 // Create unique temporary path for images
163 std::string tmpDir(testPath);
164 tmpDir += "/extractXXXXXX";
165 std::string imageDir = mkdtemp(const_cast<char*>(tmpDir.c_str()));
166
167 // Create unique temporary configuration path
168 std::string tmpConfDir(testPath);
169 tmpConfDir += "/confXXXXXX";
170 std::string confDir = mkdtemp(const_cast<char*>(tmpConfDir.c_str()));
171
172 extractPath = imageDir;
173 extractPath /= "images";
174
175 signedConfPath = confDir;
176 signedConfPath /= "conf";
177
178 signedConfOpenBMCPath = confDir;
179 signedConfOpenBMCPath /= "conf";
180 signedConfOpenBMCPath /= "OpenBMC";
181
182 std::cout << "SETUP " << std::endl;
183
184 command("mkdir " + extractPath.string());
185 command("mkdir " + signedConfPath.string());
186 command("mkdir " + signedConfOpenBMCPath.string());
187
188 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
189 command("echo \"HashType=RSA-SHA256\" > " + hashFile);
190
191 std::string manifestFile = extractPath.string() + "/" + "MANIFEST";
192 command("echo \"HashType=RSA-SHA256\" > " + manifestFile);
193 command("echo \"KeyType=OpenBMC\" >> " + manifestFile);
194
195 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
196 command("echo \"image-kernel file \" > " + kernelFile);
197
198 std::string rofsFile = extractPath.string() + "/" + "image-rofs";
199 command("echo \"image-rofs file \" > " + rofsFile);
200
201 std::string rwfsFile = extractPath.string() + "/" + "image-rwfs";
202 command("echo \"image-rwfs file \" > " + rwfsFile);
203
204 std::string ubootFile = extractPath.string() + "/" + "image-u-boot";
205 command("echo \"image-u-boot file \" > " + ubootFile);
206
207 std::string pkeyFile = extractPath.string() + "/" + "private.pem";
208 command("openssl genrsa -out " + pkeyFile + " 2048");
209
210 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
211 command("openssl rsa -in " + pkeyFile + " -outform PEM " +
212 "-pubout -out " + pubkeyFile);
213
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600214 command("cp " + pubkeyFile + " " + signedConfOpenBMCPath.string());
215 command(opensslCmd + pkeyFile + " -out " + kernelFile + ".sig " +
216 kernelFile);
217
218 command(opensslCmd + pkeyFile + " -out " + manifestFile + ".sig " +
219 manifestFile);
220 command(opensslCmd + pkeyFile + " -out " + rofsFile + ".sig " +
221 rofsFile);
222 command(opensslCmd + pkeyFile + " -out " + rwfsFile + ".sig " +
223 rwfsFile);
224 command(opensslCmd + pkeyFile + " -out " + ubootFile + ".sig " +
225 ubootFile);
226 command(opensslCmd + pkeyFile + " -out " + pubkeyFile + ".sig " +
227 pubkeyFile);
228
George Liu1c8781f2021-08-26 16:09:18 +0800229#ifdef WANT_SIGNATURE_FULL_VERIFY
230 std::string fullFile = extractPath.string() + "/" + "image-full";
231 command("cat " + kernelFile + ".sig " + rofsFile + ".sig " + rwfsFile +
232 ".sig " + ubootFile + ".sig " + manifestFile + ".sig " +
233 pubkeyFile + ".sig > " + fullFile);
234 command(opensslCmd + pkeyFile + " -out " + fullFile + ".sig " +
235 fullFile);
236#endif
237
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600238 signature = std::make_unique<Signature>(extractPath, signedConfPath);
239 }
240 virtual void TearDown()
241 {
242 command("rm -rf " + std::string(testPath));
243 }
244
245 std::unique_ptr<Signature> signature;
246 fs::path extractPath;
247 fs::path signedConfPath;
248 fs::path signedConfOpenBMCPath;
249};
250
Gunnar Mills2bcba022018-04-08 15:02:04 -0500251/** @brief Test for success scenario*/
Jayanth Othayoth6be275b2018-02-27 08:36:38 -0600252TEST_F(SignatureTest, TestSignatureVerify)
253{
254 EXPECT_TRUE(signature->verify());
255}
256
257/** @brief Test failure scenario with corrupted signature file*/
258TEST_F(SignatureTest, TestCorruptSignatureFile)
259{
260 // corrupt the image-kernel.sig file and ensure that verification fails
261 std::string kernelFile = extractPath.string() + "/" + "image-kernel";
262 command("echo \"dummy data\" > " + kernelFile + ".sig ");
263 EXPECT_FALSE(signature->verify());
264}
265
266/** @brief Test failure scenario with no public key in the image*/
267TEST_F(SignatureTest, TestNoPublicKeyInImage)
268{
269 // Remove publickey file from the image and ensure that verify fails
270 std::string pubkeyFile = extractPath.string() + "/" + "publickey";
271 command("rm " + pubkeyFile);
272 EXPECT_FALSE(signature->verify());
273}
274
275/** @brief Test failure scenario with invalid hash function value*/
276TEST_F(SignatureTest, TestInvalidHashValue)
277{
278 // Change the hashfunc value and ensure that verification fails
279 std::string hashFile = signedConfOpenBMCPath.string() + "/hashfunc";
280 command("echo \"HashType=md5\" > " + hashFile);
281 EXPECT_FALSE(signature->verify());
282}
283
284/** @brief Test for failure scenario with no config file in system*/
285TEST_F(SignatureTest, TestNoConfigFileInSystem)
286{
287 // Remove the conf folder in the system and ensure that verify fails
288 command("rm -rf " + signedConfOpenBMCPath.string());
289 EXPECT_FALSE(signature->verify());
290}
George Liu0a06e972020-12-17 09:17:04 +0800291
292class FileTest : public testing::Test
293{
294 protected:
295 std::string readFile(fs::path path)
296 {
297 std::ifstream f(path, std::ios::in);
298 const auto sz = fs::file_size(path);
299 std::string result(sz, '\0');
300 f.read(result.data(), sz);
301
302 return result;
303 }
304
305 void command(const std::string& cmd)
306 {
307 auto val = std::system(cmd.c_str());
308 if (val)
309 {
310 std::cout << "COMMAND Error: " << val << std::endl;
311 }
312 }
313
314 virtual void SetUp()
315 {
316 // Create test base directory.
317 tmpDir = fs::temp_directory_path() / "testFileXXXXXX";
318 if (!mkdtemp(tmpDir.data()))
319 {
320 throw "Failed to create tmp dir";
321 }
322
323 std::string file1 = tmpDir + "/file1";
324 std::string file2 = tmpDir + "/file2";
325 command("echo \"File Test1\n\n\" > " + file1);
326 command("echo \"FileTe st2\n\nte st2\" > " + file2);
327
328 srcFiles.push_back(file1);
329 srcFiles.push_back(file2);
330 }
331
332 virtual void TearDown()
333 {
334 fs::remove_all(tmpDir);
335 }
336
337 std::vector<std::string> srcFiles;
338 std::string tmpDir;
339};
340
341TEST_F(FileTest, TestMergeFiles)
342{
343 std::string retFile = tmpDir + "/retFile";
344 for (auto file : srcFiles)
345 {
346 command("cat " + file + " >> " + retFile);
347 }
348
349 std::string dstFile = tmpDir + "/dstFile";
350 utils::mergeFiles(srcFiles, dstFile);
351
352 ASSERT_NE(fs::file_size(retFile), static_cast<uintmax_t>(-1));
353 ASSERT_NE(fs::file_size(dstFile), static_cast<uintmax_t>(-1));
354 ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile));
355
356 std::string ssRetFile = readFile(fs::path(retFile));
357 std::string ssDstFile = readFile(fs::path(dstFile));
358 ASSERT_EQ(ssRetFile, ssDstFile);
Adriana Kobylak8a5ccbb2021-01-20 10:57:05 -0600359}
360
361TEST(ExecTest, TestConstructArgv)
362{
363 auto name = "/bin/ls";
364 auto arg1 = "-a";
365 auto arg2 = "-l";
366 auto arg3 = "-t";
367 auto arg4 = "-rS";
368 auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
369 char** charArray = argV.data();
370 EXPECT_EQ(argV.size(), 6);
371 EXPECT_EQ(charArray[0], name);
372 EXPECT_EQ(charArray[1], arg1);
373 EXPECT_EQ(charArray[2], arg2);
374 EXPECT_EQ(charArray[3], arg3);
375 EXPECT_EQ(charArray[4], arg4);
376 EXPECT_EQ(charArray[5], nullptr);
377
378 name = "/usr/bin/du";
379 argV = utils::internal::constructArgv(name);
380 charArray = argV.data();
381 EXPECT_EQ(argV.size(), 2);
382 EXPECT_EQ(charArray[0], name);
383 EXPECT_EQ(charArray[1], nullptr);
384
385 name = "/usr/bin/hexdump";
386 arg1 = "-C";
387 arg2 = "/path/to/filename";
388 argV = utils::internal::constructArgv(name, arg1, arg2);
389 charArray = argV.data();
390 EXPECT_EQ(argV.size(), 4);
391 EXPECT_EQ(charArray[0], name);
392 EXPECT_EQ(charArray[1], arg1);
393 EXPECT_EQ(charArray[2], arg2);
394 EXPECT_EQ(charArray[3], nullptr);
395}