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