Add crypto Erase to eStorageD.

The goal is to erase the keys that are used to decrypt the drive. After
the keys are erased it will not be possible to decrypt the drive, even
if the password can be recalled. The data is forever inaccessible.

Testing:

$ systemctl stop emmc.service

$ /eStoraged -b /dev/mmcblk0 &

$ busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume FormatLuks ays 3 1 2 3 xyz.openbmc_project.Inventory.Item.Volume.FilesystemType.ext4

$ busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Lock

$ busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Erase s xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.CryptoErase

$ busctl call xyz.openbmc_project.eStoraged.mmcblk0 /xyz/openbmc_project/storage/mmcblk0 xyz.openbmc_project.Inventory.Item.Volume Unlock ay 3 1 2 3
Call failed: The operation failed internally.

Signed-off-by: John Edward Broadbent <jebr@google.com>
Change-Id: I3221e82a92c1b555e2379b19c9e1d5b6e4b02f9b
diff --git a/include/cryptErase.hpp b/include/cryptErase.hpp
new file mode 100644
index 0000000..474f0df
--- /dev/null
+++ b/include/cryptErase.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "cryptsetupInterface.hpp"
+#include "erase.hpp"
+
+#include <libcryptsetup.h>
+
+#include <memory>
+#include <string_view>
+
+namespace estoraged
+{
+
+class CryptErase : public Erase
+{
+  public:
+    /** @brief Creates a CryptErase erase object.
+     *
+     *  @param[in] inDevPath - the linux device path for the block device.
+     *  @param[in](optional) cryptIface - a unique pointer to an cryptsetup
+     *  Interface object.
+     */
+    CryptErase(std::string_view devPath,
+               std::unique_ptr<estoraged::CryptsetupInterface> inCryptIface =
+                   std::make_unique<Cryptsetup>());
+
+    /** @brief searches and deletes all cryptographic keyslot
+     * and throws errors accordingly.
+     */
+    void doErase();
+
+  private:
+    std::unique_ptr<estoraged::CryptsetupInterface> cryptIface;
+};
+
+} // namespace estoraged
diff --git a/include/cryptsetupInterface.hpp b/include/cryptsetupInterface.hpp
index 25c69d0..8cc0469 100644
--- a/include/cryptsetupInterface.hpp
+++ b/include/cryptsetupInterface.hpp
@@ -96,6 +96,40 @@
      *  @returns 0 on success or negative errno value otherwise.
      */
     virtual int cryptDeactivate(struct crypt_device* cd, const char* name) = 0;
