Use AES-XTS for encryption
This commit is contained in:
Generated
+11
@@ -459,6 +459,7 @@ dependencies = [
|
||||
"termion",
|
||||
"time",
|
||||
"uuid",
|
||||
"xts-mode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -722,6 +723,16 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xts-mode"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75a099a2f21d48275314733f85bc43b6c6213b66394233aaea573fc7a520dcd9"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
||||
+6
-5
@@ -34,19 +34,20 @@ required-features = ["std"]
|
||||
aes = { version = "=0.7.5", default-features = false }
|
||||
argon2 = { version = "0.4", default-features = false, features = ["alloc"] }
|
||||
base64ct = { version = "1", default-features = false }
|
||||
env_logger = { version = "0.11", optional = true }
|
||||
endian-num = "0.1"
|
||||
env_logger = { version = "0.11", optional = true }
|
||||
getrandom = { version = "0.2.5", optional = true }
|
||||
libc = "0.2"
|
||||
libredox = { version = "0.1.3", optional = true }
|
||||
log = { version = "0.4.14", default-features = false, optional = true}
|
||||
redox_syscall = { version = "0.5.13" }
|
||||
range-tree = { version = "0.1", optional = true }
|
||||
redox-path = "0.3.0"
|
||||
redox-scheme = { version = "0.6.2", optional = true }
|
||||
redox_syscall = { version = "0.5.13" }
|
||||
seahash = { version = "4.1.0", default-features = false }
|
||||
termion = { version = "4", optional = true }
|
||||
uuid = { version = "1.4", default-features = false }
|
||||
redox-path = "0.3.0"
|
||||
libredox = { version = "0.1.3", optional = true }
|
||||
redox-scheme = { version = "0.6.2", optional = true }
|
||||
xts-mode = { version = "=0.4.1", default-features = false }
|
||||
|
||||
[features]
|
||||
default = ["std", "log"]
|
||||
|
||||
+37
-61
@@ -1,6 +1,7 @@
|
||||
use aes::{Aes128, BlockDecrypt, BlockEncrypt};
|
||||
use alloc::{collections::VecDeque, vec::Vec};
|
||||
use aes::Aes128;
|
||||
use alloc::collections::VecDeque;
|
||||
use syscall::error::{Error, Result, EKEYREJECTED, ENOENT, ENOKEY};
|
||||
use xts_mode::{get_tweak_default, Xts128};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use crate::{AllocEntry, AllocList, BlockData, BlockTrait, Key, KeySlot, Node, Salt, TreeList};
|
||||
@@ -15,8 +16,7 @@ pub struct FileSystem<D: Disk> {
|
||||
//TODO: make private
|
||||
pub header: Header,
|
||||
pub(crate) allocator: Allocator,
|
||||
pub(crate) aes_opt: Option<Aes128>,
|
||||
aes_blocks: Vec<aes::Block>,
|
||||
pub(crate) cipher_opt: Option<Xts128<Aes128>>,
|
||||
}
|
||||
|
||||
impl<D: Disk> FileSystem<D> {
|
||||
@@ -52,14 +52,14 @@ impl<D: Disk> FileSystem<D> {
|
||||
}
|
||||
}
|
||||
|
||||
let aes_opt = match password_opt {
|
||||
let cipher_opt = match password_opt {
|
||||
Some(password) => {
|
||||
if !header.encrypted() {
|
||||
// Header not encrypted but password provided
|
||||
return Err(Error::new(EKEYREJECTED));
|
||||
}
|
||||
match header.aes(password) {
|
||||
Some(aes) => Some(aes),
|
||||
match header.cipher(password) {
|
||||
Some(cipher) => Some(cipher),
|
||||
None => {
|
||||
// Header encrypted with a different password
|
||||
return Err(Error::new(ENOKEY));
|
||||
@@ -80,8 +80,7 @@ impl<D: Disk> FileSystem<D> {
|
||||
block,
|
||||
header,
|
||||
allocator: Allocator::default(),
|
||||
aes_opt,
|
||||
aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE),
|
||||
cipher_opt,
|
||||
};
|
||||
|
||||
unsafe { fs.reset_allocator()? };
|
||||
@@ -141,12 +140,16 @@ impl<D: Disk> FileSystem<D> {
|
||||
|
||||
let mut header = Header::new(size);
|
||||
|
||||
let aes_opt = match password_opt {
|
||||
let cipher_opt = match password_opt {
|
||||
Some(password) => {
|
||||
//TODO: handle errors
|
||||
header.key_slots[0] =
|
||||
KeySlot::new(password, Salt::new().unwrap(), Key::new().unwrap()).unwrap();
|
||||
Some(header.key_slots[0].key(password).unwrap().into_aes())
|
||||
header.key_slots[0] = KeySlot::new(
|
||||
password,
|
||||
Salt::new().unwrap(),
|
||||
(Key::new().unwrap(), Key::new().unwrap()),
|
||||
)
|
||||
.unwrap();
|
||||
Some(header.key_slots[0].cipher(password).unwrap())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
@@ -156,8 +159,7 @@ impl<D: Disk> FileSystem<D> {
|
||||
block: block_offset,
|
||||
header,
|
||||
allocator: Allocator::default(),
|
||||
aes_opt,
|
||||
aes_blocks: Vec::with_capacity(BLOCK_SIZE as usize / aes::BLOCK_SIZE),
|
||||
cipher_opt,
|
||||
};
|
||||
|
||||
// Write header generation zero
|
||||
@@ -267,59 +269,33 @@ impl<D: Disk> FileSystem<D> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn decrypt(&mut self, data: &mut [u8]) -> bool {
|
||||
let aes = if let Some(ref aes) = self.aes_opt {
|
||||
aes
|
||||
pub(crate) fn decrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
|
||||
if let Some(ref cipher) = self.cipher_opt {
|
||||
cipher.decrypt_area(
|
||||
data,
|
||||
BLOCK_SIZE as usize,
|
||||
addr.index().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
// Do nothing if encryption is disabled
|
||||
return false;
|
||||
};
|
||||
|
||||
assert_eq!(data.len() % aes::BLOCK_SIZE, 0);
|
||||
|
||||
self.aes_blocks.clear();
|
||||
for i in 0..data.len() / aes::BLOCK_SIZE {
|
||||
self.aes_blocks.push(aes::Block::clone_from_slice(
|
||||
&data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE],
|
||||
));
|
||||
false
|
||||
}
|
||||
|
||||
aes.decrypt_blocks(&mut self.aes_blocks);
|
||||
|
||||
for i in 0..data.len() / aes::BLOCK_SIZE {
|
||||
data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE]
|
||||
.copy_from_slice(&self.aes_blocks[i]);
|
||||
}
|
||||
self.aes_blocks.clear();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn encrypt(&mut self, data: &mut [u8]) -> bool {
|
||||
let aes = if let Some(ref aes) = self.aes_opt {
|
||||
aes
|
||||
pub(crate) fn encrypt(&mut self, data: &mut [u8], addr: BlockAddr) -> bool {
|
||||
if let Some(ref cipher) = self.cipher_opt {
|
||||
cipher.encrypt_area(
|
||||
data,
|
||||
BLOCK_SIZE as usize,
|
||||
addr.index().into(),
|
||||
get_tweak_default,
|
||||
);
|
||||
true
|
||||
} else {
|
||||
// Do nothing if encryption is disabled
|
||||
return false;
|
||||
};
|
||||
|
||||
assert_eq!(data.len() % aes::BLOCK_SIZE, 0);
|
||||
|
||||
self.aes_blocks.clear();
|
||||
for i in 0..data.len() / aes::BLOCK_SIZE {
|
||||
self.aes_blocks.push(aes::Block::clone_from_slice(
|
||||
&data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE],
|
||||
));
|
||||
false
|
||||
}
|
||||
|
||||
aes.encrypt_blocks(&mut self.aes_blocks);
|
||||
|
||||
for i in 0..data.len() / aes::BLOCK_SIZE {
|
||||
data[i * aes::BLOCK_SIZE..(i + 1) * aes::BLOCK_SIZE]
|
||||
.copy_from_slice(&self.aes_blocks[i]);
|
||||
}
|
||||
self.aes_blocks.clear();
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
+16
-14
@@ -2,7 +2,8 @@ use core::ops::{Deref, DerefMut};
|
||||
use core::{fmt, mem, slice};
|
||||
use endian_num::Le;
|
||||
|
||||
use aes::{Aes128, BlockDecrypt, BlockEncrypt};
|
||||
use aes::Aes128;
|
||||
use xts_mode::{get_tweak_default, Xts128};
|
||||
|
||||
use crate::{AllocList, BlockPtr, KeySlot, Tree, BLOCK_SIZE, SIGNATURE, VERSION};
|
||||
|
||||
@@ -29,7 +30,7 @@ pub struct Header {
|
||||
/// Key slots
|
||||
pub key_slots: [KeySlot; 64],
|
||||
/// Padding
|
||||
pub padding: [u8; BLOCK_SIZE as usize - 2152],
|
||||
pub padding: [u8; BLOCK_SIZE as usize - 3176],
|
||||
/// encrypted hash of header data without hash, set to hash and padded if disk is not encrypted
|
||||
pub encrypted_hash: [u8; 16],
|
||||
/// hash of header data without hash
|
||||
@@ -91,14 +92,14 @@ impl Header {
|
||||
seahash::hash(&self[..end])
|
||||
}
|
||||
|
||||
fn create_encrypted_hash(&self, aes_opt: Option<&Aes128>) -> [u8; 16] {
|
||||
fn create_encrypted_hash(&self, cipher_opt: Option<&Xts128<Aes128>>) -> [u8; 16] {
|
||||
let mut encrypted_hash = [0; 16];
|
||||
for (i, b) in self.hash.to_le_bytes().iter().enumerate() {
|
||||
encrypted_hash[i] = *b;
|
||||
}
|
||||
if let Some(aes) = aes_opt {
|
||||
if let Some(cipher) = cipher_opt {
|
||||
let mut block = aes::Block::from(encrypted_hash);
|
||||
aes.encrypt_block(&mut block);
|
||||
cipher.encrypt_area(&mut block, BLOCK_SIZE as usize, self.generation().into(), get_tweak_default);
|
||||
encrypted_hash = block.into();
|
||||
}
|
||||
encrypted_hash
|
||||
@@ -108,30 +109,31 @@ impl Header {
|
||||
(self.encrypted_hash) != self.create_encrypted_hash(None)
|
||||
}
|
||||
|
||||
pub fn aes(&self, password: &[u8]) -> Option<Aes128> {
|
||||
pub fn cipher(&self, password: &[u8]) -> Option<Xts128<Aes128>> {
|
||||
let hash = self.create_encrypted_hash(None);
|
||||
for slot in self.key_slots.iter() {
|
||||
//TODO: handle errors
|
||||
let aes = slot.key(password).unwrap().into_aes();
|
||||
let cipher = slot.cipher(password).unwrap();
|
||||
let mut block = aes::Block::from(self.encrypted_hash);
|
||||
aes.decrypt_block(&mut block);
|
||||
cipher.decrypt_area(&mut block, BLOCK_SIZE as usize, self.generation().into(), get_tweak_default);
|
||||
if block == aes::Block::from(hash) {
|
||||
return Some(aes);
|
||||
return Some(cipher);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
fn update_hash(&mut self, aes_opt: Option<&Aes128>) {
|
||||
|
||||
fn update_hash(&mut self, cipher_opt: Option<&Xts128<Aes128>>) {
|
||||
self.hash = self.create_hash().into();
|
||||
// Make sure to do this second, it relies on the hash being up to date
|
||||
self.encrypted_hash = self.create_encrypted_hash(aes_opt);
|
||||
self.encrypted_hash = self.create_encrypted_hash(cipher_opt);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, aes_opt: Option<&Aes128>) -> u64 {
|
||||
pub fn update(&mut self, cipher_opt: Option<&Xts128<Aes128>>) -> u64 {
|
||||
let mut generation = self.generation();
|
||||
generation += 1;
|
||||
self.generation = generation.into();
|
||||
self.update_hash(aes_opt);
|
||||
self.update_hash(cipher_opt);
|
||||
generation
|
||||
}
|
||||
}
|
||||
@@ -147,7 +149,7 @@ impl Default for Header {
|
||||
tree: BlockPtr::<Tree>::default(),
|
||||
alloc: BlockPtr::<AllocList>::default(),
|
||||
key_slots: [KeySlot::default(); 64],
|
||||
padding: [0; BLOCK_SIZE as usize - 2152],
|
||||
padding: [0; BLOCK_SIZE as usize - 3176],
|
||||
encrypted_hash: [0; 16],
|
||||
hash: 0.into(),
|
||||
}
|
||||
|
||||
+25
-16
@@ -1,4 +1,5 @@
|
||||
use aes::{Aes128, BlockDecrypt, BlockEncrypt, NewBlockCipher};
|
||||
use xts_mode::Xts128;
|
||||
|
||||
// The raw key, keep secret!
|
||||
#[repr(transparent)]
|
||||
@@ -13,6 +14,12 @@ impl Key {
|
||||
Ok(Self(bytes))
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, password_aes: &Aes128) -> EncryptedKey {
|
||||
let mut block = aes::Block::from(self.0);
|
||||
password_aes.encrypt_block(&mut block);
|
||||
EncryptedKey(block.into())
|
||||
}
|
||||
|
||||
pub fn into_aes(self) -> Aes128 {
|
||||
Aes128::new(&aes::Block::from(self.0))
|
||||
}
|
||||
@@ -23,6 +30,14 @@ impl Key {
|
||||
#[repr(transparent)]
|
||||
pub struct EncryptedKey([u8; 16]);
|
||||
|
||||
impl EncryptedKey {
|
||||
pub fn decrypt(&self, password_aes: &Aes128) -> Key {
|
||||
let mut block = aes::Block::from(self.0);
|
||||
password_aes.decrypt_block(&mut block);
|
||||
Key(block.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Salt used to prevent rainbow table attacks on the encryption password
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[repr(transparent)]
|
||||
@@ -43,7 +58,8 @@ impl Salt {
|
||||
#[repr(C, packed)]
|
||||
pub struct KeySlot {
|
||||
salt: Salt,
|
||||
encrypted_key: EncryptedKey,
|
||||
// Two keys for AES XTS 128
|
||||
encrypted_keys: (EncryptedKey, EncryptedKey),
|
||||
}
|
||||
|
||||
impl KeySlot {
|
||||
@@ -66,27 +82,20 @@ impl KeySlot {
|
||||
}
|
||||
|
||||
/// Create a new key slot from a password, salt, and encryption key
|
||||
pub fn new(password: &[u8], salt: Salt, key: Key) -> Result<Self, argon2::Error> {
|
||||
pub fn new(password: &[u8], salt: Salt, keys: (Key, Key)) -> Result<Self, argon2::Error> {
|
||||
let password_aes = Self::password_aes(password, &salt)?;
|
||||
|
||||
// Encrypt the real AES key
|
||||
let mut block = aes::Block::from(key.0);
|
||||
password_aes.encrypt_block(&mut block);
|
||||
|
||||
Ok(Self {
|
||||
salt,
|
||||
encrypted_key: EncryptedKey(block.into()),
|
||||
encrypted_keys: (keys.0.encrypt(&password_aes), keys.1.encrypt(&password_aes)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the encryption key from this key slot
|
||||
pub fn key(&self, password: &[u8]) -> Result<Key, argon2::Error> {
|
||||
/// Get the encryption cipher from this key slot
|
||||
pub fn cipher(&self, password: &[u8]) -> Result<Xts128<Aes128>, argon2::Error> {
|
||||
let password_aes = Self::password_aes(password, &self.salt)?;
|
||||
|
||||
// Decrypt the real AES key
|
||||
let mut block = aes::Block::from(self.encrypted_key.0);
|
||||
password_aes.decrypt_block(&mut block);
|
||||
|
||||
Ok(Key(block.into()))
|
||||
Ok(Xts128::new(
|
||||
self.encrypted_keys.0.decrypt(&password_aes).into_aes(),
|
||||
self.encrypted_keys.1.decrypt(&password_aes).into_aes(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
+43
-11
@@ -88,9 +88,15 @@ impl<D: Disk> FileScheme<D> {
|
||||
let target_reference = reference.to_string();
|
||||
|
||||
nodes.clear();
|
||||
if let Some((next_node, next_node_name)) =
|
||||
Self::path_nodes(scheme_name, tx, TreePtr::root(), &target_reference, uid, gid, nodes)?
|
||||
{
|
||||
if let Some((next_node, next_node_name)) = Self::path_nodes(
|
||||
scheme_name,
|
||||
tx,
|
||||
TreePtr::root(),
|
||||
&target_reference,
|
||||
uid,
|
||||
gid,
|
||||
nodes,
|
||||
)? {
|
||||
if !next_node.data().is_symlink() {
|
||||
nodes.push((next_node, next_node_name));
|
||||
return Ok(target_reference);
|
||||
@@ -104,7 +110,13 @@ impl<D: Disk> FileScheme<D> {
|
||||
Err(Error::new(ELOOP))
|
||||
}
|
||||
|
||||
fn open_internal(&mut self, start_ptr: TreePtr<Node>, url: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
|
||||
fn open_internal(
|
||||
&mut self,
|
||||
start_ptr: TreePtr<Node>,
|
||||
url: &str,
|
||||
flags: usize,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
let CallerCtx { uid, gid, .. } = *ctx;
|
||||
|
||||
let path = url.trim_matches('/');
|
||||
@@ -307,7 +319,6 @@ impl<D: Disk> FileScheme<D> {
|
||||
number: id,
|
||||
flags: NewFdFlags::POSITIONED,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
fn path_nodes(
|
||||
@@ -377,8 +388,15 @@ impl<D: Disk> SchemeSync for FileScheme<D> {
|
||||
self.open_internal(TreePtr::root(), url, flags, ctx)
|
||||
}
|
||||
|
||||
fn openat(&mut self, dirfd: usize, path: &str, flags: usize, _fcntl_flags: u32, ctx: &CallerCtx) -> Result<OpenResult> {
|
||||
// If pathname is absolute, then dirfd is ignored.
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
// If pathname is absolute, then dirfd is ignored.
|
||||
if path.starts_with('/') {
|
||||
return self.open_internal(TreePtr::root(), path, flags, ctx);
|
||||
}
|
||||
@@ -623,8 +641,15 @@ impl<D: Disk> SchemeSync for FileScheme<D> {
|
||||
}
|
||||
|
||||
let mut new_nodes = Vec::new();
|
||||
let new_node_opt =
|
||||
Self::path_nodes(scheme_name, tx, TreePtr::root(), new_path, uid, gid, &mut new_nodes)?;
|
||||
let new_node_opt = Self::path_nodes(
|
||||
scheme_name,
|
||||
tx,
|
||||
TreePtr::root(),
|
||||
new_path,
|
||||
uid,
|
||||
gid,
|
||||
&mut new_nodes,
|
||||
)?;
|
||||
|
||||
if let Some((ref new_parent, _)) = new_nodes.last() {
|
||||
if !new_parent.data().owner(uid) {
|
||||
@@ -723,8 +748,15 @@ impl<D: Disk> SchemeSync for FileScheme<D> {
|
||||
}
|
||||
|
||||
let mut new_nodes = Vec::new();
|
||||
let new_node_opt =
|
||||
Self::path_nodes(scheme_name, tx, TreePtr::root(), new_path, uid, gid, &mut new_nodes)?;
|
||||
let new_node_opt = Self::path_nodes(
|
||||
scheme_name,
|
||||
tx,
|
||||
TreePtr::root(),
|
||||
new_path,
|
||||
uid,
|
||||
gid,
|
||||
&mut new_nodes,
|
||||
)?;
|
||||
|
||||
if let Some((ref new_parent, _)) = new_nodes.last() {
|
||||
if !new_parent.data().owner(uid) {
|
||||
|
||||
+3
-3
@@ -220,7 +220,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
|
||||
// if we have any blocks to write
|
||||
assert!(self.header_changed);
|
||||
|
||||
self.fs.encrypt(raw);
|
||||
self.fs.encrypt(raw, *addr);
|
||||
let count = unsafe { self.fs.disk.write_at(self.fs.block + addr.index(), raw)? };
|
||||
if count != raw.len() {
|
||||
// Read wrong number of bytes
|
||||
@@ -240,7 +240,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
|
||||
}
|
||||
|
||||
// Update header to next generation
|
||||
let gen = self.header.update(self.fs.aes_opt.as_ref());
|
||||
let gen = self.header.update(self.fs.cipher_opt.as_ref());
|
||||
let gen_block = gen % HEADER_RING;
|
||||
|
||||
// Write header
|
||||
@@ -293,7 +293,7 @@ impl<'a, D: Disk> Transaction<'a, D> {
|
||||
log::error!("READ_BLOCK: WRONG NUMBER OF BYTES");
|
||||
return Err(Error::new(EIO));
|
||||
}
|
||||
self.fs.decrypt(&mut data);
|
||||
self.fs.decrypt(&mut data, ptr.addr());
|
||||
}
|
||||
|
||||
let block = BlockData::new(ptr.addr(), data);
|
||||
|
||||
Reference in New Issue
Block a user