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