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