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