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