blob: e05ed6b271254a1773121ceeb03d8aaf93590b73 [file]
// Copyright 2021 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.
use {
crate::trace_duration,
aes::{
cipher::{generic_array::GenericArray, NewCipher, StreamCipher as _, StreamCipherSeek},
Aes256, NewBlockCipher,
},
anyhow::{anyhow, Error},
async_trait::async_trait,
chacha20::{ChaCha20, Key},
rand::RngCore,
serde::{Deserialize, Serialize},
xts_mode::{get_tweak_default, Xts128},
};
const KEY_SIZE: usize = 256 / 8;
// The xts-mode crate expects a sector size. Fxfs will always use a block size >= 512 bytes, so we
// just assume a sector size of 512 bytes, which will work fine even if a different block size is
// used by Fxfs or the underlying device.
const SECTOR_SIZE: u64 = 512;
type KeyBytes = [u8; KEY_SIZE];
pub struct UnwrappedKey {
id: u64,
key: KeyBytes,
}
impl std::fmt::Debug for UnwrappedKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UnwrappedKey").field("id", &self.id).finish()
}
}
impl UnwrappedKey {
pub fn new(id: u64, key: KeyBytes) -> Self {
UnwrappedKey { id, key }
}
pub fn id(&self) -> u64 {
self.id
}
pub fn key(&self) -> &KeyBytes {
&self.key
}
}
pub type UnwrappedKeys = Vec<UnwrappedKey>;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct WrappedKey {
/// The identifier of the wrapping key. The identifier has meaning to whatever is doing the
/// unwrapping.
pub wrapping_key_id: u64,
/// Each of the keys is given an identifier. The identifier is unique to the object.
pub key_id: u64,
/// AES 256 requires a 512 bit key, which is made of two 256 bit keys, one for the data and one
/// for the tweak. Both those keys are derived from the single 256 bit key we have here.
pub key: [u8; 32],
}
/// To support key rolling and clones, a file can have more than one key.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct WrappedKeys(pub Vec<WrappedKey>);
impl std::ops::Deref for WrappedKeys {
type Target = [WrappedKey];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Serialize, Debug, Deserialize)]
pub struct WrappedKeysV1 {
pub wrapping_key_id: u64,
pub keys: Vec<(/* id= */ u64, [u8; 32])>,
}
impl From<WrappedKeysV1> for WrappedKeys {
fn from(value: WrappedKeysV1) -> Self {
let wrapping_key_id = value.wrapping_key_id;
Self(
value
.keys
.into_iter()
.map(|(id, key)| WrappedKey { wrapping_key_id, key_id: id, key })
.collect(),
)
}
}
struct XtsCipher {
id: u64,
xts: Xts128<Aes256>,
}
pub struct XtsCipherSet(Vec<XtsCipher>);
impl XtsCipherSet {
pub fn new(keys: &UnwrappedKeys) -> Self {
Self(
keys.iter()
.map(|k| XtsCipher {
id: k.id,
// Note: The "128" in `Xts128` refers to the cipher block size, not the key size
// (and not the device sector size). AES-256, like all forms of AES, have a
// 128-bit block size, and so will work with `Xts128`. The same key is used for
// for encrypting the data and computing the tweak.
xts: Xts128::<Aes256>::new(
Aes256::new(GenericArray::from_slice(&k.key)),
Aes256::new(GenericArray::from_slice(&k.key)),
),
})
.collect(),
)
}
/// Decrypt the data in `buffer`.
///
/// * `offset` is the byte offset within the file.
/// * `key_id` specifies which of the unwrapped keys to use.
/// * `buffer` is mutated in place.
pub fn decrypt(&self, offset: u64, key_id: u64, buffer: &mut [u8]) -> Result<(), Error> {
trace_duration!("decrypt");
assert_eq!(offset % SECTOR_SIZE, 0);
self.0
.iter()
.find(|cipher| cipher.id == key_id)
.ok_or(anyhow!("Key not found"))?
.xts
.decrypt_area(
buffer,
SECTOR_SIZE as usize,
(offset / SECTOR_SIZE).into(),
get_tweak_default,
);
Ok(())
}
/// Encrypts data in the `buffer`.
///
/// * `offset` is the byte offset within the file.
/// * `key_id` specifies which of the unwrapped keys to use.
/// * `buffer` is mutated in place.
pub fn encrypt(&self, offset: u64, key_id: u64, buffer: &mut [u8]) -> Result<(), Error> {
trace_duration!("encrypt");
assert_eq!(offset % SECTOR_SIZE, 0);
self.0
.iter()
.find(|cipher| cipher.id == key_id)
.ok_or(anyhow!("Key not found"))?
.xts
.encrypt_area(
buffer,
SECTOR_SIZE as usize,
(offset / SECTOR_SIZE).into(),
get_tweak_default,
);
Ok(())
}
}
pub struct StreamCipher(ChaCha20);
impl StreamCipher {
pub fn new(key: &UnwrappedKey, offset: u64) -> Self {
let mut cipher =
Self(ChaCha20::new(Key::from_slice(&key.key), /* nonce: */ &[0; 12].into()));
// TODO(fxbug.dev/93354): Chacha20's 32 bit counter isn't quite big enough for our offset,
// so we need to handle that case.
cipher.0.seek(offset);
cipher
}
pub fn encrypt(&mut self, buffer: &mut [u8]) {
trace_duration!("StreamCipher::encrypt");
self.0.apply_keystream(buffer);
}
pub fn decrypt(&mut self, buffer: &mut [u8]) {
trace_duration!("StreamCipher::decrypt");
self.0.apply_keystream(buffer);
}
pub fn offset(&self) -> u64 {
self.0.current_pos()
}
}
/// Different keys are used for metadata and data in order to make certain operations requiring a
/// metadata key rotation (e.g. secure erase) more efficient.
pub enum KeyPurpose {
/// The key will be used to wrap user data.
Data,
/// The key will be used to wrap internal metadata.
Metadata,
}
/// An interface trait with the ability to wrap and unwrap encryption keys.
///
/// Note that existence of this trait does not imply that an object will **securely**
/// wrap and unwrap keys; rather just that it presents an interface for wrapping operations.
#[async_trait]
pub trait Crypt: Send + Sync {
/// `owner` is intended to be used such that when the key is wrapped, it appears to be different
/// to that of the same key wrapped by a different owner. In this way, keys can be shared
/// amongst different filesystem objects (e.g. for clones), but it is not possible to tell just
/// by looking at the wrapped keys.
async fn create_key(
&self,
owner: u64,
purpose: KeyPurpose,
) -> Result<(WrappedKeys, UnwrappedKeys), Error>;
/// Unwraps the keys and stores the result in UnwrappedKeys.
async fn unwrap_keys(&self, keys: &WrappedKeys, owner: u64) -> Result<UnwrappedKeys, Error>;
}
/// This struct provides the `Crypt` trait without any strong security.
///
/// It is intended for use only in test code where actual security is inconsequential.
pub struct InsecureCrypt {}
/// Used by `InsecureCrypt` as an extremely weak form of 'encryption'.
const DATA_WRAP_XOR: [u8; 8] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef];
const METADATA_WRAP_XOR: [u8; 8] = [0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10];
impl InsecureCrypt {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl Crypt for InsecureCrypt {
async fn create_key(
&self,
owner: u64,
purpose: KeyPurpose,
) -> Result<(WrappedKeys, UnwrappedKeys), Error> {
let mut rng = rand::thread_rng();
let mut key: KeyBytes = [0; KEY_SIZE];
rng.fill_bytes(&mut key);
let mut wrapped: KeyBytes = [0; KEY_SIZE];
let owner_bytes = owner.to_le_bytes();
let (wrap_xor, wrapping_key_id) = match purpose {
KeyPurpose::Data => (&DATA_WRAP_XOR, 0),
KeyPurpose::Metadata => (&METADATA_WRAP_XOR, 1),
};
for i in 0..wrapped.len() {
let j = i % wrap_xor.len();
wrapped[i] = key[i] ^ wrap_xor[j] ^ owner_bytes[j];
}
Ok((
WrappedKeys(vec![WrappedKey { wrapping_key_id, key_id: 0, key: wrapped }]),
vec![UnwrappedKey::new(0, key)],
))
}
/// Unwraps the keys and stores the result in UnwrappedKeys.
async fn unwrap_keys(&self, keys: &WrappedKeys, owner: u64) -> Result<UnwrappedKeys, Error> {
Ok(keys
.iter()
.map(|key| {
let mut unwrapped: KeyBytes = [0; KEY_SIZE];
let owner_bytes = owner.to_le_bytes();
let wrap_xor = match key.wrapping_key_id {
0 => &DATA_WRAP_XOR,
1 => &METADATA_WRAP_XOR,
_ => panic!("Unexpected wrapping key ID for {:?}", key),
};
for i in 0..unwrapped.len() {
let j = i % wrap_xor.len();
unwrapped[i] = key.key[i] ^ wrap_xor[j] ^ owner_bytes[j];
}
UnwrappedKey::new(key.key_id, unwrapped)
})
.collect())
}
}
#[cfg(test)]
mod tests {
use super::{StreamCipher, UnwrappedKey};
#[test]
fn test_stream_cipher_offset() {
let key = UnwrappedKey::new(
0,
[
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
],
);
let mut cipher1 = StreamCipher::new(&key, 0);
let mut p1 = [1, 2, 3, 4];
let mut c1 = p1.clone();
cipher1.encrypt(&mut c1);
let mut cipher2 = StreamCipher::new(&key, 1);
let p2 = [5, 6, 7, 8];
let mut c2 = p2.clone();
cipher2.encrypt(&mut c2);
let xor_fn = |buf1: &mut [u8], buf2| {
for (b1, b2) in buf1.iter_mut().zip(buf2) {
*b1 ^= b2;
}
};
// Check that c1 ^ c2 != p1 ^ p2 (which would be the case if the same offset was used for
// both ciphers).
xor_fn(&mut c1, &c2);
xor_fn(&mut p1, &p2);
assert_ne!(c1, p1);
}
}