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