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