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