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