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