1
2 #include "estoraged.hpp"
3
4 #include "cryptErase.hpp"
5 #include "cryptsetupInterface.hpp"
6 #include "pattern.hpp"
7 #include "sanitize.hpp"
8 #include "verifyDriveGeometry.hpp"
9 #include "zero.hpp"
10
11 #include <libcryptsetup.h>
12 #include <openssl/rand.h>
13
14 #include <phosphor-logging/lg2.hpp>
15 #include <sdbusplus/asio/object_server.hpp>
16 #include <xyz/openbmc_project/Common/error.hpp>
17
18 #include <cstdlib>
19 #include <filesystem>
20 #include <iostream>
21 #include <string>
22 #include <string_view>
23 #include <utility>
24 #include <vector>
25
26 namespace estoraged
27 {
28
29 using Association = std::tuple<std::string, std::string, std::string>;
30 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
31 using sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest;
32 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Drive;
33 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
34
EStoraged(sdbusplus::asio::object_server & server,const std::string & configPath,const std::string & devPath,const std::string & luksName,uint64_t size,uint8_t lifeTime,const std::string & partNumber,const std::string & serialNumber,const std::string & locationCode,uint64_t eraseMaxGeometry,uint64_t eraseMinGeometry,const std::string & driveType,const std::string & driveProtocol,std::unique_ptr<CryptsetupInterface> cryptInterface,std::unique_ptr<FilesystemInterface> fsInterface)35 EStoraged::EStoraged(
36 sdbusplus::asio::object_server& server, const std::string& configPath,
37 const std::string& devPath, const std::string& luksName, uint64_t size,
38 uint8_t lifeTime, const std::string& partNumber,
39 const std::string& serialNumber, const std::string& locationCode,
40 uint64_t eraseMaxGeometry, uint64_t eraseMinGeometry,
41 const std::string& driveType, const std::string& driveProtocol,
42 std::unique_ptr<CryptsetupInterface> cryptInterface,
43 std::unique_ptr<FilesystemInterface> fsInterface) :
44 devPath(devPath), containerName(luksName),
45 mountPoint("/mnt/" + luksName + "_fs"), eraseMaxGeometry(eraseMaxGeometry),
46 eraseMinGeometry(eraseMinGeometry), cryptIface(std::move(cryptInterface)),
47 fsIface(std::move(fsInterface)),
48 cryptDevicePath(cryptIface->cryptGetDir() + "/" + luksName),
49 objectServer(server)
50 {
51 /* Get the filename of the device (without "/dev/"). */
52 std::string deviceName = std::filesystem::path(devPath).filename().string();
53 /* DBus object path */
54 std::string objectPath =
55 "/xyz/openbmc_project/inventory/storage/" + deviceName;
56
57 /* Add Volume interface. */
58 volumeInterface = objectServer.add_interface(
59 objectPath, "xyz.openbmc_project.Inventory.Item.Volume");
60 volumeInterface->register_method(
61 "FormatLuks", [this](const std::vector<uint8_t>& password,
62 Volume::FilesystemType type) {
63 this->formatLuks(password, type);
64 });
65 volumeInterface->register_method(
66 "Erase",
67 [this](Volume::EraseMethod eraseType) { this->erase(eraseType); });
68 volumeInterface->register_method("Lock", [this]() { this->lock(); });
69 volumeInterface->register_method(
70 "Unlock",
71 [this](std::vector<uint8_t>& password) { this->unlock(password); });
72 volumeInterface->register_method(
73 "ChangePassword", [this](const std::vector<uint8_t>& oldPassword,
74 const std::vector<uint8_t>& newPassword) {
75 this->changePassword(oldPassword, newPassword);
76 });
77 volumeInterface->register_property_r(
78 "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change,
79 [this](bool& value) {
80 value = this->isLocked();
81 return value;
82 });
83
84 /* Add Drive interface. */
85 driveInterface = objectServer.add_interface(
86 objectPath, "xyz.openbmc_project.Inventory.Item.Drive");
87 driveInterface->register_property("Capacity", size);
88 driveInterface->register_property("PredictedMediaLifeLeftPercent",
89 lifeTime);
90 driveInterface->register_property(
91 "Type",
92 "xyz.openbmc_project.Inventory.Item.Drive.DriveType." + driveType);
93 driveInterface->register_property(
94 "Protocol", "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol." +
95 driveProtocol);
96 /* This registers the Locked property for the Drives interface.
97 * Now it is the same as the volume Locked property */
98 driveInterface->register_property_r(
99 "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change,
100 [this](bool& value) {
101 value = this->isLocked();
102 return value;
103 });
104
105 driveInterface->register_property_r(
106 "EncryptionStatus", encryptionStatus,
107 sdbusplus::vtable::property_::emits_change,
108 [this](Drive::DriveEncryptionState& value) {
109 value = this->findEncryptionStatus();
110 return value;
111 });
112
113 embeddedLocationInterface = objectServer.add_interface(
114 objectPath, "xyz.openbmc_project.Inventory.Connector.Embedded");
115
116 if (!locationCode.empty())
117 {
118 locationCodeInterface = objectServer.add_interface(
119 objectPath, "xyz.openbmc_project.Inventory.Decorator.LocationCode");
120 locationCodeInterface->register_property("LocationCode", locationCode);
121 locationCodeInterface->initialize();
122 }
123
124 /* Add Asset interface. */
125 assetInterface = objectServer.add_interface(
126 objectPath, "xyz.openbmc_project.Inventory.Decorator.Asset");
127 assetInterface->register_property("PartNumber", partNumber);
128 assetInterface->register_property("SerialNumber", serialNumber);
129
130 volumeInterface->initialize();
131 driveInterface->initialize();
132 embeddedLocationInterface->initialize();
133 assetInterface->initialize();
134
135 /* Set up the association between chassis and drive. */
136 association = objectServer.add_interface(
137 objectPath, "xyz.openbmc_project.Association.Definitions");
138
139 std::vector<Association> associations;
140 associations.emplace_back("chassis", "drive",
141 std::filesystem::path(configPath).parent_path());
142 association->register_property("Associations", associations);
143 association->initialize();
144 }
145
~EStoraged()146 EStoraged::~EStoraged()
147 {
148 objectServer.remove_interface(volumeInterface);
149 objectServer.remove_interface(driveInterface);
150 objectServer.remove_interface(embeddedLocationInterface);
151 objectServer.remove_interface(assetInterface);
152 objectServer.remove_interface(association);
153
154 if (locationCodeInterface != nullptr)
155 {
156 objectServer.remove_interface(locationCodeInterface);
157 }
158 }
159
formatLuks(const std::vector<uint8_t> & password,Volume::FilesystemType type)160 void EStoraged::formatLuks(const std::vector<uint8_t>& password,
161 Volume::FilesystemType type)
162 {
163 std::string msg = "OpenBMC.0.1.DriveFormat";
164 lg2::info("Starting format", "REDFISH_MESSAGE_ID", msg);
165
166 if (type != Volume::FilesystemType::ext4)
167 {
168 lg2::error("Only ext4 filesystems are supported currently",
169 "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatFail"));
170 throw UnsupportedRequest();
171 }
172
173 formatLuksDev(password);
174 activateLuksDev(password);
175
176 createFilesystem();
177 mountFilesystem();
178 }
179
erase(Volume::EraseMethod inEraseMethod)180 void EStoraged::erase(Volume::EraseMethod inEraseMethod)
181 {
182 std::cerr << "Erasing encrypted eMMC" << std::endl;
183 lg2::info("Starting erase", "REDFISH_MESSAGE_ID",
184 std::string("OpenBMC.0.1.DriveErase"));
185 switch (inEraseMethod)
186 {
187 case Volume::EraseMethod::CryptoErase:
188 {
189 CryptErase myCryptErase(devPath);
190 myCryptErase.doErase();
191 break;
192 }
193 case Volume::EraseMethod::VerifyGeometry:
194 {
195 VerifyDriveGeometry myVerifyGeometry(devPath);
196 myVerifyGeometry.geometryOkay(eraseMaxGeometry, eraseMinGeometry);
197 break;
198 }
199 case Volume::EraseMethod::LogicalOverWrite:
200 {
201 Pattern myErasePattern(devPath);
202 myErasePattern.writePattern();
203 break;
204 }
205 case Volume::EraseMethod::LogicalVerify:
206 {
207 Pattern myErasePattern(devPath);
208 myErasePattern.verifyPattern();
209 break;
210 }
211 case Volume::EraseMethod::VendorSanitize:
212 {
213 Sanitize mySanitize(devPath);
214 mySanitize.doSanitize();
215 break;
216 }
217 case Volume::EraseMethod::ZeroOverWrite:
218 {
219 Zero myZero(devPath);
220 myZero.writeZero();
221 break;
222 }
223 case Volume::EraseMethod::ZeroVerify:
224 {
225 Zero myZero(devPath);
226 myZero.verifyZero();
227 break;
228 }
229 case Volume::EraseMethod::SecuredLocked:
230 {
231 if (!isLocked())
232 {
233 lock();
234 }
235 // TODO: implement hardware locking
236 // Until that is done, we can lock using eStoraged::lock()
237 break;
238 }
239 }
240 }
241
lock()242 void EStoraged::lock()
243 {
244 std::string msg = "OpenBMC.0.1.DriveLock";
245 lg2::info("Starting lock", "REDFISH_MESSAGE_ID", msg);
246
247 unmountFilesystem();
248 deactivateLuksDev();
249 }
250
unlock(std::vector<uint8_t> password)251 void EStoraged::unlock(std::vector<uint8_t> password)
252 {
253 std::string msg = "OpenBMC.0.1.DriveUnlock";
254 lg2::info("Starting unlock", "REDFISH_MESSAGE_ID", msg);
255
256 activateLuksDev(std::move(password));
257 mountFilesystem();
258 }
259
changePassword(const std::vector<uint8_t> & oldPassword,const std::vector<uint8_t> & newPassword)260 void EStoraged::changePassword(const std::vector<uint8_t>& oldPassword,
261 const std::vector<uint8_t>& newPassword)
262 {
263 lg2::info("Starting change password", "REDFISH_MESSAGE_ID",
264 std::string("OpenBMC.0.1.DrivePasswordChanged"));
265
266 CryptHandle cryptHandle = loadLuksHeader();
267
268 int retval = cryptIface->cryptKeyslotChangeByPassphrase(
269 cryptHandle.get(), CRYPT_ANY_SLOT, CRYPT_ANY_SLOT,
270 reinterpret_cast<const char*>(oldPassword.data()), oldPassword.size(),
271 reinterpret_cast<const char*>(newPassword.data()), newPassword.size());
272 if (retval < 0)
273 {
274 lg2::error("Failed to change password", "REDFISH_MESSAGE_ID",
275 std::string("OpenBMC.0.1.DrivePasswordChangeFail"));
276 throw InternalFailure();
277 }
278
279 lg2::info("Successfully changed password for {DEV}", "DEV", devPath,
280 "REDFISH_MESSAGE_ID",
281 std::string("OpenBMC.0.1.DrivePasswordChangeSuccess"));
282 }
283
isLocked() const284 bool EStoraged::isLocked() const
285 {
286 /*
287 * Check if the mapped virtual device exists. If it exists, the LUKS volume
288 * is unlocked.
289 */
290 try
291 {
292 std::filesystem::path mappedDevicePath(cryptDevicePath);
293 return (std::filesystem::exists(mappedDevicePath) == false);
294 }
295 catch (const std::exception& e)
296 {
297 lg2::error("Failed to query locked status: {EXCEPT}", "EXCEPT",
298 e.what(), "REDFISH_MESSAGE_ID",
299 std::string("OpenBMC.0.1.IsLockedFail"));
300 /* If we couldn't query the filesystem path, assume unlocked. */
301 return false;
302 }
303 }
304
getMountPoint() const305 std::string_view EStoraged::getMountPoint() const
306 {
307 return mountPoint;
308 }
309
formatLuksDev(std::vector<uint8_t> password)310 void EStoraged::formatLuksDev(std::vector<uint8_t> password)
311 {
312 lg2::info("Formatting device {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID",
313 std::string("OpenBMC.0.1.FormatLuksDev"));
314
315 /* Generate the volume key. */
316 const std::size_t keySize = 64;
317 std::vector<uint8_t> volumeKey(keySize);
318 if (RAND_bytes(volumeKey.data(), keySize) != 1)
319 {
320 lg2::error("Failed to create volume key", "REDFISH_MESSAGE_ID",
321 std::string("OpenBMC.0.1.FormatLuksDevFail"));
322 throw InternalFailure();
323 }
324
325 /* Create the handle. */
326 CryptHandle cryptHandle(devPath);
327
328 /* Format the LUKS encrypted device. */
329 int retval = cryptIface->cryptFormat(
330 cryptHandle.get(), CRYPT_LUKS2, "aes", "xts-plain64", nullptr,
331 reinterpret_cast<const char*>(volumeKey.data()), volumeKey.size(),
332 nullptr);
333 if (retval < 0)
334 {
335 lg2::error("Failed to format encrypted device: {RETVAL}", "RETVAL",
336 retval, "REDFISH_MESSAGE_ID",
337 std::string("OpenBMC.0.1.FormatLuksDevFail"));
338 throw InternalFailure();
339 }
340
341 /* Set the password. */
342 retval = cryptIface->cryptKeyslotAddByVolumeKey(
343 cryptHandle.get(), CRYPT_ANY_SLOT, nullptr, 0,
344 reinterpret_cast<const char*>(password.data()), password.size());
345
346 if (retval < 0)
347 {
348 lg2::error("Failed to set encryption password", "REDFISH_MESSAGE_ID",
349 std::string("OpenBMC.0.1.FormatLuksDevFail"));
350 throw InternalFailure();
351 }
352
353 lg2::info("Encrypted device {DEV} successfully formatted", "DEV", devPath,
354 "REDFISH_MESSAGE_ID",
355 std::string("OpenBMC.0.1.FormatLuksDevSuccess"));
356 }
357
loadLuksHeader()358 CryptHandle EStoraged::loadLuksHeader()
359 {
360 CryptHandle cryptHandle(devPath);
361
362 int retval = cryptIface->cryptLoad(cryptHandle.get(), CRYPT_LUKS2, nullptr);
363 if (retval < 0)
364 {
365 lg2::error("Failed to load LUKS header: {RETVAL}", "RETVAL", retval,
366 "REDFISH_MESSAGE_ID",
367 std::string("OpenBMC.0.1.ActivateLuksDevFail"));
368 throw InternalFailure();
369 }
370 return cryptHandle;
371 }
372
findEncryptionStatus()373 Drive::DriveEncryptionState EStoraged::findEncryptionStatus()
374 {
375 try
376 {
377 loadLuksHeader();
378 return Drive::DriveEncryptionState::Encrypted;
379 }
380 catch (...)
381 {
382 return Drive::DriveEncryptionState::Unencrypted;
383 }
384 }
385
activateLuksDev(std::vector<uint8_t> password)386 void EStoraged::activateLuksDev(std::vector<uint8_t> password)
387 {
388 lg2::info("Activating LUKS dev {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID",
389 std::string("OpenBMC.0.1.ActivateLuksDev"));
390
391 /* Create the handle. */
392 CryptHandle cryptHandle = loadLuksHeader();
393
394 int retval = cryptIface->cryptActivateByPassphrase(
395 cryptHandle.get(), containerName.c_str(), CRYPT_ANY_SLOT,
396 reinterpret_cast<const char*>(password.data()), password.size(), 0);
397
398 if (retval < 0)
399 {
400 lg2::error("Failed to activate LUKS dev: {RETVAL}", "RETVAL", retval,
401 "REDFISH_MESSAGE_ID",
402 std::string("OpenBMC.0.1.ActivateLuksDevFail"));
403 throw InternalFailure();
404 }
405
406 lg2::info("Successfully activated LUKS dev {DEV}", "DEV", devPath,
407 "REDFISH_MESSAGE_ID",
408 std::string("OpenBMC.0.1.ActivateLuksDevSuccess"));
409 }
410
createFilesystem()411 void EStoraged::createFilesystem()
412 {
413 /* Run the command to create the filesystem. */
414 int retval = fsIface->runMkfs(cryptDevicePath);
415 if (retval != 0)
416 {
417 lg2::error("Failed to create filesystem: {RETVAL}", "RETVAL", retval,
418 "REDFISH_MESSAGE_ID",
419 std::string("OpenBMC.0.1.CreateFilesystemFail"));
420 throw InternalFailure();
421 }
422 lg2::info("Successfully created filesystem for {CONTAINER}", "CONTAINER",
423 cryptDevicePath, "REDFISH_MESSAGE_ID",
424 std::string("OpenBMC.0.1.CreateFilesystemSuccess"));
425 }
426
mountFilesystem()427 void EStoraged::mountFilesystem()
428 {
429 /*
430 * Create directory for the filesystem, if it's not already present. It
431 * might already exist if, for example, the BMC reboots after creating the
432 * directory.
433 */
434 if (!fsIface->directoryExists(std::filesystem::path(mountPoint)))
435 {
436 bool success =
437 fsIface->createDirectory(std::filesystem::path(mountPoint));
438 if (!success)
439 {
440 lg2::error("Failed to create mount point: {DIR}", "DIR", mountPoint,
441 "REDFISH_MESSAGE_ID",
442 std::string("OpenBMC.0.1.MountFilesystemFail"));
443 throw InternalFailure();
444 }
445 }
446
447 /* Run the command to mount the filesystem. */
448 int retval = fsIface->doMount(cryptDevicePath.c_str(), mountPoint.c_str(),
449 "ext4", 0, nullptr);
450 if (retval != 0)
451 {
452 lg2::error("Failed to mount filesystem: {RETVAL}", "RETVAL", retval,
453 "REDFISH_MESSAGE_ID",
454 std::string("OpenBMC.0.1.MountFilesystemFail"));
455 bool removeSuccess =
456 fsIface->removeDirectory(std::filesystem::path(mountPoint));
457 if (!removeSuccess)
458 {
459 lg2::error("Failed to remove mount point: {DIR}", "DIR", mountPoint,
460 "REDFISH_MESSAGE_ID",
461 std::string("OpenBMC.0.1.MountFilesystemFail"));
462 }
463 throw InternalFailure();
464 }
465
466 lg2::info("Successfully mounted filesystem at {DIR}", "DIR", mountPoint,
467 "REDFISH_MESSAGE_ID",
468 std::string("OpenBMC.0.1.MountFilesystemSuccess"));
469 }
470
unmountFilesystem()471 void EStoraged::unmountFilesystem()
472 {
473 int retval = fsIface->doUnmount(mountPoint.c_str());
474 if (retval != 0)
475 {
476 lg2::error("Failed to unmount filesystem: {RETVAL}", "RETVAL", retval,
477 "REDFISH_MESSAGE_ID",
478 std::string("OpenBMC.0.1.UnmountFilesystemFail"));
479 throw InternalFailure();
480 }
481
482 /* Remove the mount point. */
483 bool success = fsIface->removeDirectory(std::filesystem::path(mountPoint));
484 if (!success)
485 {
486 lg2::error("Failed to remove mount point {DIR}", "DIR", mountPoint,
487 "REDFISH_MESSAGE_ID",
488 std::string("OpenBMC.0.1.UnmountFilesystemFail"));
489 throw InternalFailure();
490 }
491
492 lg2::info("Successfully unmounted filesystem at {DIR}", "DIR", mountPoint,
493 "REDFISH_MESSAGE_ID",
494 std::string("OpenBMC.0.1.MountFilesystemSuccess"));
495 }
496
deactivateLuksDev()497 void EStoraged::deactivateLuksDev()
498 {
499 lg2::info("Deactivating LUKS device {DEV}", "DEV", devPath,
500 "REDFISH_MESSAGE_ID",
501 std::string("OpenBMC.0.1.DeactivateLuksDev"));
502
503 int retval = cryptIface->cryptDeactivate(nullptr, containerName.c_str());
504 if (retval < 0)
505 {
506 lg2::error("Failed to deactivate crypt device: {RETVAL}", "RETVAL",
507 retval, "REDFISH_MESSAGE_ID",
508 std::string("OpenBMC.0.1.DeactivateLuksDevFail"));
509 throw InternalFailure();
510 }
511
512 lg2::info("Successfully deactivated LUKS device {DEV}", "DEV", devPath,
513 "REDFISH_MESSAGE_ID",
514 std::string("OpenBMC.0.1.DeactivateLuksDevSuccess"));
515 }
516
getCryptDevicePath() const517 std::string_view EStoraged::getCryptDevicePath() const
518 {
519 return cryptDevicePath;
520 }
521
522 } // namespace estoraged
523