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