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