Skip to content

feat: Switch from AES-GCM to XChaCha20-Poly1305 #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion wnfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ homepage = "https://fission.codes"
authors = ["The Fission Authors"]

[dependencies]
aes-gcm = "0.10"
aes-kw = { version = "0.2", features = ["alloc"] }
anyhow = "1.0"
async-once-cell = "0.4"
async-recursion = "1.0"
async-stream = "0.3"
async-trait = "0.1"
bytes = "1.4.0"
chacha20poly1305 = "0.10"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
futures = "0.3"
libipld-core = { version = "0.16" }
Expand Down
8 changes: 4 additions & 4 deletions wnfs/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ pub enum ShareError {
AccessKeyNotFound,
}

/// AES-GCM errors.
/// Symmetric encryption errors.
#[derive(Debug, Error)]
pub enum AesError {
pub enum CryptError {
#[error("Unable to encrypt data: {0}")]
UnableToEncrypt(String),
UnableToEncrypt(anyhow::Error),

#[error("Unable to decrypt data: {0}")]
UnableToDecrypt(String),
UnableToDecrypt(anyhow::Error),
}

/// RSA related errors
Expand Down
2 changes: 1 addition & 1 deletion wnfs/src/private/encrypted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
/// Any data wrapped like this **must not have low entropy**.
///
/// For anything that could potentially have low entropy,
/// please use AES-GCM instead via `SnapshotKey`.
/// please use XChaCha20-Poly1305 instead via `SnapshotKey`.
///
/// When serialized or deserialized this will only
/// ever emit or consume ciphertexts.
Expand Down
9 changes: 4 additions & 5 deletions wnfs/src/private/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ impl PrivateFile {
loop {
let mut current_block = vec![0u8; MAX_BLOCK_SIZE];
let nonce = SnapshotKey::generate_nonce(rng);
current_block[..NONCE_SIZE].copy_from_slice(&nonce);
current_block[..NONCE_SIZE].copy_from_slice(nonce.as_ref());

// read up to MAX_BLOCK_CONTENT_SIZE content

Expand All @@ -506,7 +506,7 @@ impl PrivateFile {
current_block.truncate(bytes_written + NONCE_SIZE);

let tag = key.encrypt_in_place(&nonce, &mut current_block[NONCE_SIZE..])?;
current_block.extend_from_slice(&tag);
current_block.extend_from_slice(tag.as_ref());

let content_cid = store.put_block(current_block, CODEC_RAW).await?;

Expand Down Expand Up @@ -582,15 +582,14 @@ impl PrivateFile {
}

fn create_revision_name(file_block_name: &Name, key: &SnapshotKey) -> Name {
let revision_segment =
NameSegment::from_digest(Sha3_256::new().chain_update(key.0.as_bytes()));
let revision_segment = NameSegment::from_digest(Sha3_256::new().chain_update(key.0));
file_block_name.with_segments_added(Some(revision_segment))
}

/// Creates the label for a block of a file.
fn create_block_label(key: &SnapshotKey, index: usize, file_revision_name: &Name) -> Name {
let key_hash = Sha3_256::new()
.chain_update(key.0.as_bytes())
.chain_update(key.0)
.chain_update(index.to_le_bytes());
let elem = NameSegment::from_digest(key_hash);

Expand Down
4 changes: 2 additions & 2 deletions wnfs/src/private/forest/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
error::AesError,
error::CryptError,
private::{PrivateNode, TemporalKey},
};
use anyhow::Result;
Expand Down Expand Up @@ -133,7 +133,7 @@ pub trait PrivateForest {
for cid in cids {
match PrivateNode::from_cid(*cid, temporal_key, store, parent_name.clone(), setup).await {
Ok(node) => yield Ok(node),
Err(e) if e.downcast_ref::<AesError>().is_some() => {
Err(e) if e.downcast_ref::<CryptError>().is_some() => {
// we likely matched a PrivateNodeHeader instead of a PrivateNode.
// we skip it
}
Expand Down
84 changes: 0 additions & 84 deletions wnfs/src/private/keys/aes.rs

This file was deleted.

2 changes: 0 additions & 2 deletions wnfs/src/private/keys/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
mod access;
mod aes;
mod exchange;
mod privateref;

pub use self::exchange::*;
pub use access::*;
pub use aes::*;
pub(crate) use privateref::*;
31 changes: 11 additions & 20 deletions wnfs/src/private/keys/privateref.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use super::KEY_BYTE_SIZE;
use crate::{
error::{AesError, FsError},
private::{PrivateRefSerializable, TemporalKey},
error::FsError,
private::{PrivateRefSerializable, TemporalKey, KEY_BYTE_SIZE},
};
use aes_kw::KekAes256;
use anyhow::Result;
use libipld_core::cid::Cid;
use serde::{de::Error as DeError, ser::Error as SerError, Deserialize, Serialize};
Expand Down Expand Up @@ -62,11 +60,9 @@ impl PrivateRef {
parent_temporal_key: &TemporalKey,
) -> Result<PrivateRefSerializable> {
let snapshot_key = self.temporal_key.derive_snapshot_key();
// encrypt ratchet key
let temporal_key_as_kek = KekAes256::from(parent_temporal_key.0.clone().bytes());
let temporal_key_wrapped = temporal_key_as_kek
.wrap_with_padding_vec(self.temporal_key.0.as_bytes())
.map_err(|e| AesError::UnableToEncrypt(format!("{e}")))?;

// encrypt temporal key
let temporal_key_wrapped = parent_temporal_key.key_wrap_encrypt(&self.temporal_key.0)?;

Ok(PrivateRefSerializable {
revision_name_hash: self.revision_name_hash,
Expand All @@ -80,25 +76,20 @@ impl PrivateRef {
private_ref: PrivateRefSerializable,
parent_temporal_key: &TemporalKey,
) -> Result<Self> {
// TODO: Move key wrapping & unwrapping logic to impl TemporalKey
let temporal_key_as_kek = KekAes256::from(parent_temporal_key.0.clone().bytes());

let temporal_key_raw: [u8; KEY_BYTE_SIZE] = temporal_key_as_kek
.unwrap_with_padding_vec(&private_ref.temporal_key)
.map_err(|e| AesError::UnableToDecrypt(format!("{e}")))?
.try_into()
.map_err(|e: Vec<u8>| {
let temporal_key_decrypted =
parent_temporal_key.key_wrap_decrypt(&private_ref.temporal_key)?;

let temporal_key_raw: [u8; KEY_BYTE_SIZE] =
temporal_key_decrypted.try_into().map_err(|e: Vec<u8>| {
FsError::InvalidDeserialization(format!(
"Expected 32 bytes for ratchet key, but got {}",
e.len()
))
})?;

let temporal_key = temporal_key_raw.into();

Ok(Self {
revision_name_hash: private_ref.revision_name_hash,
temporal_key,
temporal_key: temporal_key_raw.into(),
content_cid: private_ref.content_cid,
})
}
Expand Down
2 changes: 1 addition & 1 deletion wnfs/src/private/node/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl PrivateNodeHeader {
/// ```
/// use std::rc::Rc;
/// use wnfs::private::{
/// PrivateFile, AesKey,
/// PrivateFile,
/// forest::{hamt::HamtForest, traits::PrivateForest},
/// };
/// use chrono::Utc;
Expand Down
Loading