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