blob: 27fd9057c8df8f8c9f37ae326202359b180953b2 [file] [log] [blame]
// 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 <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <lib/zircon-internal/debug.h>
#include <stddef.h>
#include <stdint.h>
#include <zircon/compiler.h>
#include <zircon/device/block.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <algorithm>
#include <fbl/algorithm.h>
#include "src/security/fcrypto/bytes.h"
#include "src/security/fcrypto/cipher.h"
#include "src/security/fcrypto/hkdf.h"
#include "src/security/fcrypto/secret.h"
#include "src/security/zxcrypt/volume.h"
#define ZXDEBUG 0
namespace zxcrypt {
// Several copies of the metadata for a zxcrypt volume is saved at the beginning and end of the
// devices. The number of copies is given by |kMetadataBlocks * kReservedSlices|, and the locations
// of each block can be iterated through using |Begin| and |Next|. The metadata block, or
// superblock, consists of a fixed type GUID, an instance GUID, a 32-bit version, a set of "key
// slots" The key slots are data cipher key material encrypted with a wrapping crypto::AEAD key
// derived from the caller-provided root key and specific slot.
// Determines what algorithms are in use when creating new zxcrypt devices.
const Volume::Version Volume::kDefaultVersion = Volume::kAES256_XTS_SHA256;
// The amount of data that can "in-flight" to the underlying block device before the zxcrypt
// driver begins queuing transactions
//
// TODO(aarongreen): See fxbug.dev/31498. Tune this value. Possibly break into several smaller
// VMOs if we want to allow some to be recycled; support for this doesn't currently exist. Up to 64
// MB may be in flight at once. The device's max_transfer_size will be capped at 1/4 of this value.
__EXPORT
const uint32_t Volume::kBufferSize = 1U << 24;
static_assert(Volume::kBufferSize % PAGE_SIZE == 0, "kBufferSize must be page aligned");
namespace {
// The number of metadata blocks in a reserved metadata slice, each holding a copy of the
// superblock.
const size_t kMetadataBlocks = 2;
// HKDF labels
const size_t kMaxLabelLen = 16;
const char* kWrapKeyLabel = "wrap key %" PRIu64;
const char* kWrapIvLabel = "wrap iv %" PRIu64;
// Header is type GUID | instance GUID | version.
const size_t kHeaderLen = sizeof(zxcrypt_magic) + BLOCK_GUID_LEN + sizeof(uint32_t);
} // namespace
Volume::Volume() { Reset(); }
Volume::~Volume() {}
void Volume::Reset() {
reserved_blocks_ = 0;
reserved_slices_ = 0;
block_.Resize(0);
offset_ = UINT64_MAX;
guid_.Resize(0);
header_.Resize(0);
aead_ = crypto::AEAD::kUninitialized;
wrap_key_.Clear();
wrap_iv_.Resize(0);
cipher_ = crypto::Cipher::kUninitialized;
data_key_.Clear();
data_iv_.Resize(0);
slot_len_ = 0;
num_key_slots_ = 0;
digest_ = crypto::digest::kUninitialized;
}
zx_status_t Volume::Unlock(const crypto::Secret& key, key_slot_t slot) {
zx_status_t rc;
for (rc = Begin(); rc == ZX_ERR_NEXT; rc = Next()) {
if ((rc = Read()) != ZX_OK) {
xprintf("failed to read block at %" PRIu64 ": %d\n", offset_, rc);
} else if ((rc = UnsealBlock(key, slot)) != ZX_OK) {
xprintf("failed to open block at %" PRIu64 ": %d\n", offset_, rc);
} else {
return ZX_OK;
}
}
return ZX_ERR_ACCESS_DENIED;
}
zx_status_t Volume::Shred() {
zx_status_t rc;
if (!block_.get()) {
xprintf("not initialized\n");
return ZX_ERR_BAD_STATE;
}
if ((rc = block_.Randomize()) != ZX_OK) {
return rc;
}
for (rc = Begin(); rc == ZX_ERR_NEXT; rc = Next()) {
if ((rc = Write()) != ZX_OK) {
return rc;
}
}
Reset();
return ZX_OK;
}
zx_status_t Volume::GetSlotOffset(key_slot_t slot, zx_off_t* out) const {
if (!block_.get()) {
xprintf("not initialized\n");
return ZX_ERR_BAD_STATE;
}
zx_off_t off;
if (mul_overflow(slot, slot_len_, &off) || add_overflow(kHeaderLen, off, &off) ||
off > block_.len() - slot_len_) {
xprintf("bad key slot: %" PRIu64 "\n", slot);
return ZX_ERR_INVALID_ARGS;
}
if (out) {
*out = off;
}
return ZX_OK;
}
zx_status_t Volume::Init() {
zx_status_t rc;
// Get block info; align our blocks to pages
BlockInfo blk;
if ((rc = GetBlockInfo(&blk)) != ZX_OK) {
xprintf("failed to get block info: %s\n", zx_status_get_string(rc));
return rc;
}
// Check that we meet the minimum size.
if (blk.block_count < kMetadataBlocks) {
xprintf("device is too small; have %" PRIu64 " blocks, need %" PRIu64 "\n", blk.block_count,
kMetadataBlocks);
return ZX_ERR_NOT_SUPPORTED;
}
reserved_blocks_ = kMetadataBlocks;
// Allocate block buffer
if ((rc = block_.Resize(blk.block_size)) != ZX_OK) {
return rc;
}
// Get FVM info
uint64_t fvm_slice_size;
switch ((rc = GetFvmSliceSize(&fvm_slice_size))) {
case ZX_OK: {
// This *IS* an FVM partition.
// Ensure first kReservedSlices + 1 slices are allocated
size_t blocks_per_slice = fvm_slice_size / blk.block_size;
reserved_blocks_ = fbl::round_up(reserved_blocks_, blocks_per_slice);
reserved_slices_ = reserved_blocks_ / blocks_per_slice;
size_t required_slices = reserved_slices_ + 1;
size_t contiguous_chunk_size = 0;
// We're going to go through the vslice address space, ensuring that all
// of the first `required_slices` slices are allocated.
for (size_t slice_off = 0; slice_off < required_slices; slice_off += contiguous_chunk_size) {
// Ask about the next contiguous range starting at `slice_off`.
SliceRegion ranges[MAX_SLICE_REGIONS];
uint64_t range_count;
if ((rc = DoBlockFvmVsliceQuery(slice_off, ranges, &range_count)) != ZX_OK ||
range_count == 0 || ((contiguous_chunk_size = ranges[0].count) == 0)) {
xprintf("FVM Vslice Query failed: %s\n", zx_status_get_string(rc));
return rc;
}
// If it's already allocated, continue to the next range. The for loop
// advances by `contiguous_chunk_size`
if (ranges[0].allocated) {
continue;
};
// Otherwise, allocate it -- either up to the end of the contiguous
// unallocated chunk (if that still doesn't cover the number of slices
// we need, in which case we'll keep looping) or just as many slices as
// we require.
uint64_t extend_start_slice = slice_off;
uint64_t extend_length = std::min(required_slices - slice_off, contiguous_chunk_size);
if ((rc = DoBlockFvmExtend(extend_start_slice, extend_length)) != ZX_OK) {
xprintf("failed to extend FVM partition: %s\n", zx_status_get_string(rc));
return rc;
}
}
break;
}
case ZX_ERR_NOT_SUPPORTED:
// This is *NOT* an FVM partition.
xprintf("Not an FVM partition\n");
break;
default:
xprintf("init failed: %s\n", zx_status_get_string(rc));
// An error occurred
return rc;
}
return ZX_OK;
}
zx_status_t Volume::Configure(Volume::Version version) {
zx_status_t rc;
switch (version) {
case Volume::kAES256_XTS_SHA256:
aead_ = crypto::AEAD::kAES128_GCM_SIV;
cipher_ = crypto::Cipher::kAES256_XTS;
digest_ = crypto::digest::kSHA256;
break;
default:
xprintf("unknown version: %u\n", version);
return ZX_ERR_NOT_SUPPORTED;
}
size_t key_len, iv_len, tag_len;
if ((rc = crypto::Cipher::GetKeyLen(cipher_, &key_len)) != ZX_OK ||
(rc = crypto::Cipher::GetIVLen(cipher_, &iv_len)) != ZX_OK ||
(rc = crypto::AEAD::GetTagLen(aead_, &tag_len)) != ZX_OK) {
return rc;
}
slot_len_ = key_len + iv_len + tag_len;
num_key_slots_ = (block_.len() - kHeaderLen) / slot_len_;
if (num_key_slots_ == 0) {
xprintf("block size is too small; have %zu, need %zu\n", block_.len(), kHeaderLen + slot_len_);
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t Volume::DeriveSlotKeys(const crypto::Secret& key, key_slot_t slot) {
zx_status_t rc;
crypto::HKDF hkdf;
char label[kMaxLabelLen];
// We tolerate 128-bit keys here because some hardware we wish to operate on
// only has 128-bits of random keys in hardware. We believe that this
// entropy is sufficient for our purposes.
size_t key_len = key.len();
if (key_len == 16) {
rc = hkdf.Init(digest_, key, guid_, crypto::HKDF::ALLOW_WEAK_KEY);
} else if (key_len == 32) {
rc = hkdf.Init(digest_, key, guid_);
} else {
xprintf("invalid key length %lu (acceptable values are 16, 32)\n", key_len);
return ZX_ERR_INVALID_ARGS;
}
if (rc != ZX_OK) {
xprintf("hkdf.Init failed %s\n", zx_status_get_string(rc));
return rc;
}
snprintf(label, kMaxLabelLen, kWrapKeyLabel, slot);
size_t len;
if ((rc = crypto::AEAD::GetKeyLen(aead_, &len)) != ZX_OK ||
(rc = hkdf.Derive(label, len, &wrap_key_)) != ZX_OK) {
xprintf("failed to derive wrap key: %s\n", zx_status_get_string(rc));
return rc;
}
snprintf(label, kMaxLabelLen, kWrapIvLabel, slot);
crypto::Secret wrap_iv;
if ((rc = crypto::AEAD::GetIVLen(aead_, &len)) != ZX_OK ||
(rc = hkdf.Derive(label, len, &wrap_iv_)) != ZX_OK) {
xprintf("failed to derive wrap IV: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
// Block methods
zx_status_t Volume::Begin() {
offset_ = 0;
return ZX_ERR_NEXT;
}
zx_status_t Volume::Next() {
offset_ += block_.len();
return (offset_ / block_.len()) < kMetadataBlocks ? ZX_ERR_NEXT : ZX_ERR_STOP;
}
zx_status_t Volume::CreateBlock() {
zx_status_t rc;
// Create a "backdrop" of random data
if ((rc = block_.Randomize()) != ZX_OK) {
return rc;
}
// Write the variant 1/version 1 type GUID according to RFC 4122.
uint8_t* out = block_.get();
memcpy(out, zxcrypt_magic, sizeof(zxcrypt_magic));
out += sizeof(zxcrypt_magic);
// Create a variant 1/version 4 instance GUID according to RFC 4122.
if ((rc = guid_.Randomize(BLOCK_GUID_LEN)) != ZX_OK) {
return rc;
}
guid_[6] = (guid_[6] & 0x0F) | 0x40;
guid_[8] = (guid_[8] & 0x3F) | 0x80;
memcpy(out, guid_.get(), BLOCK_GUID_LEN);
out += BLOCK_GUID_LEN;
// Write the 32-bit version.
if ((rc = Configure(kDefaultVersion)) != ZX_OK) {
return rc;
}
uint32_t version = htonl(kDefaultVersion);
memcpy(out, &version, sizeof(version));
// Generate the data key and IV, and save the AAD.
size_t key_len, iv_len;
if ((rc = crypto::Cipher::GetKeyLen(cipher_, &key_len)) != ZX_OK ||
(rc = crypto::Cipher::GetIVLen(cipher_, &iv_len)) != ZX_OK ||
(rc = data_key_.Generate(key_len)) != ZX_OK || (rc = data_iv_.Resize(iv_len)) != ZX_OK ||
(rc = data_iv_.Randomize()) != ZX_OK ||
(rc = header_.Copy(block_.get(), kHeaderLen)) != ZX_OK) {
return rc;
}
return ZX_OK;
}
zx_status_t Volume::CommitBlock() {
zx_status_t rc;
// Make a copy to compare the read result to; this reduces the number of
// writes we must do.
crypto::Bytes block;
if ((rc = block.Copy(block_)) != ZX_OK) {
xprintf("zxcrypt: Cannot copy block: %s\n", zx_status_get_string(rc));
return rc;
}
for (rc = Begin(); rc == ZX_ERR_NEXT; rc = Next()) {
if ((rc = Read()) != ZX_OK) {
xprintf("zxcrypt: CommitBlock Read failed: %s\n", zx_status_get_string(rc));
return rc;
}
// Only write back blocks that don't match.
if (block_ == block) {
continue;
}
if ((rc = block_.Copy(block)) != ZX_OK || (rc = Write()) != ZX_OK) {
xprintf("zxcrypt: CommitBlock Write failed for offset %" PRIu64 ": %s\n", offset_,
zx_status_get_string(rc));
}
}
return ZX_OK;
}
zx_status_t Volume::SealBlock(const crypto::Secret& key, key_slot_t slot) {
zx_status_t rc;
// Encrypt the data key
zx_off_t nonce;
crypto::AEAD aead;
crypto::Bytes ptext, ctext;
zx_off_t off;
zx_off_t data_key_off = 0;
zx_off_t data_iv_off = data_key_.len();
if ((rc = GetSlotOffset(slot, &off)) != ZX_OK) {
xprintf("GetSlotOffset for slot %" PRIu64 " failed: %s\n", slot, zx_status_get_string(rc));
return rc;
}
if ((rc = ptext.Copy(data_key_.get(), data_key_.len(), data_key_off)) != ZX_OK) {
xprintf("ptext.Copy (key) failed: %s", zx_status_get_string(rc));
return rc;
}
if ((rc = ptext.Copy(data_iv_.get(), data_iv_.len(), data_iv_off)) != ZX_OK) {
xprintf("ptext.Copy (iv) failed: %s", zx_status_get_string(rc));
return rc;
}
if ((rc = DeriveSlotKeys(key, slot)) != ZX_OK) {
xprintf("DeriveSlotKeys failed: %s", zx_status_get_string(rc));
return rc;
}
if ((rc = aead.InitSeal(aead_, wrap_key_, wrap_iv_)) != ZX_OK) {
xprintf("aead.InitSeal failed: %s", zx_status_get_string(rc));
return rc;
}
if ((rc = aead.Seal(ptext, header_, &nonce, &ctext)) != ZX_OK) {
xprintf("aead.Seal failed: %s", zx_status_get_string(rc));
return rc;
}
// Check that we'll be able to unseal.
if (memcmp(&nonce, wrap_iv_.get(), sizeof(nonce)) != 0) {
xprintf("unexpected nonce: %" PRIu64 "\n", nonce);
return ZX_ERR_INTERNAL;
}
memcpy(block_.get() + off, ctext.get(), ctext.len());
return ZX_OK;
}
zx_status_t Volume::UnsealBlock(const crypto::Secret& key, key_slot_t slot) {
zx_status_t rc;
// Check the type GUID matches |kTypeGuid|.
const uint8_t* in = block_.get();
if (memcmp(in, zxcrypt_magic, sizeof(zxcrypt_magic)) != 0) {
xprintf("not a zxcrypt device\n");
return ZX_ERR_NOT_SUPPORTED;
}
in += sizeof(zxcrypt_magic);
// Save the instance GUID
if ((rc = guid_.Copy(in, BLOCK_GUID_LEN)) != ZX_OK) {
return rc;
}
in += BLOCK_GUID_LEN;
// Read the version
uint32_t version;
memcpy(&version, in, sizeof(version));
in += sizeof(version);
// Read in the data
zx_off_t off;
size_t key_len, iv_len;
uint8_t* key_buf;
crypto::AEAD aead;
crypto::Bytes ctext, ptext;
if ((rc = Configure(Version(ntohl(version)))) != ZX_OK) {
xprintf("Configure failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = GetSlotOffset(slot, &off)) != ZX_OK) {
xprintf("GetSlotOffset failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = DeriveSlotKeys(key, slot)) != ZX_OK) {
xprintf("DeriveSlotKeys failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = crypto::Cipher::GetKeyLen(cipher_, &key_len)) != ZX_OK) {
xprintf("Cipher::GetKeyLen failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = crypto::Cipher::GetIVLen(cipher_, &iv_len)) != ZX_OK) {
xprintf("Cipher::GetIVLen failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = data_key_.Allocate(key_len, &key_buf)) != ZX_OK) {
xprintf("Secret::Allocate failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = ctext.Copy(block_.get() + off, slot_len_)) != ZX_OK) {
xprintf("ctext.Copy failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = aead.InitOpen(aead_, wrap_key_, wrap_iv_)) != ZX_OK) {
xprintf("aead.InitOpen failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = header_.Copy(block_.get(), kHeaderLen)) != ZX_OK) {
xprintf("header_.Copy failed: %s\n", zx_status_get_string(rc));
return rc;
}
// Extract nonce from IV.
zx_off_t nonce;
memcpy(&nonce, wrap_iv_.get(), sizeof(nonce));
if ((rc = aead.Open(nonce, ctext, header_, &ptext)) != ZX_OK) {
xprintf("aead.Open failed: %s\n", zx_status_get_string(rc));
return rc;
}
if ((rc = data_iv_.Copy(ptext.get() + key_len, iv_len)) != ZX_OK) {
xprintf("data_iv_.Copy failed: %s\n", zx_status_get_string(rc));
return rc;
}
memcpy(key_buf, ptext.get(), key_len);
return ZX_OK;
}
} // namespace zxcrypt