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