Implement the changePassword method

With this commit, it is now possible to change the password for the
LUKS-encrypted volume, using the changePassword D-Bus method for
eStoraged.

Tested:
$ busctl call xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0 \
  xyz.openbmc_project.Inventory.Item.Volume FormatLuks ays 3 1 2 3 \
  xyz.openbmc_project.Inventory.Item.Volume.FilesystemType.ext4 \
  --timeout=60
$ busctl call xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0 \
  xyz.openbmc_project.Inventory.Item.Volume ChangePassword \
  ayay 3 1 2 3 3 4 5 6
$ busctl call xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0 \
  xyz.openbmc_project.Inventory.Item.Volume Lock
Attempted to unlock using the old password. It failed as expected.
$ busctl call xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0 \
  xyz.openbmc_project.Inventory.Item.Volume Unlock ay 3 1 2 3
Unlocked with the new password
$ busctl call xyz.openbmc_project.eStoraged \
  /xyz/openbmc_project/inventory/storage/mmcblk0 \
  xyz.openbmc_project.Inventory.Item.Volume Unlock ay 3 4 5 6

Signed-off-by: John Wedig <johnwedig@google.com>
Change-Id: If1395fb04f51b1fb1a3d26731422d21476205207
diff --git a/include/cryptsetupInterface.hpp b/include/cryptsetupInterface.hpp
index 5942b60..e13320c 100644
--- a/include/cryptsetupInterface.hpp
+++ b/include/cryptsetupInterface.hpp
@@ -28,6 +28,7 @@
 
     CryptsetupInterface(CryptsetupInterface&&) = delete;
     CryptsetupInterface& operator=(CryptsetupInterface&&) = delete;
+
     /** @brief Wrapper around crypt_format.
      *  @details Used for mocking purposes.
      *
@@ -49,6 +50,26 @@
                             const char* uuid, const char* volumeKey,
                             size_t volumeKeySize, void* params) = 0;
 
+    /** @brief Wrapper around crypt_keyslot_change_by_passphrase.
+     *  @details Used for mocking purposes.
+     *
+     *  @param[in] cd - crypt device handle.
+     *  @param[in] keyslotOld - old keyslot or CRYPT_ANY_SLOT.
+     *  @param[in] keyslotNew - new keyslot or CRYPT_ANY_SLOT.
+     *  @param[in] passphrase - passphrase for new keyslot.
+     *  @param[in] passphraseSize - size of passphrase.
+     *  @param[in] newPassphrase - new passphrase for the specified keyslot
+     *  @param[in] newPassphraseSize - size of newPassphrase (in bytes).
+     *
+     *  @returns allocated key slot number or negative errno otherwise.
+     */
+    virtual int cryptKeyslotChangeByPassphrase(struct crypt_device* cd,
+                                               int keyslotOld, int keyslotNew,
+                                               const char* passphrase,
+                                               size_t passphraseSize,
+                                               const char* newPassphrase,
+                                               size_t newPassphraseSize) = 0;
+
     /** @brief Wrapper around crypt_keyslot_add_by_volume_key.
      *  @details Used for mocking purposes.
      *
@@ -166,6 +187,17 @@
                             volumeKeySize, params);
     }
 
+    int cryptKeyslotChangeByPassphrase(struct crypt_device* cd, int keyslotOld,
+                                       int keyslotNew, const char* passphrase,
+                                       size_t passphraseSize,
+                                       const char* newPassphrase,
+                                       size_t newPassphraseSize) override
+    {
+        return crypt_keyslot_change_by_passphrase(
+            cd, keyslotOld, keyslotNew, passphrase, passphraseSize,
+            newPassphrase, newPassphraseSize);
+    }
+
     int cryptKeyslotAddByVolumeKey(struct crypt_device* cd, int keyslot,
                                    const char* volumeKey, size_t volumeKeySize,
                                    const char* passphrase,
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 0b93b3b..ef3bd63 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -234,12 +234,28 @@
     mountFilesystem();
 }
 
-void EStoraged::changePassword(const std::vector<uint8_t>& /*oldPassword*/,
-                               const std::vector<uint8_t>& /*newPassword*/)
+void EStoraged::changePassword(const std::vector<uint8_t>& oldPassword,
+                               const std::vector<uint8_t>& newPassword)
 {
-    std::cerr << "Changing password for encrypted eMMC" << std::endl;
     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<const char*>(oldPassword.data()), oldPassword.size(),
+        reinterpret_cast<const char*>(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
diff --git a/src/test/estoraged_test.cpp b/src/test/estoraged_test.cpp
index 0bbb32b..9fde533 100644
--- a/src/test/estoraged_test.cpp
+++ b/src/test/estoraged_test.cpp
@@ -452,4 +452,46 @@
     EXPECT_FALSE(esObject->isLocked());
 }
 
+/* Test case where we successfully change the password. */
+TEST_F(EStoragedTest, ChangePasswordSuccess)
+{
+    std::string newPasswordString("newPassword");
+    std::vector<uint8_t> newPassword(newPasswordString.begin(),
+                                     newPasswordString.end());
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
+
+    EXPECT_CALL(*mockCryptIface,
+                cryptKeyslotChangeByPassphrase(
+                    _, _, _, reinterpret_cast<const char*>(password.data()),
+                    password.size(),
+                    reinterpret_cast<const char*>(newPassword.data()),
+                    newPassword.size()))
+        .WillOnce(Return(0));
+
+    /* Change the password for the LUKS-encrypted device. */
+    esObject->changePassword(password, newPassword);
+}
+
+/* Test case where we fail to change the password. */
+TEST_F(EStoragedTest, ChangePasswordFail)
+{
+    std::string newPasswordString("newPassword");
+    std::vector<uint8_t> newPassword(newPasswordString.begin(),
+                                     newPasswordString.end());
+
+    EXPECT_CALL(*mockCryptIface, cryptLoad(_, _, _)).Times(1);
+
+    EXPECT_CALL(*mockCryptIface,
+                cryptKeyslotChangeByPassphrase(
+                    _, _, _, reinterpret_cast<const char*>(password.data()),
+                    password.size(),
+                    reinterpret_cast<const char*>(newPassword.data()),
+                    newPassword.size()))
+        .WillOnce(Return(-1));
+
+    EXPECT_THROW(esObject->changePassword(password, newPassword),
+                 InternalFailure);
+}
+
 } // namespace estoraged_test
diff --git a/src/test/include/estoraged_test.hpp b/src/test/include/estoraged_test.hpp
index ebd9e11..6809c37 100644
--- a/src/test/include/estoraged_test.hpp
+++ b/src/test/include/estoraged_test.hpp
@@ -59,6 +59,12 @@
                  void* params),
                 (override));
 
+    MOCK_METHOD(int, cryptKeyslotChangeByPassphrase,
+                (struct crypt_device * cd, int keyslotOld, int keyslotNew,
+                 const char* passphrase, size_t passphraseSize,
+                 const char* newPassphrase, size_t newPassphraseSize),
+                (override));
+
     MOCK_METHOD(int, cryptActivateByPassphrase,
                 (struct crypt_device * cd, const char* name, int keyslot,
                  const char* passphrase, size_t passphrase_size,