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