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