#include "estoraged.hpp" #include "cryptErase.hpp" #include "cryptsetupInterface.hpp" #include "pattern.hpp" #include "sanitize.hpp" #include "verifyDriveGeometry.hpp" #include "zero.hpp" #include #include #include #include #include #include #include #include #include #include #include #include namespace estoraged { using Association = std::tuple; using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using sdbusplus::xyz::openbmc_project::Common::Error::UnsupportedRequest; using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Drive; using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume; EStoraged::EStoraged(sdbusplus::asio::object_server& server, const std::string& configPath, const std::string& devPath, const std::string& luksName, uint64_t size, uint8_t lifeTime, const std::string& partNumber, const std::string& serialNumber, std::unique_ptr cryptInterface, std::unique_ptr fsInterface) : devPath(devPath), containerName(luksName), mountPoint("/mnt/" + luksName + "_fs"), cryptIface(std::move(cryptInterface)), fsIface(std::move(fsInterface)), cryptDevicePath(cryptIface->cryptGetDir() + "/" + luksName), objectServer(server) { /* Get the filename of the device (without "/dev/"). */ std::string deviceName = std::filesystem::path(devPath).filename().string(); /* DBus object path */ std::string objectPath = "/xyz/openbmc_project/inventory/storage/" + deviceName; /* Add Volume interface. */ volumeInterface = objectServer.add_interface( objectPath, "xyz.openbmc_project.Inventory.Item.Volume"); volumeInterface->register_method( "FormatLuks", [this](const std::vector& password, Volume::FilesystemType type) { this->formatLuks(password, type); }); volumeInterface->register_method( "Erase", [this](Volume::EraseMethod eraseType) { this->erase(eraseType); }); volumeInterface->register_method("Lock", [this]() { this->lock(); }); volumeInterface->register_method( "Unlock", [this](std::vector& password) { this->unlock(password); }); volumeInterface->register_method( "ChangePassword", [this](const std::vector& oldPassword, const std::vector& newPassword) { this->changePassword(oldPassword, newPassword); }); volumeInterface->register_property_r( "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change, [this](bool& value) { value = this->isLocked(); return value; }); /* Add Drive interface. */ driveInterface = objectServer.add_interface( objectPath, "xyz.openbmc_project.Inventory.Item.Drive"); driveInterface->register_property("Capacity", size); driveInterface->register_property("PredictedMediaLifeLeftPercent", lifeTime); /* This registers the Locked property for the Drives interface. * Now it is the same as the volume Locked property */ driveInterface->register_property_r( "Locked", lockedProperty, sdbusplus::vtable::property_::emits_change, [this](bool& value) { value = this->isLocked(); return value; }); driveInterface->register_property_r( "EncryptionStatus", encryptionStatus, sdbusplus::vtable::property_::emits_change, [this](Drive::DriveEncryptionState& value) { value = this->findEncryptionStatus(); return value; }); embeddedLocationInterface = objectServer.add_interface( objectPath, "xyz.openbmc_project.Inventory.Connector.Embedded"); /* Add Asset interface. */ assetInterface = objectServer.add_interface( objectPath, "xyz.openbmc_project.Inventory.Decorator.Asset"); assetInterface->register_property("PartNumber", partNumber); assetInterface->register_property("SerialNumber", serialNumber); volumeInterface->initialize(); driveInterface->initialize(); embeddedLocationInterface->initialize(); assetInterface->initialize(); /* Set up the association between chassis and drive. */ association = objectServer.add_interface( objectPath, "xyz.openbmc_project.Association.Definitions"); std::vector associations; associations.emplace_back("chassis", "drive", std::filesystem::path(configPath).parent_path()); association->register_property("Associations", associations); association->initialize(); } EStoraged::~EStoraged() { objectServer.remove_interface(volumeInterface); objectServer.remove_interface(driveInterface); objectServer.remove_interface(embeddedLocationInterface); objectServer.remove_interface(assetInterface); objectServer.remove_interface(association); } void EStoraged::formatLuks(const std::vector& password, Volume::FilesystemType type) { std::string msg = "OpenBMC.0.1.DriveFormat"; lg2::info("Starting format", "REDFISH_MESSAGE_ID", msg); if (type != Volume::FilesystemType::ext4) { lg2::error("Only ext4 filesystems are supported currently", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatFail")); throw UnsupportedRequest(); } formatLuksDev(password); activateLuksDev(password); createFilesystem(); mountFilesystem(); } void EStoraged::erase(Volume::EraseMethod inEraseMethod) { std::cerr << "Erasing encrypted eMMC" << std::endl; lg2::info("Starting erase", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DriveErase")); switch (inEraseMethod) { case Volume::EraseMethod::CryptoErase: { CryptErase myCryptErase(devPath); myCryptErase.doErase(); break; } case Volume::EraseMethod::VerifyGeometry: { VerifyDriveGeometry myVerifyGeometry(devPath); myVerifyGeometry.geometryOkay(); break; } case Volume::EraseMethod::LogicalOverWrite: { Pattern myErasePattern(devPath); myErasePattern.writePattern(); break; } case Volume::EraseMethod::LogicalVerify: { Pattern myErasePattern(devPath); myErasePattern.verifyPattern(); break; } case Volume::EraseMethod::VendorSanitize: { Sanitize mySanitize(devPath); mySanitize.doSanitize(); break; } case Volume::EraseMethod::ZeroOverWrite: { Zero myZero(devPath); myZero.writeZero(); break; } case Volume::EraseMethod::ZeroVerify: { Zero myZero(devPath); myZero.verifyZero(); break; } case Volume::EraseMethod::SecuredLocked: { if (!isLocked()) { lock(); } // TODO: implement hardware locking // Until that is done, we can lock using eStoraged::lock() break; } } } void EStoraged::lock() { std::string msg = "OpenBMC.0.1.DriveLock"; lg2::info("Starting lock", "REDFISH_MESSAGE_ID", msg); unmountFilesystem(); deactivateLuksDev(); } void EStoraged::unlock(std::vector password) { std::string msg = "OpenBMC.0.1.DriveUnlock"; lg2::info("Starting unlock", "REDFISH_MESSAGE_ID", msg); activateLuksDev(std::move(password)); mountFilesystem(); } void EStoraged::changePassword(const std::vector& oldPassword, const std::vector& newPassword) { lg2::info("Starting change password", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DrivePasswordChanged")); CryptHandle cryptHandle = loadLuksHeader(); int retval = cryptIface->cryptKeyslotChangeByPassphrase( cryptHandle.get(), CRYPT_ANY_SLOT, CRYPT_ANY_SLOT, reinterpret_cast(oldPassword.data()), oldPassword.size(), reinterpret_cast(newPassword.data()), newPassword.size()); if (retval < 0) { lg2::error("Failed to change password", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DrivePasswordChangeFail")); throw InternalFailure(); } lg2::info("Successfully changed password for {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DrivePasswordChangeSuccess")); } bool EStoraged::isLocked() const { /* * Check if the mapped virtual device exists. If it exists, the LUKS volume * is unlocked. */ try { std::filesystem::path mappedDevicePath(cryptDevicePath); return (std::filesystem::exists(mappedDevicePath) == false); } catch (const std::exception& e) { lg2::error("Failed to query locked status: {EXCEPT}", "EXCEPT", e.what(), "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.IsLockedFail")); /* If we couldn't query the filesystem path, assume unlocked. */ return false; } } std::string_view EStoraged::getMountPoint() const { return mountPoint; } void EStoraged::formatLuksDev(std::vector password) { lg2::info("Formatting device {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatLuksDev")); /* Generate the volume key. */ const std::size_t keySize = 64; std::vector volumeKey(keySize); if (RAND_bytes(volumeKey.data(), keySize) != 1) { lg2::error("Failed to create volume key", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatLuksDevFail")); throw InternalFailure(); } /* Create the handle. */ CryptHandle cryptHandle(devPath); /* Format the LUKS encrypted device. */ int retval = cryptIface->cryptFormat( cryptHandle.get(), CRYPT_LUKS2, "aes", "xts-plain64", nullptr, reinterpret_cast(volumeKey.data()), volumeKey.size(), nullptr); if (retval < 0) { lg2::error("Failed to format encrypted device: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatLuksDevFail")); throw InternalFailure(); } /* Set the password. */ retval = cryptIface->cryptKeyslotAddByVolumeKey( cryptHandle.get(), CRYPT_ANY_SLOT, nullptr, 0, reinterpret_cast(password.data()), password.size()); if (retval < 0) { lg2::error("Failed to set encryption password", "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatLuksDevFail")); throw InternalFailure(); } lg2::info("Encrypted device {DEV} successfully formatted", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.FormatLuksDevSuccess")); } CryptHandle EStoraged::loadLuksHeader() { CryptHandle cryptHandle(devPath); int retval = cryptIface->cryptLoad(cryptHandle.get(), CRYPT_LUKS2, nullptr); if (retval < 0) { lg2::error("Failed to load LUKS header: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.ActivateLuksDevFail")); throw InternalFailure(); } return cryptHandle; } Drive::DriveEncryptionState EStoraged::findEncryptionStatus() { try { loadLuksHeader(); return Drive::DriveEncryptionState::Encrypted; } catch (...) { return Drive::DriveEncryptionState::Unknown; } } void EStoraged::activateLuksDev(std::vector password) { lg2::info("Activating LUKS dev {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.ActivateLuksDev")); /* Create the handle. */ CryptHandle cryptHandle = loadLuksHeader(); int retval = cryptIface->cryptActivateByPassphrase( cryptHandle.get(), containerName.c_str(), CRYPT_ANY_SLOT, reinterpret_cast(password.data()), password.size(), 0); if (retval < 0) { lg2::error("Failed to activate LUKS dev: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.ActivateLuksDevFail")); throw InternalFailure(); } lg2::info("Successfully activated LUKS dev {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.ActivateLuksDevSuccess")); } void EStoraged::createFilesystem() { /* Run the command to create the filesystem. */ int retval = fsIface->runMkfs(cryptDevicePath); if (retval != 0) { lg2::error("Failed to create filesystem: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.CreateFilesystemFail")); throw InternalFailure(); } lg2::info("Successfully created filesystem for {CONTAINER}", "CONTAINER", cryptDevicePath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.CreateFilesystemSuccess")); } void EStoraged::mountFilesystem() { /* * Create directory for the filesystem, if it's not already present. It * might already exist if, for example, the BMC reboots after creating the * directory. */ if (!fsIface->directoryExists(std::filesystem::path(mountPoint))) { bool success = fsIface->createDirectory(std::filesystem::path(mountPoint)); if (!success) { lg2::error("Failed to create mount point: {DIR}", "DIR", mountPoint, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.MountFilesystemFail")); throw InternalFailure(); } } /* Run the command to mount the filesystem. */ int retval = fsIface->doMount(cryptDevicePath.c_str(), mountPoint.c_str(), "ext4", 0, nullptr); if (retval != 0) { lg2::error("Failed to mount filesystem: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.MountFilesystemFail")); bool removeSuccess = fsIface->removeDirectory(std::filesystem::path(mountPoint)); if (!removeSuccess) { lg2::error("Failed to remove mount point: {DIR}", "DIR", mountPoint, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.MountFilesystemFail")); } throw InternalFailure(); } lg2::info("Successfully mounted filesystem at {DIR}", "DIR", mountPoint, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.MountFilesystemSuccess")); } void EStoraged::unmountFilesystem() { int retval = fsIface->doUnmount(mountPoint.c_str()); if (retval != 0) { lg2::error("Failed to unmount filesystem: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.UnmountFilesystemFail")); throw InternalFailure(); } /* Remove the mount point. */ bool success = fsIface->removeDirectory(std::filesystem::path(mountPoint)); if (!success) { lg2::error("Failed to remove mount point {DIR}", "DIR", mountPoint, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.UnmountFilesystemFail")); throw InternalFailure(); } lg2::info("Successfully unmounted filesystem at {DIR}", "DIR", mountPoint, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.MountFilesystemSuccess")); } void EStoraged::deactivateLuksDev() { lg2::info("Deactivating LUKS device {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DeactivateLuksDev")); int retval = cryptIface->cryptDeactivate(nullptr, containerName.c_str()); if (retval < 0) { lg2::error("Failed to deactivate crypt device: {RETVAL}", "RETVAL", retval, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DeactivateLuksDevFail")); throw InternalFailure(); } lg2::info("Successfully deactivated LUKS device {DEV}", "DEV", devPath, "REDFISH_MESSAGE_ID", std::string("OpenBMC.0.1.DeactivateLuksDevSuccess")); } std::string_view EStoraged::getCryptDevicePath() const { return cryptDevicePath; } } // namespace estoraged