xref: /openbmc/phosphor-bmc-code-mgmt/bmc/test/utest.cpp (revision cab87e9cdeeb3e166d6d577511f6be4dc7721aca)
1 #include "config.h"
2 
3 #include "image_verify.hpp"
4 #include "utils.hpp"
5 #include "version.hpp"
6 
7 #include <openssl/evp.h>
8 #include <stdlib.h>
9 
10 #include <filesystem>
11 #include <fstream>
12 #include <iostream>
13 #include <sstream>
14 #include <string>
15 #include <vector>
16 
17 #include <gtest/gtest.h>
18 
19 using namespace phosphor::software::manager;
20 using namespace phosphor::software::image;
21 
22 namespace fs = std::filesystem;
23 
24 class VersionTest : public testing::Test
25 {
26   protected:
SetUp()27     void SetUp() override
28     {
29         char versionDir[] = "./versionXXXXXX";
30         _directory = mkdtemp(versionDir);
31 
32         if (_directory.empty())
33         {
34             throw std::bad_alloc();
35         }
36     }
37 
TearDown()38     void TearDown() override
39     {
40         fs::remove_all(_directory);
41     }
42 
43     std::string _directory;
44 };
45 
46 /** @brief Make sure we correctly get the version and purpose from getValue()*/
TEST_F(VersionTest,TestGetValue)47 TEST_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 
57     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 
TEST_F(VersionTest,TestGetRepeatedValue)65 TEST_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 
TEST_F(VersionTest,TestGetValueWithCRLF)84 TEST_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";
96     file.close();
97 
98     EXPECT_EQ(Version::getValue(manifestFilePath, "version"), version);
99     EXPECT_EQ(Version::getValue(manifestFilePath, "purpose"), purpose);
100 }
101 
TEST_F(VersionTest,TestGetVersionWithQuotes)102 TEST_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 
TEST_F(VersionTest,TestGetVersionWithoutQuotes)117 TEST_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 
132 /** @brief Make sure we correctly get the Id from getId()*/
TEST_F(VersionTest,TestGetId)133 TEST_F(VersionTest, TestGetId)
134 {
135     auto version = "test-id";
136     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++)
147     {
148         snprintf(&mdString[static_cast<size_t>(i) * 2], 3, "%02x",
149                  (unsigned int)digest[i]);
150     }
151     std::string hexId = std::string(mdString);
152     hexId = hexId.substr(0, 8);
153     EXPECT_EQ(Version::getId(version), hexId);
154 }
155 
TEST_F(VersionTest,TestGetExtendedVersion)156 TEST_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 
171 class SignatureTest : public testing::Test
172 {
173   public:
174     static constexpr auto opensslCmd = "openssl dgst -sha256 -sign ";
175     static constexpr auto testPath = "/tmp/_testSig";
176 
177   protected:
command(const std::string & cmd)178     static 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     }
SetUp()186     void SetUp() override
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";
221         command(
222             "echo \"purpose=xyz.openbmc_project.Software.Version.VersionPurpose.BMC\" > " +
223             manifestFile);
224         command("echo \"HashType=RSA-SHA256\" >> " + manifestFile);
225         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 + " 4096");
241 
242         std::string pubkeyFile = extractPath.string() + "/" + "publickey";
243         command("openssl rsa -in " + pkeyFile + " -outform PEM " +
244                 "-pubout -out " + pubkeyFile);
245 
246         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 
261 #ifdef WANT_SIGNATURE_VERIFY
262         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 
270         signature = std::make_unique<Signature>(extractPath, signedConfPath);
271     }
TearDown()272     void TearDown() override
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 
283 /** @brief Test for success scenario*/
TEST_F(SignatureTest,TestSignatureVerify)284 TEST_F(SignatureTest, TestSignatureVerify)
285 {
286     EXPECT_TRUE(signature->verify());
287 }
288 
289 /** @brief Test failure scenario with corrupted signature file*/
TEST_F(SignatureTest,TestCorruptSignatureFile)290 TEST_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*/
TEST_F(SignatureTest,TestNoPublicKeyInImage)299 TEST_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*/
TEST_F(SignatureTest,TestInvalidHashValue)308 TEST_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*/
TEST_F(SignatureTest,TestNoConfigFileInSystem)317 TEST_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 }
323 
324 #ifdef WANT_SIGNATURE_VERIFY
325 /** @brief Test for failure scenario without full verification */
TEST_F(SignatureTest,TestNoFullSignature)326 TEST_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 */
TEST_F(SignatureTest,TestNoFullSignatureForBIOS)335 TEST_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 
354 class FileTest : public testing::Test
355 {
356   protected:
readFile(const fs::path & path)357     static std::string readFile(const fs::path& path)
358     {
359         std::ifstream f(path, std::ios::in);
360         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');
367         f.read(result.data(), sz);
368 
369         return result;
370     }
371 
command(const std::string & cmd)372     static 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 
SetUp()381     void SetUp() override
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 
TearDown()399     void TearDown() override
400     {
401         fs::remove_all(tmpDir);
402     }
403 
404     std::vector<std::string> srcFiles;
405     std::string tmpDir;
406 };
407 
TEST_F(FileTest,TestMergeFiles)408 TEST_F(FileTest, TestMergeFiles)
409 {
410     std::string retFile = tmpDir + "/retFile";
411     std::stringstream ss;
412     ss << "cat";
413     for (const auto& file : srcFiles)
414     {
415         ss << " " << file;
416     }
417     ss << " >> " << retFile;
418     std::string catCommand = ss.str();
419     command(catCommand);
420 
421     std::string dstFile = tmpDir + "/dstFile";
422     utils::mergeFiles(srcFiles, dstFile);
423 
424     ASSERT_NE(fs::file_size(retFile), static_cast<uintmax_t>(-1));
425     ASSERT_NE(fs::file_size(dstFile), static_cast<uintmax_t>(-1));
426     ASSERT_EQ(fs::file_size(retFile), fs::file_size(dstFile));
427 
428     std::string ssRetFile = readFile(fs::path(retFile));
429     std::string ssDstFile = readFile(fs::path(dstFile));
430     ASSERT_EQ(ssRetFile, ssDstFile);
431 }
432 
TEST(ExecTest,TestConstructArgv)433 TEST(ExecTest, TestConstructArgv)
434 {
435     auto name = "/bin/ls";
436     auto arg1 = "-a";
437     auto arg2 = "-l";
438     auto arg3 = "-t";
439     auto arg4 = "-rS";
440     auto argV = utils::internal::constructArgv(name, arg1, arg2, arg3, arg4);
441     char** charArray = argV.data();
442     EXPECT_EQ(argV.size(), 6);
443     EXPECT_EQ(charArray[0], name);
444     EXPECT_EQ(charArray[1], arg1);
445     EXPECT_EQ(charArray[2], arg2);
446     EXPECT_EQ(charArray[3], arg3);
447     EXPECT_EQ(charArray[4], arg4);
448     EXPECT_EQ(charArray[5], nullptr);
449 
450     name = "/usr/bin/du";
451     argV = utils::internal::constructArgv(name);
452     charArray = argV.data();
453     EXPECT_EQ(argV.size(), 2);
454     EXPECT_EQ(charArray[0], name);
455     EXPECT_EQ(charArray[1], nullptr);
456 
457     name = "/usr/bin/hexdump";
458     arg1 = "-C";
459     arg2 = "/path/to/filename";
460     argV = utils::internal::constructArgv(name, arg1, arg2);
461     charArray = argV.data();
462     EXPECT_EQ(argV.size(), 4);
463     EXPECT_EQ(charArray[0], name);
464     EXPECT_EQ(charArray[1], arg1);
465     EXPECT_EQ(charArray[2], arg2);
466     EXPECT_EQ(charArray[3], nullptr);
467 }
468