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