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