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