| // Copyright 2017 The Fuchsia 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 <inttypes.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <crypto/cipher.h> |
| #include <crypto/secret.h> |
| #include <unittest/unittest.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| #include <zxcrypt/volume.h> |
| |
| #include <utility> |
| |
| #include "test-device.h" |
| |
| namespace zxcrypt { |
| namespace testing { |
| namespace { |
| |
| // See test-device.h; the following macros allow reusing tests for each of the supported versions. |
| #define EACH_PARAM(OP, Test) OP(Test, Volume, AES256_XTS_SHA256) |
| |
| // ZX-1948: Dump extra information if encountering an unexpected error during volume creation. |
| bool VolumeCreate(const fbl::unique_fd& fd, const crypto::Secret& key, bool fvm, |
| zx_status_t expected) { |
| BEGIN_HELPER; |
| |
| char err[128]; |
| block_info_t bInfo; |
| ASSERT_GE(ioctl_block_get_info(fd.get(), &bInfo), 0); |
| if (fvm) { |
| fvm_info_t fInfo; |
| ASSERT_GE(ioctl_block_fvm_query(fd.get(), &fInfo), 0); |
| snprintf(err, sizeof(err), |
| "details: block size=%" PRIu32 ", block count=%" PRIu64 |
| ", slice size=%zu, slice count=%zu", |
| bInfo.block_size, bInfo.block_count, fInfo.slice_size, fInfo.vslice_count); |
| } else { |
| snprintf(err, sizeof(err), "details: block size=%" PRIu32 ", block count=%" PRIu64, |
| bInfo.block_size, bInfo.block_count); |
| } |
| |
| fbl::unique_fd new_fd(dup(fd.get())); |
| EXPECT_EQ(Volume::Create(std::move(new_fd), key), expected, err); |
| |
| END_HELPER; |
| } |
| |
| bool TestInit(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| |
| TestDevice device; |
| ASSERT_TRUE(device.Create(kDeviceSize, kBlockSize, fvm)); |
| |
| // Invalid arguments |
| fbl::unique_fd bad_fd; |
| fbl::unique_ptr<Volume> volume; |
| EXPECT_ZX(Volume::Init(std::move(bad_fd), &volume), ZX_ERR_INVALID_ARGS); |
| EXPECT_ZX(Volume::Init(device.parent(), nullptr), ZX_ERR_INVALID_ARGS); |
| |
| // Valid |
| EXPECT_ZX(Volume::Init(device.parent(), &volume), ZX_OK); |
| ASSERT_TRUE(!!volume); |
| EXPECT_EQ(volume->reserved_blocks(), fvm ? (FVM_BLOCK_SIZE / kBlockSize) : 2u); |
| EXPECT_EQ(volume->reserved_slices(), fvm ? 1u : 0u); |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestInit); |
| |
| bool TestCreate(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| |
| TestDevice device; |
| ASSERT_TRUE(device.Create(kDeviceSize, kBlockSize, fvm)); |
| |
| // Invalid file descriptor |
| fbl::unique_fd bad_fd; |
| EXPECT_ZX(Volume::Create(std::move(bad_fd), device.key()), ZX_ERR_INVALID_ARGS); |
| |
| // Weak key |
| crypto::Secret short_key; |
| ASSERT_OK(short_key.Generate(device.key().len() - 1)); |
| EXPECT_TRUE(VolumeCreate(device.parent(), short_key, fvm, ZX_ERR_INVALID_ARGS)); |
| |
| // Valid |
| EXPECT_TRUE(VolumeCreate(device.parent(), device.key(), fvm, ZX_OK)); |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestCreate); |
| |
| bool TestUnlock(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| |
| TestDevice device; |
| ASSERT_TRUE(device.Create(kDeviceSize, kBlockSize, fvm)); |
| |
| // Invalid device |
| fbl::unique_ptr<Volume> volume; |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), 0, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| |
| // Bad file descriptor |
| fbl::unique_fd bad_fd; |
| EXPECT_ZX(Volume::Unlock(std::move(bad_fd), device.key(), 0, &volume), ZX_ERR_INVALID_ARGS); |
| |
| // Bad key |
| ASSERT_TRUE(VolumeCreate(device.parent(), device.key(), fvm, ZX_OK)); |
| |
| crypto::Secret bad_key; |
| ASSERT_OK(bad_key.Generate(device.key().len())); |
| EXPECT_ZX(Volume::Unlock(device.parent(), bad_key, 0, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| |
| // Bad slot |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), -1, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), 1, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| |
| // Valid |
| EXPECT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| |
| // Corrupt the key in each block. |
| fbl::unique_fd parent = device.parent(); |
| off_t off = 0; |
| uint8_t before[kBlockSize]; |
| uint8_t after[sizeof(before)]; |
| const size_t num_blocks = volume->reserved_blocks(); |
| |
| for (size_t i = 0; i < num_blocks; ++i) { |
| // On FVM, the trailing reserved blocks may just be to pad to a slice, and not have any |
| // metdata. Start from the end and iterate backward to ensure the last block corrupted has |
| // metadata. |
| ASSERT_TRUE(device.Corrupt(num_blocks - 1 - i, 0)); |
| lseek(parent.get(), off, SEEK_SET); |
| read(parent.get(), before, sizeof(before)); |
| |
| if (i < num_blocks - 1) { |
| // Volume should still be unlockable as long as one copy of the key exists |
| EXPECT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| } else { |
| // Key should fail when last copy is corrupted. |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), 0, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| } |
| |
| lseek(parent.get(), off, SEEK_SET); |
| read(parent.get(), after, sizeof(after)); |
| |
| // Unlock should not modify the parent |
| EXPECT_EQ(memcmp(before, after, sizeof(before)), 0); |
| } |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestUnlock); |
| |
| bool TestEnroll(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| TestDevice device; |
| ASSERT_TRUE(device.Bind(version, fvm)); |
| |
| fbl::unique_ptr<Volume> volume; |
| ASSERT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| |
| // Bad key |
| crypto::Secret bad_key; |
| EXPECT_ZX(volume->Enroll(bad_key, 1), ZX_ERR_INVALID_ARGS); |
| |
| // Bad slot |
| EXPECT_ZX(volume->Enroll(device.key(), volume->num_slots()), ZX_ERR_INVALID_ARGS); |
| |
| // Valid; new slot |
| EXPECT_OK(volume->Enroll(device.key(), 1)); |
| EXPECT_OK(Volume::Unlock(device.parent(), device.key(), 1, &volume)); |
| |
| // Valid; existing slot |
| EXPECT_OK(volume->Enroll(device.key(), 0)); |
| EXPECT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestEnroll); |
| |
| bool TestRevoke(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| |
| TestDevice device; |
| ASSERT_TRUE(device.Bind(version, fvm)); |
| |
| fbl::unique_ptr<Volume> volume; |
| ASSERT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| |
| // Bad slot |
| EXPECT_ZX(volume->Revoke(volume->num_slots()), ZX_ERR_INVALID_ARGS); |
| |
| // Valid, even if slot isn't enrolled |
| EXPECT_OK(volume->Revoke(volume->num_slots() - 1)); |
| |
| // Valid, even if last slot |
| EXPECT_OK(volume->Revoke(0)); |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), 0, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestRevoke); |
| |
| bool TestShred(Volume::Version version, bool fvm) { |
| BEGIN_TEST; |
| |
| TestDevice device; |
| ASSERT_TRUE(device.Bind(version, fvm)); |
| |
| fbl::unique_ptr<Volume> volume; |
| ASSERT_OK(Volume::Unlock(device.parent(), device.key(), 0, &volume)); |
| |
| // Valid |
| EXPECT_OK(volume->Shred()); |
| |
| // No further methods work |
| EXPECT_ZX(volume->Enroll(device.key(), 0), ZX_ERR_BAD_STATE); |
| EXPECT_ZX(volume->Revoke(0), ZX_ERR_BAD_STATE); |
| EXPECT_ZX(Volume::Unlock(device.parent(), device.key(), 0, &volume), |
| ZX_ERR_ACCESS_DENIED); |
| |
| END_TEST; |
| } |
| DEFINE_EACH_DEVICE(TestShred); |
| |
| BEGIN_TEST_CASE(VolumeTest) |
| RUN_EACH_DEVICE(TestInit) |
| RUN_EACH_DEVICE(TestCreate) |
| RUN_EACH_DEVICE(TestUnlock) |
| RUN_EACH_DEVICE(TestEnroll) |
| RUN_EACH_DEVICE(TestRevoke) |
| RUN_EACH_DEVICE(TestShred) |
| END_TEST_CASE(VolumeTest) |
| |
| } // namespace |
| } // namespace testing |
| } // namespace zxcrypt |