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