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