+
+    /** @brief Wrapper around crypt_keyslot_destory.
+     *  @details Used for mocking purposes.
+     *
+     *  @param[in] cd - crypt device handle, can not be NULL.
+     *  @param[in] keyslot requested key slot to destroy
+     *
+     *  @returns 0 on success or negative errno value otherwise.
+     */
+    virtual int cryptKeyslotDestroy(struct crypt_device* cd,
+                                    const int keyslot) = 0;
+
+    /** @breif Wapper around crypt_keyslot_max
+     *  @details Used for mocking purposes.
+     *
+     * @param type crypt device type
+     *
+     * @return slot count or negative errno otherwise if device
+     * does not support keyslots.
+     */
+    virtual int cryptKeySlotMax(const char* type) = 0;
+
+    /** @breif Wapper around crypt_keyslot_status
+     *  @details Used for mocking purposes.
+     *  Get information about particular key slot.
+     *
+     * @param cd crypt device handle
+     * @param keyslot requested keyslot to check or CRYPT_ANY_SLOT
+     *
+     * @return value defined by crypt_keyslot_info
+     *
+     */
+    virtual crypt_keyslot_info cryptKeySlotStatus(struct crypt_device* cd,
+                                                  int keyslot) = 0;
 };
 
 /** @class Cryptsetup
@@ -143,6 +177,22 @@
     {
         return crypt_deactivate(cd, name);
     }
+
+    int cryptKeyslotDestroy(struct crypt_device* cd, const int keyslot) override
+    {
+        return crypt_keyslot_destroy(cd, keyslot);
+    }
+
+    int cryptKeySlotMax(const char* type) override
+    {
+        return crypt_keyslot_max(type);
+    }
+
+    crypt_keyslot_info cryptKeySlotStatus(struct crypt_device* cd,
+                                          int keyslot) override
+    {
+        return crypt_keyslot_status(cd, keyslot);
+    }
 };
 
 /** @class CryptHandle
diff --git a/meson.build b/meson.build
index 7282cf3..1e0ed00 100644
--- a/meson.build
+++ b/meson.build
@@ -10,7 +10,6 @@
 )
 
 eStoraged_root = meson.current_source_dir()
-eStoraged_dbus_headers = include_directories('.')
 
 subdir('include')
 subdir('src')
diff --git a/src/erase/cryptoErase.cpp b/src/erase/cryptoErase.cpp
new file mode 100644
index 0000000..d99f6bf
--- /dev/null
+++ b/src/erase/cryptoErase.cpp
@@ -0,0 +1,88 @@
+#include "cryptErase.hpp"
+#include "cryptsetupInterface.hpp"
+#include "erase.hpp"
+
+#include <libcryptsetup.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace estoraged
+{
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
+
+CryptErase::CryptErase(
+    std::string_view devPathIn,
+    std::unique_ptr<estoraged::CryptsetupInterface> inCryptIface) :
+    Erase(devPathIn),
+    cryptIface(std::move(inCryptIface))
+{}
+
+void CryptErase::doErase()
+{
+    /* get cryptHandle */
+    CryptHandle cryptHandle(std::string(devPath).c_str());
+    if (cryptHandle.get() == nullptr)
+    {
+        lg2::error("Failed to initialize crypt device", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.EraseFailure"));
+        throw ResourceNotFound();
+    }
+    /* cryptLoad */
+    if (cryptIface.get()->cryptLoad(cryptHandle.get(), CRYPT_LUKS2, nullptr) !=
+        0)
+    {
+        lg2::error("Failed to load the key slots for destruction",
+                   "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.EraseFailure"));
+        throw ResourceNotFound();
+    }
+
+    /* find key slots */
+    int nKeySlots = cryptIface.get()->cryptKeySlotMax(CRYPT_LUKS2);
+    if (nKeySlots < 0)
+    {
+        lg2::error("Failed to find the max keyslots", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.EraseFailure"));
+        throw ResourceNotFound();
+    }
+
+    if (nKeySlots == 0)
+    {
+        lg2::error("Max keyslots should never be zero", "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.0.1.EraseFailure"));
+        throw ResourceNotFound();
+    }
+
+    /* destory working keyslots */
+    bool keySlotIssue = false;
+    for (int i = 0; i < nKeySlots; i++)
+    {
+        crypt_keyslot_info ki =
+            cryptIface.get()->cryptKeySlotStatus(cryptHandle.get(), i);
+
+        if (ki == CRYPT_SLOT_ACTIVE || ki == CRYPT_SLOT_ACTIVE_LAST)
+        {
+            if (cryptIface.get()->cryptKeyslotDestroy(cryptHandle.get(), i) !=
+                0)
+            {
+                lg2::error(
+                    "Estoraged erase failed to destroy keyslot, continuing",
+                    "REDFISH_MESSAGE_ID",
+                    std::string("eStorageD.1.0.EraseFailure"));
+                keySlotIssue = true;
+            }
+        }
+    }
+    if (keySlotIssue)
+    {
+        throw InternalFailure();
+    }
+}
+
+} // namespace estoraged
diff --git a/src/erase/meson.build b/src/erase/meson.build
index 2265c1a..14e4668 100644
--- a/src/erase/meson.build
+++ b/src/erase/meson.build
@@ -2,6 +2,7 @@
   'libeStoragedErase-lib',
   'verifyDriveGeometry.cpp',
   'pattern.cpp',
