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