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