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