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