blob: bf831371c258e8ad64d79738f9f7302febd96630 [file]
/*
* 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 file contents encryption is producing the correct ciphertext
// on-disk. This is useful for verifying that vendors' inline encryption
// hardware is working correctly, for example.
//
// This test checks fscrypt policies equivalent to the following fstab settings:
//
// fileencryption=aes-256-xts:aes-256-cts:v2
// fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized
// fileencryption=adiantum:adiantum:v2
//
// On devices launching with R or higher those are equivalent to simply:
//
// fileencryption=aes-256-xts
// fileencryption=aes-256-xts:aes-256-cts:inlinecrypt_optimized
// fileencryption=adiantum
//
// This test doesn't currently check which one of those settings, if any, the
// device is actually using; it just tries to test everything it can.
// "fileencryption=aes-256-xts" is guaranteed to be available if the kernel
// supports any "fscrypt v2" features at all. The others may not be available,
// so this test takes that into account and skips testing them when unavailable.
//
// This test doesn't currently test hardware-wrapped keys ("wrappedkey_v0").
//
// This test should never fail. In particular, vendors must not break any
// standard fscrypt functionality, regardless of what the device actually uses.
// If it does fail, make sure to check things like the byte order of keys.
//
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <errno.h>
#include <ext4_utils/ext4.h>
#include <ext4_utils/ext4_sb.h>
#include <ext4_utils/ext4_utils.h>
#include <fcntl.h>
#include <gtest/gtest.h>
#include <limits.h>
#include <linux/fiemap.h>
#include <linux/fs.h>
#include <linux/fscrypt.h>
#include <linux/magic.h>
#include <mntent.h>
#include <openssl/evp.h>
#include <openssl/hkdf.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "vts_kernel_encryption.h"
#ifndef F2FS_IOCTL_MAGIC
#define F2FS_IOCTL_MAGIC 0xf5
#endif
#ifndef F2FS_IOC_SET_PIN_FILE
#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
#endif
#ifndef FS_IOC_GET_ENCRYPTION_NONCE
#define FS_IOC_GET_ENCRYPTION_NONCE _IOR('f', 27, __u8[16])
#endif
namespace android {
namespace kernel {
// Location of the test directory and file. Since it's not possible to override
// an existing encryption policy, in order for this test to set its own
// encryption policy the parent directory must be unencrypted.
constexpr const char *kTestMountpoint = "/data";
constexpr const char *kTestDir = "/data/unencrypted/vts-test-dir";
constexpr const char *kTestFile = "/data/unencrypted/vts-test-dir/file";
// Assumed size of filesystem blocks, in bytes
constexpr int kFilesystemBlockSize = 4096;
// Size of the test file in filesystem blocks
constexpr int kTestFileBlocks = 256;
// Size of the test file in bytes
constexpr int kTestFileBytes = kFilesystemBlockSize * kTestFileBlocks;
// Size of a filesystem UUID, in bytes
constexpr int kFilesystemUuidSize = 16;
// Offset in bytes to the filesystem superblock, relative to the beginning of
// the block device
constexpr int kExt4SuperBlockOffset = 1024;
constexpr int kF2fsSuperBlockOffset = 1024;
// For F2FS: the offsets in bytes to the filesystem magic number and filesystem
// UUID, relative to the beginning of the block device
constexpr int kF2fsMagicOffset = kF2fsSuperBlockOffset;
constexpr int kF2fsUuidOffset = kF2fsSuperBlockOffset + 108;
// fscrypt master key size in bytes
constexpr int kFscryptMasterKeySize = 64;
// fscrypt maximum IV size in bytes
constexpr int kFscryptMaxIVSize = 32;
// fscrypt per-file nonce size in bytes
constexpr int kFscryptFileNonceSize = 16;
// fscrypt HKDF context bytes, from kernel fs/crypto/fscrypt_private.h
enum FscryptHkdfContext {
HKDF_CONTEXT_KEY_IDENTIFIER = 1,
HKDF_CONTEXT_PER_FILE_ENC_KEY = 2,
HKDF_CONTEXT_DIRECT_KEY = 3,
HKDF_CONTEXT_IV_INO_LBLK_64_KEY = 4,
HKDF_CONTEXT_DIRHASH_KEY = 5,
};
struct FilesystemUuid {
uint8_t bytes[kFilesystemUuidSize];
};
struct FscryptFileNonce {
uint8_t bytes[kFscryptFileNonceSize];
};
// Format of the initialization vector
union FscryptIV {
struct {
__le32 lblk_num; // file logical block number, starts at 0
__le32 inode_number; // only used for IV_INO_LBLK_64
u8 file_nonce[kFscryptFileNonceSize]; // only used for DIRECT_KEY
};
u8 bytes[kFscryptMaxIVSize];
};
struct TestFileInfo {
std::vector<uint8_t> plaintext;
std::vector<uint8_t> actual_ciphertext;
uint64_t inode_number;
FscryptFileNonce nonce;
};
// Given a mountpoint, gets the corresponding block device and filesystem type
// from /proc/mounts. This block device is the one on which the filesystem is
// directly located. In the case of device-mapper that means something like
// /dev/mapper/dm-5, not the underlying device like /dev/block/by-name/userdata.
static bool GetFsBlockDeviceAndType(const std::string &mountpoint,
std::string *fs_blk_device,
std::string *fs_type) {
std::unique_ptr<FILE, int (*)(FILE *)> mnts(setmntent("/proc/mounts", "re"),
endmntent);
if (!mnts) {
ADD_FAILURE() << "Failed to open /proc/mounts" << Errno();
return false;
}
struct mntent *mnt;
while ((mnt = getmntent(mnts.get())) != nullptr) {
if (mnt->mnt_dir == mountpoint) {
*fs_blk_device = mnt->mnt_fsname;
*fs_type = mnt->mnt_type;
GTEST_LOG_(INFO) << kTestMountpoint << " is " << *fs_blk_device
<< " mounted with type " << *fs_type;
return true;
}
}
ADD_FAILURE() << "No /proc/mounts entry found for " << mountpoint;
return false;
}
//
// Checks whether the kernel has support for the following fscrypt features:
//
// - Filesystem-level keyring (FS_IOC_ADD_ENCRYPTION_KEY and
// FS_IOC_REMOVE_ENCRYPTION_KEY)
// - v2 encryption policies
// - The IV_INO_LBLK_64 encryption policy flag
// - The FS_IOC_GET_ENCRYPTION_NONCE ioctl
//
// To do this it's sufficient to just check whether FS_IOC_ADD_ENCRYPTION_KEY is
// available, as the other features were added in the same AOSP release.
//
// The easiest way to do this is to just execute the ioctl with a NULL argument.
// If available it will fail with EFAULT; otherwise it will fail with ENOTTY.
//
static bool IsFscryptV2Supported() {
android::base::unique_fd fd(
open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
if (fd < 0) {
ADD_FAILURE() << "Failed to open " << kTestMountpoint << Errno();
return false;
}
if (ioctl(fd, FS_IOC_ADD_ENCRYPTION_KEY, nullptr) == 0) {
ADD_FAILURE()
<< "FS_IOC_ADD_ENCRYPTION_KEY(nullptr) unexpectedly succeeded on "
<< kTestMountpoint;
return false;
}
switch (errno) {
case EFAULT:
return true;
case ENOTTY:
GTEST_LOG_(INFO) << "No support for FS_IOC_ADD_ENCRYPTION_KEY on "
<< kTestMountpoint;
return false;
default:
ADD_FAILURE()
<< "Unexpected error from FS_IOC_ADD_ENCRYPTION_KEY(nullptr) on "
<< kTestMountpoint << Errno();
return false;
}
}
// TODO(ebiggers): remove this check once kernel patches have landed in AOSP
static bool IsGetFscryptNonceIoctlSupported() {
android::base::unique_fd fd(
open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
errno = 0;
ioctl(fd, FS_IOC_GET_ENCRYPTION_NONCE, nullptr);
return errno != ENOTTY;
}
// Helper class to pin / unpin a file on f2fs, to prevent f2fs from moving the
// file's blocks while the test is accessing them via the underlying device.
//
// This can be used without checking the filesystem type, since on other
// filesystem types F2FS_IOC_SET_PIN_FILE will just fail and do nothing.
class ScopedF2fsFilePinning {
public:
explicit ScopedF2fsFilePinning(int fd) : fd_(fd) {
__u32 set = 1;
ioctl(fd_, F2FS_IOC_SET_PIN_FILE, &set);
}
~ScopedF2fsFilePinning() {
__u32 set = 0;
ioctl(fd_, F2FS_IOC_SET_PIN_FILE, &set);
}
private:
int fd_;
};
// Reads the raw data of the file specified by |fd| from its underlying block
// device |blk_device|. The file is |expected_file_size| bytes long; this is
// assumed to be a multiple of the filesystem block size kFilesystemBlockSize.
static bool ReadRawDataOfFile(int fd, const std::string &blk_device,
int expected_file_size,
std::vector<uint8_t> *raw_data) {
int max_extents = expected_file_size / kFilesystemBlockSize;
EXPECT_TRUE(expected_file_size % kFilesystemBlockSize == 0);
// It's not entirely clear how F2FS_IOC_SET_PIN_FILE interacts with dirty
// data, so do an extra sync here and don't just rely on FIEMAP_FLAG_SYNC.
if (fsync(fd) != 0) {
ADD_FAILURE() << "Failed to sync file" << Errno();
return false;
}
ScopedF2fsFilePinning pinned_file(fd); // no-op on non-f2fs
// Query the file's extents.
size_t allocsize = offsetof(struct fiemap, fm_extents[max_extents]);
std::unique_ptr<struct fiemap> map(
new (::operator new(allocsize)) struct fiemap);
memset(map.get(), 0, allocsize);
map->fm_flags = FIEMAP_FLAG_SYNC;
map->fm_length = expected_file_size;
map->fm_extent_count = max_extents;
if (ioctl(fd, FS_IOC_FIEMAP, map.get()) != 0) {
ADD_FAILURE() << "Failed to get extents of file" << Errno();
return false;
}
// Read the raw data, using direct I/O to avoid getting any stale cached data.
// Direct I/O requires using a block size aligned buffer.
std::unique_ptr<void, void (*)(void *)> buf_mem(
aligned_alloc(kFilesystemBlockSize, expected_file_size), free);
if (buf_mem == nullptr) {
ADD_FAILURE() << "Out of memory";
return false;
}
uint8_t *buf = static_cast<uint8_t *>(buf_mem.get());
int offset = 0;
android::base::unique_fd blk_fd(
open(blk_device.c_str(), O_RDONLY | O_DIRECT | O_CLOEXEC));
if (blk_fd < 0) {
ADD_FAILURE() << "Failed to open raw block device " << blk_device
<< Errno();
return false;
}
for (int i = 0; i < map->fm_mapped_extents; i++) {
const struct fiemap_extent &extent = map->fm_extents[i];
GTEST_LOG_(INFO) << "Extent " << i + 1 << " of " << map->fm_mapped_extents
<< " is logical offset " << extent.fe_logical
<< ", physical offset " << extent.fe_physical
<< ", length " << extent.fe_length << ", flags 0x"
<< std::hex << extent.fe_flags << std::dec;
// Make sure the flags indicate that fe_physical is actually valid.
if (extent.fe_flags & (FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN)) {
ADD_FAILURE() << "Unsupported extent flags: 0x" << std::hex
<< extent.fe_flags << std::dec;
return false;
}
if (extent.fe_length % kFilesystemBlockSize != 0) {
ADD_FAILURE() << "Extent is not aligned to filesystem block size";
return false;
}
if (extent.fe_length > expected_file_size - offset) {
ADD_FAILURE() << "File is longer than expected";
return false;
}
if (pread(blk_fd, &buf[offset], extent.fe_length, extent.fe_physical) !=
extent.fe_length) {
ADD_FAILURE() << "Error reading raw data from block device" << Errno();
return false;
}
offset += extent.fe_length;
}
if (offset != expected_file_size) {
ADD_FAILURE() << "File is shorter than expected";
return false;
}
*raw_data = std::vector<uint8_t>(&buf[0], &buf[offset]);
return true;
}
class FileBasedEncryptionTest : public ::testing::Test {
protected:
void SetUp() override;
void TearDown() override;
void RemoveTestDirectory();
bool FindFilesystemTypeAndUuid();
bool SetEncryptionPolicy(int contents_mode, int filenames_mode, int flags,
bool required);
bool GenerateTestFile(TestFileInfo *info);
bool DeriveEncryptionKey(const std::vector<uint8_t> &hdkf_info,
std::vector<uint8_t> &enc_key);
bool DerivePerModeEncryptionKey(int mode, FscryptHkdfContext context,
std::vector<uint8_t> &enc_key);
bool DerivePerFileEncryptionKey(const FscryptFileNonce &nonce,
std::vector<uint8_t> &enc_key);
void VerifyCiphertext(const std::vector<uint8_t> &enc_key,
const FscryptIV &starting_iv, const Cipher &cipher,
const TestFileInfo &file_info);
std::vector<uint8_t> master_key_;
struct fscrypt_key_specifier master_key_specifier_;
bool skip_test_ = false;
bool key_added_ = false;
std::string raw_partition_;
std::string fs_type_;
FilesystemUuid fs_uuid_;
};
// Test setup procedure. Creates a test directory kTestDir, generates and adds
// an encryption key to kTestMountpoint, and does other preparations.
// skip_test_ is set to true if the test should be skipped.
void FileBasedEncryptionTest::SetUp() {
if (!IsFscryptV2Supported()) {
int first_api_level;
ASSERT_TRUE(GetFirstApiLevel(&first_api_level));
// Devices launching with R or higher must support fscrypt v2.
ASSERT_LE(first_api_level, __ANDROID_API_Q__);
GTEST_LOG_(INFO) << "Skipping test because fscrypt v2 is unsupported";
skip_test_ = true;
return;
}
// TODO(ebiggers): remove this check once kernel patches have landed in AOSP
if (!IsGetFscryptNonceIoctlSupported()) {
GTEST_LOG_(INFO)
<< "Skipping test because FS_IOC_GET_ENCRYPTION_NONCE is unsupported";
skip_test_ = true;
return;
}
ASSERT_TRUE(FindFilesystemTypeAndUuid());
ASSERT_TRUE(FindRawPartition(kTestMountpoint, &raw_partition_));
RemoveTestDirectory();
if (mkdir(kTestDir, 0700) != 0) {
FAIL() << "Failed to create " << kTestDir << Errno();
}
// Generate an fscrypt master key and add it to kTestMountpoint.
// This gives us back the key identifier to use in the encryption policy.
master_key_ = GenerateTestKey(kFscryptMasterKeySize);
size_t allocsize = sizeof(struct fscrypt_add_key_arg) + master_key_.size();
std::unique_ptr<struct fscrypt_add_key_arg> arg(
new (::operator new(allocsize)) struct fscrypt_add_key_arg);
memset(arg.get(), 0, allocsize);
arg->key_spec.type = FSCRYPT_KEY_SPEC_TYPE_IDENTIFIER;
arg->raw_size = master_key_.size();
std::copy(master_key_.begin(), master_key_.end(), arg->raw);
GTEST_LOG_(INFO) << "Adding fscrypt master key, raw bytes are "
<< BytesToHex(master_key_);
android::base::unique_fd mntfd(
open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
if (mntfd < 0) {
FAIL() << "Failed to open " << kTestMountpoint << Errno();
}
if (ioctl(mntfd, FS_IOC_ADD_ENCRYPTION_KEY, arg.get()) != 0) {
FAIL() << "FS_IOC_ADD_ENCRYPTION_KEY failed on " << kTestMountpoint
<< Errno();
}
master_key_specifier_ = arg->key_spec;
GTEST_LOG_(INFO) << "Master key identifier is "
<< BytesToHex(master_key_specifier_.u.identifier);
key_added_ = true;
}
void FileBasedEncryptionTest::TearDown() {
RemoveTestDirectory();
// Remove the test key from kTestMountpoint.
if (key_added_) {
android::base::unique_fd mntfd(
open(kTestMountpoint, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
if (mntfd < 0) {
FAIL() << "Failed to open " << kTestMountpoint << Errno();
}
struct fscrypt_remove_key_arg arg;
memset(&arg, 0, sizeof(arg));
arg.key_spec = master_key_specifier_;
if (ioctl(mntfd, FS_IOC_REMOVE_ENCRYPTION_KEY, &arg) != 0) {
FAIL() << "FS_IOC_REMOVE_ENCRYPTION_KEY failed on " << kTestMountpoint
<< Errno();
}
}
}
void FileBasedEncryptionTest::RemoveTestDirectory() {
if (unlink(kTestFile) != 0 && errno != ENOENT && errno != ENOPKG) {
FAIL() << "Failed to remove file " << kTestFile << Errno();
}
if (rmdir(kTestDir) != 0 && errno != ENOENT) {
FAIL() << "Failed to remove directory " << kTestDir << Errno();
}
}
// Finds the type and UUID of the filesystem mounted on kTestMountpoint.
//
// Unfortunately there's no kernel API to get the UUID; instead we have to read
// it from the filesystem superblock.
bool FileBasedEncryptionTest::FindFilesystemTypeAndUuid() {
std::string fs_blk_device;
if (!GetFsBlockDeviceAndType(kTestMountpoint, &fs_blk_device, &fs_type_)) {
ADD_FAILURE() << "Failed to find filesystem block device and type";
return false;
}
android::base::unique_fd fd(
open(fs_blk_device.c_str(), O_RDONLY | O_CLOEXEC));
if (fd < 0) {
ADD_FAILURE() << "Failed to open fs block device " << fs_blk_device
<< Errno();
return false;
}
if (fs_type_ == "ext4") {
struct ext4_super_block sb;
if (pread(fd, &sb, sizeof(sb), kExt4SuperBlockOffset) != sizeof(sb)) {
ADD_FAILURE() << "Error reading ext4 superblock from " << fs_blk_device
<< Errno();
return false;
}
if (sb.s_magic != cpu_to_le16(EXT4_SUPER_MAGIC)) {
ADD_FAILURE() << "Failed to find ext4 superblock on " << fs_blk_device;
return false;
}
static_assert(sizeof(sb.s_uuid) == kFilesystemUuidSize);
memcpy(fs_uuid_.bytes, sb.s_uuid, kFilesystemUuidSize);
} else if (fs_type_ == "f2fs") {
// Android doesn't have an f2fs equivalent of libext4_utils, so we have to
// hard-code the offset to the magic number and UUID.
__le32 magic;
if (pread(fd, &magic, sizeof(magic), kF2fsMagicOffset) != sizeof(magic)) {
ADD_FAILURE() << "Error reading f2fs superblock from " << fs_blk_device
<< Errno();
return false;
}
if (magic != cpu_to_le32(F2FS_SUPER_MAGIC)) {
ADD_FAILURE() << "Failed to find f2fs superblock on " << fs_blk_device;
return false;
}
if (pread(fd, fs_uuid_.bytes, kFilesystemUuidSize, kF2fsUuidOffset) !=
kFilesystemUuidSize) {
ADD_FAILURE() << "Failed to read f2fs filesystem UUID from "
<< fs_blk_device << Errno();
return false;
}
} else {
ADD_FAILURE() << "Unknown filesystem type " << fs_type_;
return false;
}
GTEST_LOG_(INFO) << "Filesystem UUID is " << BytesToHex(fs_uuid_.bytes);
return true;
}
// Sets a v2 encryption policy on the test directory. The policy will use the
// test key and the specified encryption modes and flags. If required=false,
// then a failure won't be added if the kernel doesn't support the policy.
bool FileBasedEncryptionTest::SetEncryptionPolicy(int contents_mode,
int filenames_mode, int flags,
bool required) {
struct fscrypt_policy_v2 policy;
memset(&policy, 0, sizeof(policy));
policy.version = FSCRYPT_POLICY_V2;
policy.contents_encryption_mode = contents_mode;
policy.filenames_encryption_mode = filenames_mode;
// Always give PAD_16, to match the policies that Android sets for real.
// It doesn't affect contents encryption, though.
policy.flags = flags | FSCRYPT_POLICY_FLAGS_PAD_16;
memcpy(policy.master_key_identifier, master_key_specifier_.u.identifier,
FSCRYPT_KEY_IDENTIFIER_SIZE);
android::base::unique_fd dirfd(
open(kTestDir, O_RDONLY | O_DIRECTORY | O_CLOEXEC));
if (dirfd < 0) {
ADD_FAILURE() << "Failed to open " << kTestDir << Errno();
return false;
}
GTEST_LOG_(INFO) << "Setting encryption policy on " << kTestDir;
if (ioctl(dirfd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) != 0) {
if (errno == EINVAL && !required) {
GTEST_LOG_(INFO) << "Skipping test because encryption policy is "
"unsupported on this filesystem / kernel";
return false;
}
ADD_FAILURE() << "FS_IOC_SET_ENCRYPTION_POLICY failed on " << kTestDir
<< " using contents_mode=" << contents_mode
<< ", filenames_mode=" << filenames_mode << ", flags=0x"
<< std::hex << flags << std::dec << Errno();
return false;
}
if (!required) {
// Setting an encryption policy that uses modes that aren't enabled in the
// kernel's crypto API (e.g. FSCRYPT_MODE_ADIANTUM when the kernel lacks
// CONFIG_CRYPTO_ADIANTUM) will still succeed, but actually creating a file
// will fail with ENOPKG. Make sure to check for this case.
android::base::unique_fd fd(
open(kTestFile, O_WRONLY | O_CREAT | O_CLOEXEC, 0600));
if (fd < 0 && errno == ENOPKG) {
GTEST_LOG_(INFO)
<< "Skipping test because encryption policy is "
"unsupported on this kernel, due to missing crypto API support";
return false;
}
unlink(kTestFile);
}
return true;
}
// Generates some test data, writes it to a file in the test directory, and
// returns in |info| the file's plaintext, the file's raw ciphertext read from
// disk, and other information about the file.
bool FileBasedEncryptionTest::GenerateTestFile(TestFileInfo *info) {
// Generate the test data.
info->plaintext.resize(kTestFileBytes);
RandomBytesForTesting(info->plaintext);
// Write the test data to the file.
GTEST_LOG_(INFO) << "Creating test file " << kTestFile << " containing "
<< kTestFileBytes << " bytes of data (" << kTestFileBlocks
<< " blocks)";
android::base::unique_fd fd(
open(kTestFile, O_WRONLY | O_CREAT | O_CLOEXEC, 0600));
if (fd < 0) {
ADD_FAILURE() << "Failed to create " << kTestFile << Errno();
return false;
}
if (!android::base::WriteFully(fd, info->plaintext.data(),
info->plaintext.size())) {
ADD_FAILURE() << "Error writing to " << kTestFile << Errno();
return false;
}
// Get the file's inode number.
struct stat stbuf;
if (fstat(fd, &stbuf) != 0) {
ADD_FAILURE() << "Failed to stat " << kTestFile << Errno();
return false;
}
info->inode_number = stbuf.st_ino;
GTEST_LOG_(INFO) << "Inode number: " << info->inode_number;
// Get the file's nonce.
if (ioctl(fd, FS_IOC_GET_ENCRYPTION_NONCE, info->nonce.bytes) != 0) {
ADD_FAILURE() << "FS_IOC_GET_ENCRYPTION_NONCE failed on " << kTestFile
<< Errno();
return false;
}
GTEST_LOG_(INFO) << "File nonce: " << BytesToHex(info->nonce.bytes);
// Read the file's raw ciphertext.
GTEST_LOG_(INFO) << "Reading the raw ciphertext from disk";
if (!ReadRawDataOfFile(fd, raw_partition_, kTestFileBytes,
&info->actual_ciphertext)) {
ADD_FAILURE() << "Failed to read the raw ciphertext";
return false;
}
return true;
}
static std::vector<uint8_t> InitHkdfInfo(FscryptHkdfContext context) {
return {
'f', 's', 'c', 'r', 'y', 'p', 't', '\0', static_cast<uint8_t>(context)};
}
bool FileBasedEncryptionTest::DeriveEncryptionKey(
const std::vector<uint8_t> &hkdf_info, std::vector<uint8_t> &out) {
if (HKDF(out.data(), out.size(), EVP_sha512(), master_key_.data(),
master_key_.size(), nullptr, 0, hkdf_info.data(),
hkdf_info.size()) != 1) {
ADD_FAILURE() << "BoringSSL HKDF-SHA512 call failed";
return false;
}
GTEST_LOG_(INFO) << "Derived encryption key " << BytesToHex(out)
<< " using HKDF info " << BytesToHex(hkdf_info);
return true;
}
// Derives a per-mode encryption key from the master key, |mode|, |context|, and
// (if needed for the context) the filesystem UUID.
bool FileBasedEncryptionTest::DerivePerModeEncryptionKey(
int mode, FscryptHkdfContext context, std::vector<uint8_t> &enc_key) {
std::vector<uint8_t> hkdf_info = InitHkdfInfo(context);
hkdf_info.push_back(mode);
if (context == HKDF_CONTEXT_IV_INO_LBLK_64_KEY)
hkdf_info.insert(hkdf_info.end(), fs_uuid_.bytes, std::end(fs_uuid_.bytes));
return DeriveEncryptionKey(hkdf_info, enc_key);
}
// Derives a per-file encryption key from the master key and |nonce|.
bool FileBasedEncryptionTest::DerivePerFileEncryptionKey(
const FscryptFileNonce &nonce, std::vector<uint8_t> &enc_key) {
std::vector<uint8_t> hkdf_info = InitHkdfInfo(HKDF_CONTEXT_PER_FILE_ENC_KEY);
hkdf_info.insert(hkdf_info.end(), nonce.bytes, std::end(nonce.bytes));
return DeriveEncryptionKey(hkdf_info, enc_key);
}
void FileBasedEncryptionTest::VerifyCiphertext(
const std::vector<uint8_t> &enc_key, const FscryptIV &starting_iv,
const Cipher &cipher, const TestFileInfo &file_info) {
const std::vector<uint8_t> &plaintext = file_info.plaintext;
GTEST_LOG_(INFO) << "Verifying correctness of encrypted data";
FscryptIV iv = starting_iv;
std::vector<uint8_t> computed_ciphertext(plaintext.size());
// Encrypt each filesystem block of file contents.
for (size_t i = 0; i < plaintext.size(); i += kFilesystemBlockSize) {
int block_size =
std::min<size_t>(kFilesystemBlockSize, plaintext.size() - i);
ASSERT_GE(sizeof(iv.bytes), cipher.ivsize());
ASSERT_TRUE(cipher.Encrypt(enc_key, iv.bytes, &plaintext[i],
&computed_ciphertext[i], block_size));
// Update the IV by incrementing the file logical block number.
iv.lblk_num = cpu_to_le32(le32_to_cpu(iv.lblk_num) + 1);
ASSERT_NE(le32_to_cpu(iv.lblk_num), 0);
}
ASSERT_EQ(file_info.actual_ciphertext, computed_ciphertext);
}
// Tests a policy matching fileencryption=aes-256-xts:aes-256-cts:v2
// (or simply fileencryption=aes-256-xts on devices launched with R or higher)
TEST_F(FileBasedEncryptionTest, TestAesV2Policy) {
if (skip_test_) return;
if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
0, true))
return;
TestFileInfo file_info;
if (!GenerateTestFile(&file_info)) return;
std::vector<uint8_t> enc_key(kAes256XtsKeySize);
if (!DerivePerFileEncryptionKey(file_info.nonce, enc_key)) return;
FscryptIV iv;
memset(&iv, 0, sizeof(iv));
VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
}
// Tests a policy matching
// fileencryption=aes-256-xts:aes-256-cts:v2+inlinecrypt_optimized
// (or simply fileencryption=aes-256-xts:aes-256-cts:inlinecrypt_optimized on
// devices launched with R or higher)
TEST_F(FileBasedEncryptionTest, TestAesV2InlineCryptOptimizedPolicy) {
if (skip_test_) return;
// On ext4, FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64 is only supported when the
// filesystem has EXT4_FEATURE_COMPAT_STABLE_INODES, which only happens when
// inlinecrypt_optimized is selected in the fstab. So we don't require
// setting this type of policy to work on ext4.
if (!SetEncryptionPolicy(FSCRYPT_MODE_AES_256_XTS, FSCRYPT_MODE_AES_256_CTS,
FSCRYPT_POLICY_FLAG_IV_INO_LBLK_64,
fs_type_ != "ext4"))
return;
TestFileInfo file_info;
if (!GenerateTestFile(&file_info)) return;
std::vector<uint8_t> enc_key(kAes256XtsKeySize);
if (!DerivePerModeEncryptionKey(FSCRYPT_MODE_AES_256_XTS,
HKDF_CONTEXT_IV_INO_LBLK_64_KEY, enc_key))
return;
FscryptIV iv;
memset(&iv, 0, sizeof(iv));
ASSERT_LE(file_info.inode_number, UINT32_MAX);
iv.inode_number = cpu_to_le32(file_info.inode_number);
VerifyCiphertext(enc_key, iv, Aes256XtsCipher(), file_info);
}
// Tests a policy matching fileencryption=adiantum:adiantum:v2 (or simply
// fileencryption=adiantum on devices launched with R or higher)
TEST_F(FileBasedEncryptionTest, TestAdiantumV2Policy) {
if (skip_test_) return;
// Adiantum support isn't required (since CONFIG_CRYPTO_ADIANTUM can be unset
// in the kernel config), so we may skip the test here.
if (!SetEncryptionPolicy(FSCRYPT_MODE_ADIANTUM, FSCRYPT_MODE_ADIANTUM,
FSCRYPT_POLICY_FLAG_DIRECT_KEY, false))
return;
TestFileInfo file_info;
if (!GenerateTestFile(&file_info)) return;
std::vector<uint8_t> enc_key(kAdiantumKeySize);
if (!DerivePerModeEncryptionKey(FSCRYPT_MODE_ADIANTUM,
HKDF_CONTEXT_DIRECT_KEY, enc_key))
return;
FscryptIV iv;
memset(&iv, 0, sizeof(iv));
memcpy(iv.file_nonce, file_info.nonce.bytes, kFscryptFileNonceSize);
VerifyCiphertext(enc_key, iv, AdiantumCipher(), file_info);
}
} // namespace kernel
} // namespace android