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