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