1 #include "estoraged_test.hpp"
2 
3 #include "estoraged.hpp"
4 
5 #include <unistd.h>
6 
7 #include <boost/asio/io_context.hpp>
8 #include <sdbusplus/asio/connection.hpp>
9 #include <sdbusplus/asio/object_server.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11 #include <xyz/openbmc_project/Inventory/Item/Volume/server.hpp>
12 
13 #include <exception>
14 #include <filesystem>
15 #include <fstream>
16 #include <iterator>
17 #include <memory>
18 #include <string>
19 #include <vector>
20 
21 #include <gmock/gmock.h>
22 #include <gtest/gtest.h>
23 
24 namespace estoraged_test
25 {
26 
27 using sdbusplus::server::xyz::openbmc_project::inventory::item::Volume;
28 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
29 using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
30 using std::filesystem::path;
31 using ::testing::_;
32 using ::testing::Return;
33 using ::testing::StrEq;
34 
35 class EStoragedTest : public testing::Test
36 {
37   public:
38     const char* testFileName = "testfile";
39     const char* testLuksDevName = "testfile_luksDev";
40     const char* testCryptDir = "/tmp";
41     const std::string testConfigPath =
42         "/xyz/openbmc_project/inventory/system/board/test_board/test_emmc";
43     const uint64_t testSize = 24;
44     const uint8_t testLifeTime = 25;
45     const std::string testPartNumber = "12345678";
46     const std::string testSerialNumber = "ABCDEF1234";
47     const std::string testLocationCode = "U102020";
48     std::ofstream testFile;
49     std::string passwordString;
50     std::vector<uint8_t> password;
51     MockCryptsetupInterface* mockCryptIface{};
52     MockFilesystemInterface* mockFsIface{};
53     boost::asio::io_context io;
54     std::shared_ptr<sdbusplus::asio::connection> conn;
55     std::unique_ptr<sdbusplus::asio::object_server> objectServer;
56     std::unique_ptr<estoraged::EStoraged> esObject;
57 
58     EStoragedTest() :
59         passwordString("password"),
60         password(passwordString.begin(), passwordString.end())
61     {}
62 
63     void SetUp() override
64     {
65         /* Create an empty file that we'll pretend is a 'storage device'. */
66         testFile.open(testFileName,
67                       std::ios::out | std::ios::binary | std::ios::trunc);
68         testFile.close();
69         if (testFile.fail())
70         {
71             throw std::runtime_error("Failed to open test file");
72         }
73 
74         std::unique_ptr<MockCryptsetupInterface> cryptIface =
75             std::make_unique<MockCryptsetupInterface>();
76         mockCryptIface = cryptIface.get();
77         std::unique_ptr<MockFilesystemInterface> fsIface =
78             std::make_unique<MockFilesystemInterface>();
79         mockFsIface = fsIface.get();
80 
81         /* Set up location of dummy mapped crypt file. */
82         EXPECT_CALL(*cryptIface, cryptGetDir).WillOnce(Return(testCryptDir));
83 
84         conn = std::make_shared<sdbusplus::asio::connection>(io);
85         // request D-Bus server name.
86         conn->request_name("xyz.openbmc_project.eStoraged.test");
87         objectServer = std::make_unique<sdbusplus::asio::object_server>(conn);
88 
89         esObject = std::make_unique<estoraged::EStoraged>(
90             *objectServer, testConfigPath, testFileName, testLuksDevName,
91             testSize, testLifeTime, testPartNumber, testSerialNumber,
92             testLocationCode, std::move(cryptIface), std::move(fsIface));
93     }
94 
95     void TearDown() override
96     {
97         EXPECT_EQ(0, unlink(testFileName));
98     }
99 };
100 
101 const char* mappedDevicePath = "/tmp/testfile_luksDev";
102 std::ofstream mappedDevice;
103 
104 int createMappedDev()
105 {
106     mappedDevice.open(mappedDevicePath,
107                       std::ios::out | std::ios::binary | std::ios::trunc);
108     mappedDevice.close();
109     if (mappedDevice.fail())
110     {
111         throw std::runtime_error("Failed to open test mapped device");
112     }
113 
114     return 0;
115 }
116 
117 int removeMappedDev()
118 {
119     EXPECT_EQ(0, unlink(mappedDevicePath));
120 
121     return 0;
122 }
123 
124 /* Test case to format and then lock the LUKS device. */
125 TEST_F(EStoragedTest, FormatPass)
126 {
127     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
128 
129     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
130         .Times(1);
131 
132     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
133 
134     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
135         .WillOnce(&createMappedDev);
136 
137     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
138         .WillOnce(Return(0));
139 
140     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
141         .WillOnce(Return(false));
142 
143     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
144         .WillOnce(Return(true));
145 
146     EXPECT_CALL(*mockFsIface,
147                 doMount(StrEq(esObject->getCryptDevicePath()),
148                         StrEq(esObject->getMountPoint()), _, _, _))
149         .WillOnce(Return(0));
150 
151     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
152         .WillOnce(Return(0));
153 
154     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
155         .WillOnce(Return(true));
156 
157     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _))
158         .WillOnce(&removeMappedDev);
159 
160     /* Format the encrypted device. */
161     esObject->formatLuks(password, Volume::FilesystemType::ext4);
162     EXPECT_FALSE(esObject->isLocked());
163 
164     esObject->lock();
165     EXPECT_TRUE(esObject->isLocked());
166 }
167 
168 /*
169  * Test case where the mount point directory already exists, so it shouldn't
170  * try to create it.
171  */
172 TEST_F(EStoragedTest, MountPointExistsPass)
173 {
174     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
175 
176     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
177         .Times(1);
178 
179     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
180 
181     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
182         .WillOnce(&createMappedDev);
183 
184     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
185         .WillOnce(Return(0));
186 
187     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
188         .WillOnce(Return(true));
189 
190     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
191         .Times(0);
192 
193     EXPECT_CALL(*mockFsIface,
194                 doMount(StrEq(esObject->getCryptDevicePath()),
195                         StrEq(esObject->getMountPoint()), _, _, _))
196         .WillOnce(Return(0));
197 
198     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
199         .WillOnce(Return(0));
200 
201     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
202         .WillOnce(Return(true));
203 
204     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _))
205         .WillOnce(&removeMappedDev);
206 
207     /* Format the encrypted device. */
208     esObject->formatLuks(password, Volume::FilesystemType::ext4);
209     EXPECT_FALSE(esObject->isLocked());
210 
211     esObject->lock();
212     EXPECT_TRUE(esObject->isLocked());
213 }
214 
215 /* Test case where the device/file doesn't exist. */
216 TEST_F(EStoragedTest, FormatNoDeviceFail)
217 {
218     /* Delete the test file. */
219     EXPECT_EQ(0, unlink(testFileName));
220 
221     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
222                  ResourceNotFound);
223     EXPECT_TRUE(esObject->isLocked());
224 
225     /* Create the test file again, so that the TearDown function works. */
226     testFile.open(testFileName,
227                   std::ios::out | std::ios::binary | std::ios::trunc);
228     testFile.close();
229 }
230 
231 /* Test case where we fail to format the LUKS device. */
232 TEST_F(EStoragedTest, FormatFail)
233 {
234     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _))
235         .WillOnce(Return(-1));
236 
237     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
238                  InternalFailure);
239     EXPECT_TRUE(esObject->isLocked());
240 }
241 
242 /* Test case where we fail to set the password for the LUKS device. */
243 TEST_F(EStoragedTest, AddKeyslotFail)
244 {
245     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
246 
247     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
248         .WillOnce(Return(-1));
249 
250     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
251                  InternalFailure);
252     EXPECT_TRUE(esObject->isLocked());
253 }
254 
255 /* Test case where we fail to load the LUKS header. */
256 TEST_F(EStoragedTest, LoadLuksHeaderFail)
257 {
258     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
259 
260     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
261         .Times(1);
262 
263     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).WillOnce(Return(-1));
264 
265     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
266                  InternalFailure);
267     EXPECT_TRUE(esObject->isLocked());
268 }
269 
270 /* Test case where we fail to activate the LUKS device. */
271 TEST_F(EStoragedTest, ActivateFail)
272 {
273     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
274 
275     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
276         .Times(1);
277 
278     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
279 
280     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
281         .WillOnce(Return(-1));
282 
283     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
284                  InternalFailure);
285     EXPECT_TRUE(esObject->isLocked());
286 }
287 
288 /* Test case where we fail to create the filesystem. */
289 TEST_F(EStoragedTest, CreateFilesystemFail)
290 {
291     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
292 
293     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
294         .Times(1);
295 
296     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
297 
298     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
299         .WillOnce(&createMappedDev);
300 
301     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
302         .WillOnce(Return(-1));
303 
304     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
305                  InternalFailure);
306     EXPECT_FALSE(esObject->isLocked());
307 
308     EXPECT_EQ(0, removeMappedDev());
309 }
310 
311 /* Test case where we fail to create the mount point. */
312 TEST_F(EStoragedTest, CreateMountPointFail)
313 {
314     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
315 
316     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
317         .Times(1);
318 
319     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
320 
321     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
322         .WillOnce(&createMappedDev);
323 
324     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
325         .WillOnce(Return(0));
326 
327     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
328         .WillOnce(Return(false));
329 
330     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
331         .WillOnce(Return(false));
332 
333     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
334                  InternalFailure);
335     EXPECT_FALSE(esObject->isLocked());
336 
337     EXPECT_EQ(0, removeMappedDev());
338 }
339 
340 /* Test case where we fail to mount the filesystem. */
341 TEST_F(EStoragedTest, MountFail)
342 {
343     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
344 
345     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
346         .Times(1);
347 
348     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
349 
350     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
351         .WillOnce(&createMappedDev);
352 
353     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
354         .WillOnce(Return(0));
355 
356     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
357         .WillOnce(Return(false));
358 
359     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
360         .WillOnce(Return(true));
361 
362     EXPECT_CALL(*mockFsIface,
363                 doMount(StrEq(esObject->getCryptDevicePath()),
364                         StrEq(esObject->getMountPoint()), _, _, _))
365         .WillOnce(Return(-1));
366 
367     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
368         .WillOnce(Return(true));
369 
370     EXPECT_THROW(esObject->formatLuks(password, Volume::FilesystemType::ext4),
371                  InternalFailure);
372     EXPECT_FALSE(esObject->isLocked());
373 
374     EXPECT_EQ(0, removeMappedDev());
375 }
376 
377 /* Test case where we fail to unmount the filesystem. */
378 TEST_F(EStoragedTest, UnmountFail)
379 {
380     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
381 
382     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
383         .Times(1);
384 
385     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
386 
387     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
388         .WillOnce(&createMappedDev);
389 
390     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
391         .WillOnce(Return(0));
392 
393     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
394         .WillOnce(Return(false));
395 
396     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
397         .WillOnce(Return(true));
398 
399     EXPECT_CALL(*mockFsIface,
400                 doMount(StrEq(esObject->getCryptDevicePath()),
401                         StrEq(esObject->getMountPoint()), _, _, _))
402         .WillOnce(Return(0));
403 
404     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
405         .WillOnce(Return(-1));
406 
407     esObject->formatLuks(password, Volume::FilesystemType::ext4);
408     EXPECT_FALSE(esObject->isLocked());
409 
410     EXPECT_THROW(esObject->lock(), InternalFailure);
411     EXPECT_FALSE(esObject->isLocked());
412 
413     EXPECT_EQ(0, removeMappedDev());
414 }
415 
416 /* Test case where we fail to remove the mount point. */
417 TEST_F(EStoragedTest, RemoveMountPointFail)
418 {
419     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
420 
421     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
422         .Times(1);
423 
424     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
425 
426     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
427         .WillOnce(&createMappedDev);
428 
429     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
430         .WillOnce(Return(0));
431 
432     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
433         .WillOnce(Return(false));
434 
435     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
436         .WillOnce(Return(true));
437 
438     EXPECT_CALL(*mockFsIface,
439                 doMount(StrEq(esObject->getCryptDevicePath()),
440                         StrEq(esObject->getMountPoint()), _, _, _))
441         .WillOnce(Return(0));
442 
443     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
444         .WillOnce(Return(0));
445 
446     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
447         .WillOnce(Return(false));
448 
449     esObject->formatLuks(password, Volume::FilesystemType::ext4);
450     EXPECT_FALSE(esObject->isLocked());
451 
452     /* This will fail to remove the mount point. */
453     EXPECT_THROW(esObject->lock(), InternalFailure);
454     EXPECT_FALSE(esObject->isLocked());
455 
456     EXPECT_EQ(0, removeMappedDev());
457 }
458 
459 /* Test case where we fail to deactivate the LUKS device. */
460 TEST_F(EStoragedTest, DeactivateFail)
461 {
462     EXPECT_CALL(*mockCryptIface, cryptFormat(_, _, _, _, _, _, _, _)).Times(1);
463 
464     EXPECT_CALL(*mockCryptIface, cryptKeyslotAddByVolumeKey(_, _, _, _, _, _))
465         .Times(1);
466 
467     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
468 
469     EXPECT_CALL(*mockCryptIface, cryptActivateByPassphrase(_, _, _, _, _, _))
470         .WillOnce(&createMappedDev);
471 
472     EXPECT_CALL(*mockFsIface, runMkfs(StrEq(esObject->getCryptDevicePath())))
473         .WillOnce(Return(0));
474 
475     EXPECT_CALL(*mockFsIface, directoryExists(path(esObject->getMountPoint())))
476         .WillOnce(Return(false));
477 
478     EXPECT_CALL(*mockFsIface, createDirectory(path(esObject->getMountPoint())))
479         .WillOnce(Return(true));
480 
481     EXPECT_CALL(*mockFsIface,
482                 doMount(StrEq(esObject->getCryptDevicePath()),
483                         StrEq(esObject->getMountPoint()), _, _, _))
484         .WillOnce(Return(0));
485 
486     EXPECT_CALL(*mockFsIface, doUnmount(StrEq(esObject->getMountPoint())))
487         .WillOnce(Return(0));
488 
489     EXPECT_CALL(*mockFsIface, removeDirectory(path(esObject->getMountPoint())))
490         .WillOnce(Return(true));
491 
492     EXPECT_CALL(*mockCryptIface, cryptDeactivate(_, _)).WillOnce(Return(-1));
493 
494     /* Format the encrypted device. */
495     esObject->formatLuks(password, Volume::FilesystemType::ext4);
496     EXPECT_FALSE(esObject->isLocked());
497 
498     EXPECT_THROW(esObject->lock(), InternalFailure);
499     EXPECT_FALSE(esObject->isLocked());
500 
501     EXPECT_EQ(0, removeMappedDev());
502 }
503 
504 /* Test case where we successfully change the password. */
505 TEST_F(EStoragedTest, ChangePasswordSuccess)
506 {
507     std::string newPasswordString("newPassword");
508     std::vector<uint8_t> newPassword(newPasswordString.begin(),
509                                      newPasswordString.end());
510 
511     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
512 
513     EXPECT_CALL(*mockCryptIface,
514                 cryptKeyslotChangeByPassphrase(
515                     _, _, _, reinterpret_cast<const char*>(password.data()),
516                     password.size(),
517                     reinterpret_cast<const char*>(newPassword.data()),
518                     newPassword.size()))
519         .WillOnce(Return(0));
520 
521     /* Change the password for the LUKS-encrypted device. */
522     esObject->changePassword(password, newPassword);
523 }
524 
525 /* Test case where we fail to change the password. */
526 TEST_F(EStoragedTest, ChangePasswordFail)
527 {
528     std::string newPasswordString("newPassword");
529     std::vector<uint8_t> newPassword(newPasswordString.begin(),
530                                      newPasswordString.end());
531 
532     EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
533 
534     EXPECT_CALL(*mockCryptIface,
535                 cryptKeyslotChangeByPassphrase(
536                     _, _, _, reinterpret_cast<const char*>(password.data()),
537                     password.size(),
538                     reinterpret_cast<const char*>(newPassword.data()),
539                     newPassword.size()))
540         .WillOnce(Return(-1));
541 
542     EXPECT_THROW(esObject->changePassword(password, newPassword),
543                  InternalFailure);
544 }
545 
546 } // namespace estoraged_test
547