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