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