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