+  'cryptoErase.cpp',
   'erase.cpp',
   'zero.cpp',
   include_directories : eStoraged_headers,
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 3e3d0bb..826d44b 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -1,6 +1,7 @@
 
 #include "estoraged.hpp"
 
+#include "cryptErase.hpp"
 #include "cryptsetupInterface.hpp"
 #include "pattern.hpp"
 #include "verifyDriveGeometry.hpp"
@@ -61,6 +62,8 @@
     {
         case EraseMethod::CryptoErase:
         {
+            CryptErase myCryptErase(devPath);
+            myCryptErase.doErase();
             break;
         }
         case EraseMethod::VerifyGeometry:
diff --git a/src/test/erase/crypto_test.cpp b/src/test/erase/crypto_test.cpp
new file mode 100644
index 0000000..917b23b
--- /dev/null
+++ b/src/test/erase/crypto_test.cpp
@@ -0,0 +1,146 @@
+
+#include "cryptErase.hpp"
+#include "cryptsetupInterface.hpp"
+#include "estoraged.hpp"
+#include "estoraged_test.hpp"
+
+#include <unistd.h>
+
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <exception>
+#include <filesystem>
+#include <fstream>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace estoraged_test
+{
+
+using estoraged::CryptErase;
+using estoraged::Cryptsetup;
+using estoraged::CryptsetupInterface;
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
+using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
+using ::testing::_;
+using ::testing::Return;
+using ::testing::StrEq;
+
+class cryptoEraseTest : public testing::Test
+{
+  public:
+    static constexpr char testFileName[] = "testfile";
+    std::ofstream testFile;
+
+    void SetUp() override
+    {
+        /* Create an empty file that we'll pretend is a 'storage device'. */
+        testFile.open(testFileName,
+                      std::ios::out | std::ios::binary | std::ios::trunc);
+        testFile.close();
+        if (testFile.fail())
+        {
+            throw std::runtime_error("Failed to open test file");
+        }
+        testFile.close();
+    }
+};
+
+TEST_F(cryptoEraseTest, EraseCryptPass)
+{
+    std::unique_ptr<MockCryptsetupInterface> mockCryptIface =
+        std::make_unique<MockCryptsetupInterface>();
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, StrEq(CRYPT_LUKS2), nullptr))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotMax(StrEq(CRYPT_LUKS2)))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotStatus(_, 0))
+        .WillOnce(Return(CRYPT_SLOT_ACTIVE_LAST));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeyslotDestroy(_, 0)).Times(1);
+
+    CryptErase myCryptErase =
+        CryptErase(testFileName, std::move(mockCryptIface));
+    EXPECT_NO_THROW(myCryptErase.doErase());
+}
+
+TEST_F(cryptoEraseTest, EraseCrypMaxSlotFails)
+{
+    std::unique_ptr<MockCryptsetupInterface> mockCryptIface =
+        std::make_unique<MockCryptsetupInterface>();
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, StrEq(CRYPT_LUKS2), nullptr))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotMax(StrEq(CRYPT_LUKS2)))
+        .WillOnce(Return(-1));
+
+    CryptErase myCryptErase =
+        CryptErase(testFileName, std::move(mockCryptIface));
+    EXPECT_THROW(myCryptErase.doErase(), ResourceNotFound);
+}
+
+TEST_F(cryptoEraseTest, EraseCrypMaxSlotZero)
+{
+    std::unique_ptr<MockCryptsetupInterface> mockCryptIface =
+        std::make_unique<MockCryptsetupInterface>();
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, StrEq(CRYPT_LUKS2), nullptr))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotMax(StrEq(CRYPT_LUKS2)))
+        .WillOnce(Return(0));
+
+    CryptErase myCryptErase =
+        CryptErase(testFileName, std::move(mockCryptIface));
+    EXPECT_THROW(myCryptErase.doErase(), ResourceNotFound);
+}
+
+TEST_F(cryptoEraseTest, EraseCrypOnlyInvalid)
+{
+    std::unique_ptr<MockCryptsetupInterface> mockCryptIface =
+        std::make_unique<MockCryptsetupInterface>();
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, StrEq(CRYPT_LUKS2), nullptr))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotMax(StrEq(CRYPT_LUKS2)))
+        .WillOnce(Return(32));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotStatus(_, _))
+        .WillRepeatedly(Return(CRYPT_SLOT_INVALID));
+
+    CryptErase myCryptErase =
+        CryptErase(testFileName, std::move(mockCryptIface));
+    EXPECT_NO_THROW(myCryptErase.doErase());
+}
+
+TEST_F(cryptoEraseTest, EraseCrypDestoryFails)
+{
+    std::unique_ptr<MockCryptsetupInterface> mockCryptIface =
+        std::make_unique<MockCryptsetupInterface>();
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, StrEq(CRYPT_LUKS2), nullptr))
+        .WillOnce(Return(0));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotMax(StrEq(CRYPT_LUKS2)))
+        .WillOnce(Return(1));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeySlotStatus(_, 0))
+        .WillOnce(Return(CRYPT_SLOT_ACTIVE));
+
+    EXPECT_CALL(*mockCryptIface, cryptKeyslotDestroy(_, 0))
+        .WillOnce(Return(-1));
+
+    CryptErase myCryptErase =
+        CryptErase(testFileName, std::move(mockCryptIface));
+    EXPECT_THROW(myCryptErase.doErase(), InternalFailure);
+}
+
+} // namespace estoraged_test
diff --git a/src/test/estoraged_test.cpp b/src/test/estoraged_test.cpp
index 3b23215..10357fe 100644
--- a/src/test/estoraged_test.cpp
+++ b/src/test/estoraged_test.cpp
@@ -1,13 +1,12 @@
+#include "estoraged_test.hpp"
 
