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