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