/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//
// Test that metadata encryption is working, via:
//
// - Correctness tests.  These test the standard metadata encryption formats
//   supported by Android R and higher via dm-default-key v2.
//
// - Randomness test.  This runs on all devices that use metadata encryption.
//
// The correctness tests create a temporary default-key mapping over the raw
// userdata partition, read from it, and verify that the data got decrypted
// correctly.  This only tests decryption, since this avoids having to find a
// region on disk that can safely be modified.  This should be good enough since
// the device wouldn't work anyway if decryption didn't invert encryption.
//
// Note that this temporary default-key mapping will overlap the device's "real"
// default-key mapping, if the device has one.  The kernel allows this.  The
// tests don't use a loopback device instead, since dm-default-key over a
// loopback device can't use the real inline encryption hardware.
//
// The correctness tests cover the following settings:
//
//    metadata_encryption=aes-256-xts
//    metadata_encryption=adiantum
//    metadata_encryption=aes-256-xts:wrappedkey_v0
//
// The tests don't check which one of those settings, if any, the device is
// actually using; they just try to test everything they can.
//
// These tests don't specifically test that file contents aren't encrypted
// twice.  That's already implied by the file-based encryption test cases,
// provided that the device actually has metadata encryption enabled.
//

#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
#include <asm/byteorder.h>
#include <fcntl.h>
#include <fstab/fstab.h>
#include <gtest/gtest.h>
#include <libdm/dm.h>
#include <linux/types.h>
#include <stdlib.h>
#include <unistd.h>

#include <chrono>

#include "vts_kernel_encryption.h"

using namespace android::dm;

namespace android {
namespace kernel {

#define cpu_to_le64 __cpu_to_le64
#define le64_to_cpu __le64_to_cpu

// Alignment to use for direct I/O reads of block devices
static constexpr int kDirectIOAlignment = 4096;

// Assumed size of filesystem blocks, in bytes
static constexpr int kFilesystemBlockSize = 4096;

// Checks whether the kernel supports version 2 or higher of dm-default-key.
static bool IsDmDefaultKeyV2Supported(DeviceMapper &dm) {
  DmTargetTypeInfo info;
  if (!dm.GetTargetByName("default-key", &info)) {
    GTEST_LOG_(INFO) << "dm-default-key not enabled";
    return false;
  }
  if (!info.IsAtLeast(2, 0, 0)) {
    // The legacy version of dm-default-key (which was never supported by the
    // Android common kernels) used a vendor-specific on-disk format, so it's
    // not testable by a vendor-independent test.
    GTEST_LOG_(INFO) << "Detected legacy dm-default-key";
    return false;
  }
  return true;
}

// Reads |count| bytes from the beginning of |blk_device|, using direct I/O to
// avoid getting any stale cached data.  Direct I/O requires using a hardware
// sector size aligned buffer.
static bool ReadBlockDevice(const std::string &blk_device, size_t count,
                            std::vector<uint8_t> *data) {
  GTEST_LOG_(INFO) << "Reading " << count << " bytes from " << blk_device;
  std::unique_ptr<void, void (*)(void *)> buf_mem(
      aligned_alloc(kDirectIOAlignment, count), free);
  if (buf_mem == nullptr) {
    ADD_FAILURE() << "out of memory";
    return false;
  }
  uint8_t *buffer = static_cast<uint8_t *>(buf_mem.get());

  android::base::unique_fd fd(
      open(blk_device.c_str(), O_RDONLY | O_DIRECT | O_CLOEXEC));
  if (fd < 0) {
    ADD_FAILURE() << "Failed to open " << blk_device << Errno();
    return false;
  }
  if (!android::base::ReadFully(fd, buffer, count)) {
    ADD_FAILURE() << "Failed to read from " << blk_device << Errno();
    return false;
  }

  *data = std::vector<uint8_t>(buffer, buffer + count);
  return true;
}

class DmDefaultKeyTest : public ::testing::Test {
  // Filesystem whose underlying partition the test will use
  static constexpr const char *kTestMountpoint = "/data";

  // Size of the dm-default-key crypto sector size (data unit size) in bytes
  static constexpr int kCryptoSectorSize = 4096;

  // Size of the test data in crypto sectors
  static constexpr int kTestDataSectors = 256;

