xref: /openbmc/estoraged/src/estoraged.cpp (revision 15b63e12)
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