Skip to content

Commit

Permalink
Merge pull request #11 from banyancomputer/vera/eng-416-fix-wnfs-snap…
Browse files Browse the repository at this point in the history
…shotkey-decryption

Vera/ENG-416 Fix WNFS SnapshotKey Decryption
  • Loading branch information
organizedgrime authored Nov 20, 2023
2 parents 55858a9 + 829629a commit 169e7d7
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 38 deletions.
48 changes: 43 additions & 5 deletions wnfs/src/private/directory.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{
encrypted::Encrypted, link::PrivateLink, PrivateDirectoryContentSerializable, PrivateFile,
PrivateForest, PrivateNode, PrivateNodeContentSerializable, PrivateNodeHeader, PrivateRef,
TemporalKey,
encrypted::Encrypted, link::PrivateLink, AesKey, PrivateDirectoryContentSerializable,
PrivateFile, PrivateForest, PrivateNode, PrivateNodeContentSerializable, PrivateNodeHeader,
PrivateRef, SnapshotKey, TemporalKey, KEY_BYTE_SIZE,
};
use crate::{error::FsError, traits::Id, SearchResult, WNFS_VERSION};
use anyhow::{bail, ensure, Result};
Expand Down Expand Up @@ -1459,7 +1459,7 @@ impl PrivateDirectory {
}

