cryptohome: Add fscrypt container

Add fscrypt container to encapsulate teardown/setup for directories
encrypted with fscrypt (supported on ext4, f2fs and ubifs).
Fscrypt directories are 'unlocked' once the encrypted key associated
with directory is inserted into either the session keyring or the
filesystem specific keyring (available from v5.4).

BUG=b:172344853
TEST=unittests

Change-Id: I7db5a54a05f1355dc1fbbfa63539506ba46edefa
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2541889
Reviewed-by: Leo Lai <cylai@google.com>
Reviewed-by: Daniil Lunev <dlunev@chromium.org>
Tested-by: Sarthak Kukreti <sarthakkukreti@chromium.org>
Commit-Queue: Sarthak Kukreti <sarthakkukreti@chromium.org>
diff --git a/cryptohome/BUILD.gn b/cryptohome/BUILD.gn
index fcbc8b7..5145791 100644
--- a/cryptohome/BUILD.gn
+++ b/cryptohome/BUILD.gn
@@ -467,6 +467,7 @@
       "storage/arc_disk_quota_unittest.cc",
       "storage/disk_cleanup_routines_unittest.cc",
       "storage/disk_cleanup_unittest.cc",
+      "storage/encrypted_container/fscrypt_container_test.cc",
       "storage/homedirs_unittest.cc",
       "storage/mock_disk_cleanup.cc",
       "storage/mock_disk_cleanup_routines.cc",
diff --git a/cryptohome/libs/BUILD.gn b/cryptohome/libs/BUILD.gn
index 70f8302..63e18d5 100644
--- a/cryptohome/libs/BUILD.gn
+++ b/cryptohome/libs/BUILD.gn
@@ -144,6 +144,8 @@
     "../dircrypto_util.cc",
     "../libscrypt_compat.cc",
     "../platform.cc",
