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