-#include "cryptsetupInterface.hpp"
 #include "estoraged.hpp"
-#include "filesystemInterface.hpp"
 
 #include <unistd.h>
 
-#include <sdbusplus/bus.hpp>
 #include <sdbusplus/test/sdbus_mock.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/Inventory/Item/Volume/client.hpp>
 
 #include <exception>
 #include <filesystem>
@@ -22,59 +21,6 @@
 namespace estoraged_test
 {
 
-class MockFilesystemInterface : public estoraged::FilesystemInterface
-{
-  public:
-    MOCK_METHOD(int, runMkfs, (const std::string& logicalVolume), (override));
-
-    MOCK_METHOD(int, doMount,
-                (const char* source, const char* target,
-                 const char* filesystemtype, unsigned long mountflags,
-                 const void* data),
-                (override));
-
-    MOCK_METHOD(int, doUnmount, (const char* target), (override));
-
-    MOCK_METHOD(bool, createDirectory, (const std::filesystem::path& p),
-                (override));
-
-    MOCK_METHOD(bool, removeDirectory, (const std::filesystem::path& p),
-                (override));
-
-    MOCK_METHOD(bool, directoryExists, (const std::filesystem::path& p),
-                (override));
-};
-
-class MockCryptsetupInterface : public estoraged::CryptsetupInterface
-{
-  public:
-    MOCK_METHOD(int, cryptFormat,
-                (struct crypt_device * cd, const char* type, const char* cipher,
-                 const char* cipher_mode, const char* uuid,
-                 const char* volume_key, size_t volume_key_size, void* params),
-                (override));
-
-    MOCK_METHOD(int, cryptKeyslotAddByVolumeKey,
-                (struct crypt_device * cd, int keyslot, const char* volume_key,
-                 size_t volume_key_size, const char* passphrase,
-                 size_t passphrase_size),
-                (override));
-
-    MOCK_METHOD(int, cryptLoad,
-                (struct crypt_device * cd, const char* requested_type,
-                 void* params),
-                (override));
-
-    MOCK_METHOD(int, cryptActivateByPassphrase,
-                (struct crypt_device * cd, const char* name, int keyslot,
-                 const char* passphrase, size_t passphrase_size,
-                 uint32_t flags),
-                (override));
-
-    MOCK_METHOD(int, cryptDeactivate,
-                (struct crypt_device * cd, const char* name), (override));
-};
-
 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
 using sdbusplus::xyz::openbmc_project::Common::Error::ResourceNotFound;
 using sdbusplus::xyz::openbmc_project::Inventory::Item::server::Volume;
diff --git a/src/test/include/estoraged_test.hpp b/src/test/include/estoraged_test.hpp
new file mode 100644
index 0000000..ebd9e11
--- /dev/null
+++ b/src/test/include/estoraged_test.hpp
@@ -0,0 +1,80 @@
+
+#include "cryptErase.hpp"
+#include "cryptsetupInterface.hpp"
+#include "estoraged.hpp"
+#include "filesystemInterface.hpp"
+
+#include <unistd.h>
+
+#include <exception>
+#include <filesystem>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace estoraged_test
+{
+
+class MockFilesystemInterface : public estoraged::FilesystemInterface
+{
+  public:
+    MOCK_METHOD(int, runMkfs, (const std::string& logicalVolume), (override));
+
+    MOCK_METHOD(int, doMount,
+                (const char* source, const char* target,
+                 const char* filesystemtype, unsigned long mountflags,
+                 const void* data),
+                (override));
+
+    MOCK_METHOD(int, doUnmount, (const char* target), (override));
+
+    MOCK_METHOD(bool, createDirectory, (const std::filesystem::path& p),
+                (override));
+
+    MOCK_METHOD(bool, removeDirectory, (const std::filesystem::path& p),
+                (override));
+
+    MOCK_METHOD(bool, directoryExists, (const std::filesystem::path& p),
+                (override));
+};
+
+class MockCryptsetupInterface : public estoraged::CryptsetupInterface
+{
+  public:
+    MOCK_METHOD(int, cryptFormat,
+                (struct crypt_device * cd, const char* type, const char* cipher,
+                 const char* cipher_mode, const char* uuid,
+                 const char* volume_key, size_t volume_key_size, void* params),
+                (override));
+
+    MOCK_METHOD(int, cryptKeyslotAddByVolumeKey,
+                (struct crypt_device * cd, int keyslot, const char* volume_key,
+                 size_t volume_key_size, const char* passphrase,
+                 size_t passphrase_size),
+                (override));
+
+    MOCK_METHOD(int, cryptLoad,
+                (struct crypt_device * cd, const char* requested_type,
+                 void* params),
+                (override));
+
+    MOCK_METHOD(int, cryptActivateByPassphrase,
+                (struct crypt_device * cd, const char* name, int keyslot,
+                 const char* passphrase, size_t passphrase_size,
+                 uint32_t flags),
+                (override));
+
+    MOCK_METHOD(int, cryptDeactivate,
+                (struct crypt_device * cd, const char* name), (override));
+
+    MOCK_METHOD(int, cryptKeyslotDestroy,
+                (struct crypt_device * cd, const int slot), (override));
+
+    MOCK_METHOD(int, cryptKeySlotMax, (const char* type), (override));
+
+    MOCK_METHOD(crypt_keyslot_info, cryptKeySlotStatus,
+                (struct crypt_device * cd, int keyslot), (override));
+};
+
+} // namespace estoraged_test
diff --git a/src/test/meson.build b/src/test/meson.build
index 6d6880d..b684d24 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -5,9 +5,12 @@
   'erase/verifyGeometry_test',
   'erase/pattern_test',
   'erase/zero_test',
+  'erase/crypto_test',
   'estoraged_test',
 ]
 
+test_eStoraged_headers = include_directories('include')
+
 foreach t : tests
   test(t, executable(t.underscorify(), t + '.cpp',
                      implicit_include_directories: false,
@@ -16,5 +19,5 @@
                        gmock,
                        libeStoraged,
                      ],
-                     include_directories: eStoraged_headers))
+                     include_directories: [eStoraged_headers, test_eStoraged_headers]))
 endforeach