Skip to content
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

Vera/ENG-416 Fix WNFS SnapshotKey Decryption #11

Merged
merged 7 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
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
Loading