/// Creates a new [`PrivateDirectory`] from a [`PrivateDirectoryContentSerializable`].
pub(crate) async fn from_serializable(
pub(crate) async fn from_serializable_temporal(
serializable: PrivateDirectoryContentSerializable,
temporal_key: &TemporalKey,
cid: Cid,
Expand All @@ -1483,9 +1483,47 @@ impl PrivateDirectory {
entries: entries_decrypted,
};

let header = PrivateNodeHeader::load(&serializable.header_cid, temporal_key, store).await?;
let header =
PrivateNodeHeader::load_temporal(&serializable.header_cid, temporal_key, store).await?;
Ok(Self { header, content })
}

#[allow(dead_code)]
/// Creates a new [`PrivateDirectory`] from a [`PrivateDirectoryContentSerializable`].
pub(crate) async fn from_serializable_snapshot(
serializable: PrivateDirectoryContentSerializable,
snapshot_key: &SnapshotKey,
cid: Cid,
store: &impl BlockStore,
) -> Result<Self> {
if serializable.version.major != 0 || serializable.version.minor != 2 {
bail!(FsError::UnexpectedVersion(serializable.version));
}

let mut entries_decrypted = BTreeMap::new();
// let temporal_key = TemporalKey(snapshot_key.0.to_owned());
for (name, private_ref_serializable) in serializable.entries {
let private_ref = PrivateRef {
saturated_name_hash: private_ref_serializable.saturated_name_hash,
// What are we supposed to do here in the absence of a parent key? This node is not decryptable
temporal_key: TemporalKey(AesKey::new([0u8; KEY_BYTE_SIZE])),
content_cid: private_ref_serializable.content_cid,
};
entries_decrypted.insert(name, PrivateLink::from_ref(private_ref));
}

let content = PrivateDirectoryContent {
persisted_as: OnceCell::new_with(Some(cid)),
metadata: serializable.metadata,
previous: serializable.previous.into_iter().collect(),
entries: entries_decrypted,
};

let header =
PrivateNodeHeader::load_snapshot(&serializable.header_cid, snapshot_key, store).await?;
Ok(Self { header, content })
}

/// Wraps the directory in a [`PrivateNode`].
pub fn as_node(self: &Rc<Self>) -> PrivateNode {
PrivateNode::Dir(Rc::clone(self))
Expand Down
26 changes: 25 additions & 1 deletion wnfs/src/private/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -808,7 +808,31 @@ impl PrivateFile {
content: serializable.content,
};

let header = PrivateNodeHeader::load(&serializable.header_cid, temporal_key, store).await?;
let header =
PrivateNodeHeader::load_temporal(&serializable.header_cid, temporal_key, store).await?;
Ok(Self { header, content })
}

/// Creates a new [`PrivateFile`] from a [`PrivateFileContentSerializable`] but only a Snapshot.
pub(crate) async fn from_serializable_snapshot(
serializable: PrivateFileContentSerializable,
snapshot_key: &SnapshotKey,
cid: Cid,
store: &impl BlockStore,
) -> Result<Self> {
if serializable.version.major != 0 || serializable.version.minor != 2 {
bail!(FsError::UnexpectedVersion(serializable.version));
}

let content = PrivateFileContent {
persisted_as: OnceCell::new_with(Some(cid)),
previous: serializable.previous.into_iter().collect(),
metadata: serializable.metadata,
content: serializable.content,
};

let header =
PrivateNodeHeader::load_snapshot(&serializable.header_cid, snapshot_key, store).await?;
Ok(Self { header, content })
}

Expand Down
102 changes: 92 additions & 10 deletions wnfs/src/private/node/header.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use super::TemporalKey;
use super::{SnapshotKey, TemporalKey};
use crate::private::RevisionRef;
use anyhow::Result;
use libipld::{Cid, IpldCodec};
use libipld::{Cid, Ipld, IpldCodec};
use rand_core::RngCore;
use serde::{Deserialize, Serialize};
use sha3::Sha3_256;
use skip_ratchet::Ratchet;
use std::fmt::Debug;
use std::{collections::BTreeMap, fmt::Debug};
use wnfs_common::{utils, BlockStore, HashOutput, HASH_BYTE_SIZE};
use wnfs_hamt::Hasher;
use wnfs_namefilter::Namefilter;
Expand Down Expand Up @@ -216,21 +216,103 @@ impl PrivateNodeHeader {
/// BlockStore and returns its CID.
pub async fn store(&self, store: &impl BlockStore) -> Result<Cid> {
let temporal_key = self.derive_temporal_key();
let cbor_bytes = serde_ipld_dagcbor::to_vec(self)?;
let ciphertext = temporal_key.key_wrap_encrypt(&cbor_bytes)?;
store.put_block(ciphertext, IpldCodec::Raw).await
let snapshot_key = TemporalKey(temporal_key.derive_snapshot_key().0);

let inumber_bytes =
snapshot_key.key_wrap_encrypt(&serde_ipld_dagcbor::to_vec(&self.inumber)?)?;
let ratchet_bytes =
temporal_key.key_wrap_encrypt(&serde_ipld_dagcbor::to_vec(&self.ratchet)?)?;
let bare_name_bytes =
snapshot_key.key_wrap_encrypt(&serde_ipld_dagcbor::to_vec(&self.bare_name)?)?;

let inumber_cid = store.put_block(inumber_bytes, IpldCodec::Raw).await?;
let ratchet_cid = store.put_block(ratchet_bytes, IpldCodec::Raw).await?;
let bare_name_cid = store.put_block(bare_name_bytes, IpldCodec::Raw).await?;

let mut map = <BTreeMap<String, Ipld>>::new();
map.insert("inumber".to_string(), Ipld::Link(inumber_cid));
map.insert("ratchet".to_string(), Ipld::Link(ratchet_cid));
map.insert("bare_name".to_string(), Ipld::Link(bare_name_cid));

let ipld_bytes = serde_ipld_dagcbor::to_vec(&Ipld::Map(map))?;
store.put_block(ipld_bytes, IpldCodec::Raw).await
}

// async fn load_bytes(cid: &Cid, store: &impl BlockStore) -> Result<(Vec<u8>)> {

// }

/// Loads a private node header from a given CID linking to the ciphertext block
/// to be decrypted with given key.
pub(crate) async fn load(
pub(crate) async fn load_temporal(
cid: &Cid,
temporal_key: &TemporalKey,
store: &impl BlockStore,
) -> Result<PrivateNodeHeader> {
let ciphertext = store.get_block(cid).await?;
let cbor_bytes = temporal_key.key_wrap_decrypt(&ciphertext)?;
Ok(serde_ipld_dagcbor::from_slice(&cbor_bytes)?)
let snapshot_key = temporal_key.derive_snapshot_key();

let ipld_bytes = store.get_block(cid).await?;
let Ipld::Map(map) = serde_ipld_dagcbor::from_slice(&ipld_bytes)? else {
return Err(anyhow::anyhow!("Unable to deserialize ipld map"));
};

let Some(Ipld::Link(inumber_cid)) = map.get("inumber") else {
return Err(anyhow::anyhow!("Missing inumber_cid"));
};
let Some(Ipld::Link(ratchet_cid)) = map.get("ratchet") else {
return Err(anyhow::anyhow!("Missing ratchet_cid"));
};
let Some(Ipld::Link(bare_name_cid)) = map.get("bare_name") else {
return Err(anyhow::anyhow!("Missing bare_name_cid"));
};

let inumber_bytes = TemporalKey(snapshot_key.0.to_owned())
.key_wrap_decrypt(&store.get_block(inumber_cid).await?)?;
let ratchet_bytes = temporal_key.key_wrap_decrypt(&store.get_block(ratchet_cid).await?)?;
let bare_name_bytes = TemporalKey(snapshot_key.0.to_owned())
.key_wrap_decrypt(&store.get_block(bare_name_cid).await?)?;

let inumber: [u8; HASH_BYTE_SIZE] = serde_ipld_dagcbor::from_slice(&inumber_bytes)?;
let ratchet: Ratchet = serde_ipld_dagcbor::from_slice(&ratchet_bytes)?;
let bare_name: Namefilter = serde_ipld_dagcbor::from_slice(&bare_name_bytes)?;

Ok(Self {
inumber,
ratchet,
bare_name,
})
}

pub(crate) async fn load_snapshot(
cid: &Cid,
snapshot_key: &SnapshotKey,
store: &impl BlockStore,
) -> Result<PrivateNodeHeader> {
let ipld_bytes = store.get_block(cid).await?;
let Ipld::Map(map) = serde_ipld_dagcbor::from_slice(&ipld_bytes)? else {
return Err(anyhow::anyhow!("Unable to deserialize ipld map"));
};

let Some(Ipld::Link(inumber_cid)) = map.get("inumber") else {
return Err(anyhow::anyhow!("Missing inumber_cid"));
};
let Some(Ipld::Link(bare_name_cid)) = map.get("bare_name") else {
return Err(anyhow::anyhow!("Missing bare_name_cid"));
};

let inumber_bytes = TemporalKey(snapshot_key.0.to_owned())
.key_wrap_decrypt(&store.get_block(inumber_cid).await?)?;
let bare_name_bytes = TemporalKey(snapshot_key.0.to_owned())
.key_wrap_decrypt(&store.get_block(bare_name_cid).await?)?;

let inumber: [u8; HASH_BYTE_SIZE] = serde_ipld_dagcbor::from_slice(&inumber_bytes)?;
let bare_name: Namefilter = serde_ipld_dagcbor::from_slice(&bare_name_bytes)?;

Ok(Self {
inumber,
ratchet: Ratchet::default(),
bare_name,
})
}
}

Expand Down
48 changes: 43 additions & 5 deletions wnfs/src/private/node/node.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use super::{PrivateNodeHeader, TemporalKey};
use super::{PrivateNodeHeader, SnapshotKey, TemporalKey};
use crate::{
error::FsError,
private::{
encrypted::Encrypted, link::PrivateLink, PrivateDirectory, PrivateFile, PrivateForest,
PrivateNodeContentSerializable, PrivateRef,
encrypted::Encrypted, link::PrivateLink, share::SnapshotSharePointer, PrivateDirectory,
PrivateFile, PrivateForest, PrivateNodeContentSerializable, PrivateRef,
},
traits::Id,
};
use anyhow::{bail, Result};
use anyhow::{anyhow, bail, Result};
use async_once_cell::OnceCell;
use async_recursion::async_recursion;
use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -481,9 +481,46 @@ impl PrivateNode {
_ => return Err(FsError::NotFound.into()),
};

// let snapshot_key = private_ref.temporal_key.derive_snapshot_key();
Self::from_cid(cid, &private_ref.temporal_key, store).await
}

/// A version of the load function designed to work when only a SnapshotKey is available
pub async fn load_from_snapshot(
snapshot: SnapshotSharePointer,
forest: &PrivateForest,
store: &impl BlockStore,
) -> Result<PrivateNode> {
let cid = match forest.get_encrypted(&snapshot.label, store).await? {
Some(cids) if cids.contains(&snapshot.content_cid) => snapshot.content_cid,
_ => return Err(FsError::NotFound.into()),
};

Self::from_cid_snapshot(cid, &snapshot.snapshot_key, store).await
}

pub(crate) async fn from_cid_snapshot(
cid: Cid,
snapshot_key: &SnapshotKey,
store: &impl BlockStore,
) -> Result<PrivateNode> {
let encrypted_bytes = store.get_block(&cid).await?;
let bytes = snapshot_key.decrypt(&encrypted_bytes)?;
let node: PrivateNodeContentSerializable = serde_ipld_dagcbor::from_slice(&bytes)?;
let node = match node {
PrivateNodeContentSerializable::File(file) => {
let file =
PrivateFile::from_serializable_snapshot(file, snapshot_key, cid, store).await?;
PrivateNode::File(Rc::new(file))
}
PrivateNodeContentSerializable::Dir(_) => {
return Err(anyhow!("Not yet able to deserialize Dir from snapshot"));
}
};

Ok(node)
}

pub(crate) async fn from_cid(
cid: Cid,
temporal_key: &TemporalKey,
Expand All @@ -500,7 +537,8 @@ impl PrivateNode {
}
PrivateNodeContentSerializable::Dir(dir) => {
let dir =
PrivateDirectory::from_serializable(dir, temporal_key, cid, store).await?;
PrivateDirectory::from_serializable_temporal(dir, temporal_key, cid, store)
.await?;
PrivateNode::Dir(Rc::new(dir))
}
};
Expand Down
Loading

0 comments on commit 169e7d7

Please sign in to comment.