  // Size of the test data in bytes
  static constexpr int kTestDataBytes = kTestDataSectors * kCryptoSectorSize;

  // Device-mapper API sector size in bytes.
  // This is unrelated to the crypto sector size.
  static constexpr int kDmApiSectorSize = 512;

 protected:
  void SetUp() override;
  void TearDown() override;
  bool CreateTestDevice(const std::string &cipher,
                        const std::vector<uint8_t> &key, bool is_wrapped_key);
  void VerifyDecryption(const std::vector<uint8_t> &key, const Cipher &cipher);
  void DoTest(const std::string &cipher_string, const Cipher &cipher);
  std::string test_dm_device_name_;
  bool skip_test_ = false;
  DeviceMapper *dm_ = nullptr;
  std::string raw_blk_device_;
  std::string dm_device_path_;
};

// Test setup procedure.  Checks for the needed kernel support, finds the raw
// partition to use, and does other preparations.  skip_test_ is set to true if
// the test should be skipped.
void DmDefaultKeyTest::SetUp() {
  dm_ = &DeviceMapper::Instance();

  if (!IsDmDefaultKeyV2Supported(*dm_)) {
    int first_api_level;
    ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
    // Devices launching with R or higher must support dm-default-key v2.
    ASSERT_LE(first_api_level, __ANDROID_API_Q__);
    GTEST_LOG_(INFO)
        << "Skipping test because dm-default-key v2 is unsupported";
    skip_test_ = true;
    return;
  }
  test_dm_device_name_ =
      android::base::StringPrintf("DmDefaultKeyTest.%d", getpid());

  FilesystemInfo fs_info;
  ASSERT_TRUE(GetFilesystemInfo(kTestMountpoint, &fs_info));
  raw_blk_device_ = fs_info.disk_map[0].raw_blk_device;

  dm_->DeleteDevice(test_dm_device_name_.c_str());
}

void DmDefaultKeyTest::TearDown() { dm_->DeleteDevice(test_dm_device_name_); }

// Creates the test dm-default-key mapping using the given key and settings.
// If the dm device creation fails, then it is assumed the kernel doesn't
// support the given encryption settings, and a failure is not added.
bool DmDefaultKeyTest::CreateTestDevice(const std::string &cipher,
                                        const std::vector<uint8_t> &key,
                                        bool is_wrapped_key) {
  static_assert(kTestDataBytes % kDmApiSectorSize == 0);
  std::unique_ptr<DmTargetDefaultKey> target =
      std::make_unique<DmTargetDefaultKey>(0, kTestDataBytes / kDmApiSectorSize,
                                           cipher.c_str(), BytesToHex(key),
                                           raw_blk_device_, 0);
  target->SetSetDun();
  if (is_wrapped_key) target->SetWrappedKeyV0();

  DmTable table;
  if (!table.AddTarget(std::move(target))) {
    ADD_FAILURE() << "Failed to add default-key target to table";
    return false;
  }
  if (!table.valid()) {
    ADD_FAILURE() << "Device-mapper table failed to validate";
    return false;
  }
  if (!dm_->CreateDevice(test_dm_device_name_, table, &dm_device_path_,
                         std::chrono::seconds(5))) {
    GTEST_LOG_(INFO) << "Unable to create default-key mapping" << Errno()
                     << ".  Assuming that the encryption settings cipher=\""
                     << cipher << "\", is_wrapped_key=" << is_wrapped_key
                     << " are unsupported and skipping the test.";
    return false;
  }
  GTEST_LOG_(INFO) << "Created default-key mapping at " << dm_device_path_
                   << " using cipher=\"" << cipher
                   << "\", key=" << BytesToHex(key)
                   << ", is_wrapped_key=" << is_wrapped_key;
  return true;
}

void DmDefaultKeyTest::VerifyDecryption(const std::vector<uint8_t> &key,
                                        const Cipher &cipher) {
  std::vector<uint8_t> raw_data;
  std::vector<uint8_t> decrypted_data;

  ASSERT_TRUE(ReadBlockDevice(raw_blk_device_, kTestDataBytes, &raw_data));
  ASSERT_TRUE(
      ReadBlockDevice(dm_device_path_, kTestDataBytes, &decrypted_data));

  // Verify that the decrypted data encrypts to the raw data.

  GTEST_LOG_(INFO) << "Verifying correctness of decrypted data";

  // Initialize the IV for crypto sector 0.
  ASSERT_GE(cipher.ivsize(), sizeof(__le64));
  std::unique_ptr<__le64> iv(new (::operator new(cipher.ivsize())) __le64);
  memset(iv.get(), 0, cipher.ivsize());

  // Encrypt each sector.
  std::vector<uint8_t> encrypted_data(kTestDataBytes);
  static_assert(kTestDataBytes % kCryptoSectorSize == 0);
  for (size_t i = 0; i < kTestDataBytes; i += kCryptoSectorSize) {
    ASSERT_TRUE(cipher.Encrypt(key, reinterpret_cast<const uint8_t *>(iv.get()),
                               &decrypted_data[i], &encrypted_data[i],
                               kCryptoSectorSize));

    // Update the IV by incrementing the crypto sector number.
    *iv = cpu_to_le64(le64_to_cpu(*iv) + 1);
  }

  ASSERT_EQ(encrypted_data, raw_data);
}

void DmDefaultKeyTest::DoTest(const std::string &cipher_string,
                              const Cipher &cipher) {
  if (skip_test_) return;

  std::vector<uint8_t> key = GenerateTestKey(cipher.keysize());

  if (!CreateTestDevice(cipher_string, key, false)) return;

  VerifyDecryption(key, cipher);
}

// Tests dm-default-key parameters matching metadata_encryption=aes-256-xts.
TEST_F(DmDefaultKeyTest, TestAes256Xts) {
  DoTest("aes-xts-plain64", Aes256XtsCipher());
}

// Tests dm-default-key parameters matching metadata_encryption=adiantum.
TEST_F(DmDefaultKeyTest, TestAdiantum) {
  DoTest("xchacha12,aes-adiantum-plain64", AdiantumCipher());
}

// Tests dm-default-key parameters matching
// metadata_encryption=aes-256-xts:wrappedkey_v0.
TEST_F(DmDefaultKeyTest, TestHwWrappedKey) {
  if (skip_test_) return;

  std::vector<uint8_t> master_key, exported_key;
  if (!CreateHwWrappedKey(&master_key, &exported_key)) return;

  if (!CreateTestDevice("aes-xts-plain64", exported_key, true)) return;

  std::vector<uint8_t> enc_key;
  ASSERT_TRUE(DeriveHwWrappedEncryptionKey(master_key, &enc_key));

  VerifyDecryption(enc_key, Aes256XtsCipher());
}

// Tests that if the device uses metadata encryption, then the first
// kFilesystemBlockSize bytes of the userdata partition appear random.  For ext4
// and f2fs, this block should contain the filesystem superblock; it therefore
// should be initialized and metadata-encrypted.  Ideally we'd check additional
// blocks too, but that would require awareness of the filesystem structure.
//
// This isn't as strong a test as the correctness tests, but it's useful because
// it applies regardless of the encryption format and key.  Thus it runs even on
// old devices, including ones that used a vendor-specific encryption format.
TEST(MetadataEncryptionTest, TestRandomness) {
  constexpr const char *mountpoint = "/data";

  android::fs_mgr::Fstab fstab;
  ASSERT_TRUE(android::fs_mgr::ReadDefaultFstab(&fstab));
  const fs_mgr::FstabEntry *entry = GetEntryForMountPoint(&fstab, mountpoint);
  ASSERT_TRUE(entry != nullptr);

  if (entry->metadata_key_dir.empty()) {
    int first_api_level;
    ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
    ASSERT_LE(first_api_level, __ANDROID_API_Q__)
        << "Metadata encryption is required";
    GTEST_LOG_(INFO)
        << "Skipping test because device doesn't use metadata encryption";
    return;
  }

  GTEST_LOG_(INFO) << "Verifying randomness of ciphertext";
  std::vector<uint8_t> raw_data;
  FilesystemInfo fs_info;
  ASSERT_TRUE(GetFilesystemInfo(mountpoint, &fs_info));
  // The first block of the filesystem's main block device should always be
  // metadata-encrypted.
  ASSERT_TRUE(ReadBlockDevice(fs_info.disk_map[0].raw_blk_device,
                              kFilesystemBlockSize, &raw_data));
  ASSERT_TRUE(VerifyDataRandomness(raw_data));
}

}  // namespace kernel
}  // namespace android
