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