+    "../storage/encrypted_container/encrypted_container.cc",
+    "../storage/encrypted_container/fscrypt_container.cc",
   ]
   libs = [
     "keyutils",
diff --git a/cryptohome/storage/encrypted_container/encrypted_container.cc b/cryptohome/storage/encrypted_container/encrypted_container.cc
new file mode 100644
index 0000000..4437d39
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/encrypted_container.cc
@@ -0,0 +1,32 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cryptohome/storage/encrypted_container/encrypted_container.h"
+
+#include <memory>
+
+#include <base/files/file_path.h>
+
+#include "cryptohome/platform.h"
+#include "cryptohome/storage/encrypted_container/filesystem_key.h"
+#include "cryptohome/storage/encrypted_container/fscrypt_container.h"
+
+namespace cryptohome {
+
+// static
+std::unique_ptr<EncryptedContainer> EncryptedContainer::Generate(
+    EncryptedContainerType type,
+    const base::FilePath& backing_dir,
+    const FileSystemKeyReference& key_reference,
+    Platform* platform) {
+  switch (type) {
+    case EncryptedContainerType::kFscrypt:
+      return std::make_unique<FscryptContainer>(backing_dir, key_reference,
+                                                platform);
+    default:
+      return nullptr;
+  }
+}
+
+}  // namespace cryptohome
diff --git a/cryptohome/storage/encrypted_container/encrypted_container.h b/cryptohome/storage/encrypted_container/encrypted_container.h
new file mode 100644
index 0000000..bd4347e
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/encrypted_container.h
@@ -0,0 +1,50 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_ENCRYPTED_CONTAINER_H_
+#define CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_ENCRYPTED_CONTAINER_H_
+
+#include <memory>
+
+#include <base/files/file_path.h>
+
+#include "cryptohome/platform.h"
+#include "cryptohome/storage/encrypted_container/filesystem_key.h"
+
+namespace cryptohome {
+
+// Type of encrypted containers.
+enum class EncryptedContainerType {
+  kUnknown = 0,
+  kFscrypt,
+};
+
+// An encrypted container is an abstract class that represents an encrypted
+// backing storage medium. Since encrypted containers can be used in both
+// daemons and one-shot calls, the implementation of each encrypted container
+// leans towards keeping the container as stateless as possible.
+class EncryptedContainer {
+ public:
+  virtual ~EncryptedContainer() {}
+
+  static std::unique_ptr<EncryptedContainer> Generate(
+      EncryptedContainerType type,
+      const base::FilePath& backing_dir,
+      const FileSystemKeyReference& key_reference,
+      Platform* platform);
+
+  // Removes the encrypted container's backing storage.
+  virtual bool Purge() = 0;
+  // Sets up the encrypted container, including creating the container if
+  // needed.
+  virtual bool Setup(const FileSystemKey& encryption_key, bool create) = 0;
+  // Tears down the container, removing the encryption key if it was added.
+  virtual bool Teardown() = 0;
+  // Gets the type of the encrypted container.
+  virtual EncryptedContainerType GetType() = 0;
+};
+
+}  // namespace cryptohome
+
+#endif  // CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_ENCRYPTED_CONTAINER_H_
diff --git a/cryptohome/storage/encrypted_container/fscrypt_container.cc b/cryptohome/storage/encrypted_container/fscrypt_container.cc
new file mode 100644
index 0000000..d0b25a4
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/fscrypt_container.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cryptohome/storage/encrypted_container/fscrypt_container.h"
+
+#include <base/files/file_path.h>
+
+#include "cryptohome/platform.h"
+#include "cryptohome/storage/encrypted_container/filesystem_key.h"
+
+namespace cryptohome {
+
+FscryptContainer::FscryptContainer(const base::FilePath& backing_dir,
+                                   const FileSystemKeyReference& key_reference,
+                                   Platform* platform)
+    : backing_dir_(backing_dir),
+      key_reference_({.reference = key_reference.fek_sig}),
+      platform_(platform) {}
+
+bool FscryptContainer::Purge() {
+  return platform_->DeletePathRecursively(backing_dir_);
+}
+
+bool FscryptContainer::Setup(const FileSystemKey& encryption_key, bool create) {
+  if (create) {
+    if (!platform_->CreateDirectory(backing_dir_)) {
+      LOG(ERROR) << "Failed to create directory " << backing_dir_;
+      return false;
+    }
+  }
+
+  key_reference_.policy_version =
+      dircrypto::GetDirectoryPolicyVersion(backing_dir_);
+
+  if (key_reference_.policy_version < 0) {
+    key_reference_.policy_version = dircrypto::CheckFscryptKeyIoctlSupport()
+                                        ? FSCRYPT_POLICY_V2
+                                        : FSCRYPT_POLICY_V1;
+  }
+
+  if (!platform_->AddDirCryptoKeyToKeyring(encryption_key.fek,
+                                           &key_reference_)) {
+    LOG(ERROR) << "Failed to add fscrypt key to kernel";
+    return false;
+  }
+
+  // `SetDirectoryKey` is a set-or-verify function: for directories with the
+  // encryption policy already set, this function call acts as a verifier.
+  if (!platform_->SetDirCryptoKey(backing_dir_, key_reference_)) {
+    LOG(ERROR) << "Failed to set fscrypt key for backing directory";
+    return false;
+  }
+
+  return true;
+}
+
+bool FscryptContainer::Teardown() {
+  return platform_->InvalidateDirCryptoKey(key_reference_, backing_dir_);
+}
+
+}  // namespace cryptohome
diff --git a/cryptohome/storage/encrypted_container/fscrypt_container.h b/cryptohome/storage/encrypted_container/fscrypt_container.h
new file mode 100644
index 0000000..cde0e3a
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/fscrypt_container.h
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_FSCRYPT_CONTAINER_H_
+#define CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_FSCRYPT_CONTAINER_H_
+
+#include "cryptohome/storage/encrypted_container/encrypted_container.h"
+
+#include <base/files/file_path.h>
+
+#include "cryptohome/platform.h"
+#include "cryptohome/storage/encrypted_container/filesystem_key.h"
+
+namespace cryptohome {
+
+// `FscryptContainer` is a file-level encrypted container which uses fscrypt to
+// encrypt the `backing_dir_` transparently.
+class FscryptContainer : public EncryptedContainer {
+ public:
+  FscryptContainer(const base::FilePath& backing_dir,
+                   const FileSystemKeyReference& key_reference,
+                   Platform* platform);
+  ~FscryptContainer() = default;
+
+  bool Setup(const FileSystemKey& encryption_key, bool create) override;
+  bool Teardown() override;
+  bool Purge() override;
+  EncryptedContainerType GetType() override {
+    return EncryptedContainerType::kFscrypt;
+  }
+
+ private:
+  const base::FilePath backing_dir_;
+  dircrypto::KeyReference key_reference_;
+  Platform* platform_;
+};
+
+}  // namespace cryptohome
+
+#endif  // CRYPTOHOME_STORAGE_ENCRYPTED_CONTAINER_FSCRYPT_CONTAINER_H_
diff --git a/cryptohome/storage/encrypted_container/fscrypt_container_test.cc b/cryptohome/storage/encrypted_container/fscrypt_container_test.cc
new file mode 100644
index 0000000..f8323b4
--- /dev/null
+++ b/cryptohome/storage/encrypted_container/fscrypt_container_test.cc
@@ -0,0 +1,92 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cryptohome/storage/encrypted_container/fscrypt_container.h"
+
+#include <memory>
+
+#include <base/files/file_path.h>
+#include <brillo/secure_blob.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "cryptohome/mock_platform.h"
+#include "cryptohome/storage/encrypted_container/filesystem_key.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace cryptohome {
+
+class FscryptContainerTest : public ::testing::Test {
+ public:
+  FscryptContainerTest()
+      : backing_dir_(base::FilePath("/a/b/c")),
+        key_reference_({.fek_sig = brillo::SecureBlob("random_keysig")}),
+        key_({.fek = brillo::SecureBlob("random key")}),
+        container_(
+            EncryptedContainer::Generate(EncryptedContainerType::kFscrypt,
+                                         backing_dir_,
+                                         key_reference_,
+                                         &platform_)) {}
+  ~FscryptContainerTest() override = default;
+
+ protected:
+  base::FilePath backing_dir_;
+  FileSystemKeyReference key_reference_;
+  FileSystemKey key_;
+  MockPlatform platform_;
+  std::unique_ptr<EncryptedContainer> container_;
+};
+
+// Tests the create path for fscrypt containers.
+TEST_F(FscryptContainerTest, SetupCreateCheck) {
+  EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _)).WillOnce(Return(true));
+
+  EXPECT_CALL(platform_, SetDirCryptoKey(backing_dir_, _))
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(container_->Setup(key_, true));
+  EXPECT_TRUE(platform_.DirectoryExists(backing_dir_));
+}
+
+// Tests the setup path for an existing fscrypt container.
+TEST_F(FscryptContainerTest, SetupNoCreateCheck) {
+  EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _)).WillOnce(Return(true));
+
+  EXPECT_CALL(platform_, SetDirCryptoKey(backing_dir_, _))
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(container_->Setup(key_, false));
+}
+
+// Tests failure path when adding the encryption key to the kernel/filesystem
+// keyring fails.
+TEST_F(FscryptContainerTest, SetupFailedEncryptionKeyAdd) {
+  EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _))
+      .WillOnce(Return(false));
+
+  EXPECT_FALSE(container_->Setup(key_, false));
+}
+
+// Tests failure path when setting the encryption policy for the backing
+// directory fails.
+TEST_F(FscryptContainerTest, SetupFailedEncryptionKeySet) {
+  EXPECT_CALL(platform_, AddDirCryptoKeyToKeyring(_, _)).WillOnce(Return(true));
+
+  EXPECT_CALL(platform_, SetDirCryptoKey(backing_dir_, _))
+      .WillOnce(Return(false));
+
+  EXPECT_FALSE(container_->Setup(key_, false));
+}
+
+// Tests failure path on failing to invalidate an added key from the
+// kernel/filesystem keyring.
+TEST_F(FscryptContainerTest, TeardownInvalidateKey) {
+  EXPECT_CALL(platform_, InvalidateDirCryptoKey(_, _)).WillOnce(Return(false));
+
+  EXPECT_FALSE(container_->Teardown());
+}
+
+}  // namespace cryptohome