mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2025-12-11 20:15:30 +01:00
* add crypto crate with some functionality * formatting * add `argon2id` parameter levels * add descriptive comments * add stream decryption objects * add `StreamEncryptor` struct * add `StreamDecryptor` * general cleanup * add `thiserror` and error handling * add header structs * add basic serialization functionality * advance serialization * finish serialization * clean up serialization and use `impl` * finalise deserialization * add stream helper functions and remove old code * add AAD creation and retrieval * add important comment * add `ChaCha20Rng` as a CSPRNG * cleanup and crate-wide clippy lints * apply nursery lints * add in-memory encryption objects * rename `utils` to `objects` * move (de)serialization rules to separate file * add header-write helper function * add password hash helper function * add `decrypt_master_key` function * cleanup, formatting, linting * move keyslots to separate file, and rename them * add basic comments * remove `secrecy` dependency and import `protected` * add `to_array` helper function * `sd_crypto` -> `sd-crypto` * remove manual drops * add clippy allows * add `new()` for `Keyslot` and `FileHeader` * remove license * zeroize read buffer on error * magic bytes are now `ballapp` Co-authored-by: Brendan Allan <brendonovich@outlook.com> Co-authored-by: Jamie Pine <32987599+jamiepine@users.noreply.github.com>
221 lines
6.0 KiB
Rust
221 lines
6.0 KiB
Rust
use std::io::{Read, Seek, Write};
|
|
|
|
use zeroize::Zeroize;
|
|
|
|
use crate::{
|
|
error::Error,
|
|
objects::memory::MemoryDecryption,
|
|
primitives::{Algorithm, Mode, MASTER_KEY_LEN},
|
|
protected::Protected,
|
|
};
|
|
|
|
use super::keyslot::Keyslot;
|
|
|
|
/// These are used to quickly and easily identify Spacedrive-encrypted files
|
|
/// Random values - can be changed (up until 0.1.0)
|
|
pub const MAGIC_BYTES: [u8; 7] = [0x62, 0x61, 0x6C, 0x6C, 0x61, 0x70, 0x70];
|
|
|
|
// Everything contained within this header can be flaunted around with minimal security risk
|
|
// The only way this could compromise any data is if a weak password/key was used
|
|
// Even then, `argon2id` helps alleiviate this somewhat (brute-forcing it is incredibly tough)
|
|
// We also use high memory parameters in order to hinder attacks with ASICs
|
|
// There should be no more than two keyslots in this header type
|
|
pub struct FileHeader {
|
|
pub version: FileHeaderVersion,
|
|
pub algorithm: Algorithm,
|
|
pub mode: Mode,
|
|
pub nonce: Vec<u8>,
|
|
pub keyslots: Vec<Keyslot>,
|
|
}
|
|
|
|
/// This defines the main file header version
|
|
pub enum FileHeaderVersion {
|
|
V1,
|
|
}
|
|
|
|
impl FileHeader {
|
|
#[must_use]
|
|
pub fn new(
|
|
version: FileHeaderVersion,
|
|
algorithm: Algorithm,
|
|
nonce: Vec<u8>,
|
|
keyslots: Vec<Keyslot>,
|
|
) -> Self {
|
|
Self {
|
|
version,
|
|
algorithm,
|
|
mode: Mode::Stream,
|
|
nonce,
|
|
keyslots,
|
|
}
|
|
}
|
|
|
|
/// This is a helper function to decrypt a master key from a set of keyslots
|
|
/// It's easier to call this on the header for now - but this may be changed in the future
|
|
/// You receive an error if the password doesn't match
|
|
#[allow(clippy::needless_pass_by_value)]
|
|
pub fn decrypt_master_key(
|
|
&self,
|
|
password: Protected<Vec<u8>>,
|
|
) -> Result<Protected<[u8; 32]>, Error> {
|
|
let mut master_key = [0u8; MASTER_KEY_LEN];
|
|
|
|
for keyslot in &self.keyslots {
|
|
let key = keyslot
|
|
.hashing_algorithm
|
|
.hash(password.clone(), keyslot.salt)
|
|
.map_err(|_| Error::PasswordHash)?;
|
|
|
|
let decryptor =
|
|
MemoryDecryption::new(key, keyslot.algorithm).map_err(|_| Error::MemoryModeInit)?;
|
|
if let Ok(mut decrypted_master_key) =
|
|
decryptor.decrypt(keyslot.master_key.as_ref(), &keyslot.nonce)
|
|
{
|
|
master_key.copy_from_slice(&decrypted_master_key);
|
|
decrypted_master_key.zeroize();
|
|
}
|
|
}
|
|
|
|
if master_key == [0u8; MASTER_KEY_LEN] {
|
|
Err(Error::IncorrectPassword)
|
|
} else {
|
|
Ok(Protected::new(master_key))
|
|
}
|
|
}
|
|
|
|
pub fn write<W>(&self, writer: &mut W) -> Result<(), Error>
|
|
where
|
|
W: Write + Seek,
|
|
{
|
|
writer.write(&self.serialize()).map_err(Error::Io)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn generate_aad(&self) -> Vec<u8> {
|
|
match self.version {
|
|
FileHeaderVersion::V1 => {
|
|
let mut aad: Vec<u8> = Vec::new();
|
|
aad.extend_from_slice(&MAGIC_BYTES); // 6
|
|
aad.extend_from_slice(&self.version.serialize()); // 8
|
|
aad.extend_from_slice(&self.algorithm.serialize()); // 10
|
|
aad.extend_from_slice(&self.mode.serialize()); // 12
|
|
aad.extend_from_slice(&self.nonce); // 20 OR 32
|
|
aad.extend_from_slice(&vec![0u8; 24 - self.nonce.len()]); // padded until 36 bytes
|
|
aad
|
|
}
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn serialize(&self) -> Vec<u8> {
|
|
match self.version {
|
|
FileHeaderVersion::V1 => {
|
|
let mut header: Vec<u8> = Vec::new();
|
|
header.extend_from_slice(&MAGIC_BYTES); // 6
|
|
header.extend_from_slice(&self.version.serialize()); // 8
|
|
header.extend_from_slice(&self.algorithm.serialize()); // 10
|
|
header.extend_from_slice(&self.mode.serialize()); // 12
|
|
header.extend_from_slice(&self.nonce); // 20 OR 32
|
|
header.extend_from_slice(&vec![0u8; 24 - self.nonce.len()]); // padded until 36 bytes
|
|
|
|
for keyslot in &self.keyslots {
|
|
header.extend_from_slice(&keyslot.serialize());
|
|
}
|
|
|
|
for _ in 0..(2 - self.keyslots.len()) {
|
|
header.extend_from_slice(&[0u8; 96]);
|
|
}
|
|
|
|
header
|
|
}
|
|
}
|
|
}
|
|
|
|
// This includes the magic bytes at the start of the file
|
|
#[must_use]
|
|
pub const fn length(&self) -> usize {
|
|
match self.version {
|
|
FileHeaderVersion::V1 => 222 + MAGIC_BYTES.len(),
|
|
}
|
|
}
|
|
|
|
// This includes the magic bytes at the start of the file
|
|
#[must_use]
|
|
pub const fn aad_length(&self) -> usize {
|
|
match self.version {
|
|
FileHeaderVersion::V1 => 30 + MAGIC_BYTES.len(),
|
|
}
|
|
}
|
|
|
|
// The AAD retrieval here could be optimised - we do rewind a couple of times
|
|
/// This deserializes a header directly from a reader, and leaves the reader at the start of the encrypted data
|
|
/// It returns both the header, and the AAD that should be used for decryption
|
|
pub fn deserialize<R>(reader: &mut R) -> Result<(Self, Vec<u8>), Error>
|
|
where
|
|
R: Read + Seek,
|
|
{
|
|
let mut magic_bytes = [0u8; MAGIC_BYTES.len()];
|
|
reader.read(&mut magic_bytes).map_err(Error::Io)?;
|
|
|
|
if magic_bytes != MAGIC_BYTES {
|
|
return Err(Error::FileHeader);
|
|
}
|
|
|
|
let mut version = [0u8; 2];
|
|
|
|
reader.read(&mut version).map_err(Error::Io)?;
|
|
let version = FileHeaderVersion::deserialize(version)?;
|
|
|
|
let header = match version {
|
|
FileHeaderVersion::V1 => {
|
|
let mut algorithm = [0u8; 2];
|
|
reader.read(&mut algorithm).map_err(Error::Io)?;
|
|
let algorithm = Algorithm::deserialize(algorithm)?;
|
|
|
|
let mut mode = [0u8; 2];
|
|
reader.read(&mut mode).map_err(Error::Io)?;
|
|
let mode = Mode::deserialize(mode)?;
|
|
|
|
let mut nonce = vec![0u8; algorithm.nonce_len(mode)];
|
|
reader.read(&mut nonce).map_err(Error::Io)?;
|
|
|
|
// read and discard the padding
|
|
reader
|
|
.read(&mut vec![0u8; 24 - nonce.len()])
|
|
.map_err(Error::Io)?;
|
|
|
|
let mut keyslots: Vec<Keyslot> = Vec::new();
|
|
|
|
for _ in 0..2 {
|
|
if let Ok(keyslot) = Keyslot::deserialize(reader) {
|
|
keyslots.push(keyslot);
|
|
}
|
|
}
|
|
|
|
Self {
|
|
version,
|
|
algorithm,
|
|
mode,
|
|
nonce,
|
|
keyslots,
|
|
}
|
|
}
|
|
};
|
|
|
|
// Rewind so we can get the AAD
|
|
reader.rewind().map_err(Error::Io)?;
|
|
|
|
let mut aad = vec![0u8; header.aad_length()];
|
|
reader.read(&mut aad).map_err(Error::Io)?;
|
|
|
|
// We return the cursor position to the end of the header,
|
|
// So that the encrypted data can be read directly afterwards
|
|
reader
|
|
.seek(std::io::SeekFrom::Start(header.length() as u64))
|
|
.map_err(Error::Io)?;
|
|
|
|
Ok((header, aad))
|
|
}
|
|
}
|