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