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