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