diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index 781c44f..700be2f 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -26,4 +26,6 @@ jobs: run: cargo clippy --all-targets --all-features -- -D warnings - name: Run tests + env: + RUST_MIN_STACK: 8388608 run: cargo test --verbose diff --git a/Cargo.toml b/Cargo.toml index 48740b9..6aaee9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,17 +4,16 @@ version = "0.1.0" edition = "2021" [dependencies] -async-stream = "0.3.5" -futures-core = "0.3.30" -futures-util = "0.3.30" -iroh = "0.17.0" -tokio = { version = "1", features = ["full"] } -tokio-stream = "0.1.15" -veilid-core = "0.3.3" +iroh-blobs = "0.23.0" +veilid-core = { git = "https://gitlab.com/veilid/veilid.git", version = "0.3.4" } eyre = "0.6" tracing = "0.1" xdg = "2.4" tmpdir = "1" serde = "1.0.204" serde_cbor = "0.11.2" -clap = "4.5.9" \ No newline at end of file +clap = "4.5.9" +anyhow = "1.0.86" +tokio = {version ="1.39.3", features=["full"] } +async-stream = "0.3.5" +futures-core = "0.3.30" diff --git a/src/backend.rs b/src/backend.rs index 3746725..668fb5c 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -1,18 +1,28 @@ -use std::path::{Path, PathBuf}; +use crate::common::{CommonKeypair, DHTEntity}; +use crate::group::Group; +use crate::repo::Repo; +use anyhow::{anyhow, Result}; +use iroh_blobs::Hash; use std::collections::HashMap; -use eyre::{Result, anyhow}; +use std::mem; +use std::path::{Path, PathBuf}; +use std::sync::Arc; use tokio::fs; +use tokio::sync::{mpsc, oneshot}; use tracing::info; +use veilid_core::{ + api_startup_config, vld0_generate_keypair, CryptoKey, CryptoSystem, CryptoSystemVLD0, + CryptoTyped, DHTSchema, KeyPair, ProtectedStore, RoutingContext, SharedSecret, UpdateCallback, + VeilidAPI, VeilidConfigInner, VeilidUpdate, CRYPTO_KIND_VLD0, TypedKey +}; use xdg::BaseDirectories; -use veilid_core::{VeilidAPI, CryptoKey, VeilidUpdate, VeilidConfigInner, api_startup_config, DHTSchema, CRYPTO_KIND_VLD0, vld0_generate_keypair, CryptoTyped, CryptoSystemVLD0, RoutingContext, KeyPair, ProtectedStore}; // Added ProtectedStore here -use std::sync::Arc; -use crate::group::{Group, GroupKeypair}; pub struct Backend { path: PathBuf, port: u16, veilid_api: Option<VeilidAPI>, groups: HashMap<CryptoKey, Box<Group>>, + repos: HashMap<CryptoKey, Box<Repo>>, } impl Backend { @@ -22,16 +32,41 @@ impl Backend { port, veilid_api: None, groups: HashMap::new(), + repos: HashMap::new(), }) } pub async fn start(&mut self) -> Result<()> { - println!("Starting on {} with port {}", self.path.display(), self.port); + println!( + "Starting on {} with port {}", + self.path.display(), + self.port + ); let base_dir = &self.path; - fs::create_dir_all(base_dir).await.map_err(|e| anyhow!("Failed to create base directory {}: {}", base_dir.display(), e))?; + fs::create_dir_all(base_dir).await.map_err(|e| { + anyhow!( + "Failed to create base directory {}: {}", + base_dir.display(), + e + ) + })?; - let update_callback: Arc<dyn Fn(VeilidUpdate) + Send + Sync> = Arc::new(|update| { - info!("Received update: {:?}", update); + let (tx, mut rx) = mpsc::channel(1); + + let update_callback: UpdateCallback = Arc::new(move |update| { + // Else handle update for something + // info!("Received update: {:?}", update); + if let VeilidUpdate::Attachment(attachment_state) = &update { + if attachment_state.public_internet_ready { + println!("Public internet ready!"); + let tx = tx.clone(); + tokio::spawn(async move { + if tx.send(()).await.is_err() { + println!("receiver dropped"); + } + }); + } + } }); let xdg_dirs = BaseDirectories::with_prefix("save-dweb-backend")?; @@ -44,7 +79,10 @@ impl Backend { protected_store: veilid_core::VeilidConfigProtectedStore { allow_insecure_fallback: true, always_use_insecure_storage: true, - directory: base_dir.join("protected_store").to_string_lossy().to_string(), + directory: base_dir + .join("protected_store") + .to_string_lossy() + .to_string(), delete: false, device_encryption_key_password: "".to_string(), new_device_encryption_key_password: None, @@ -60,75 +98,113 @@ impl Backend { network: Default::default(), }; - self.veilid_api = Some(api_startup_config(update_callback, config_inner).await.map_err(|e| anyhow!("Failed to initialize Veilid API: {}", e))?); + if self.veilid_api.is_none() { + let veilid_api = api_startup_config(update_callback, config_inner) + .await + .map_err(|e| anyhow!("Failed to initialize Veilid API: {}", e))?; + self.veilid_api = Some(veilid_api); + } else { + return Err(anyhow!("Veilid already initialized")); + } + + self.veilid_api.clone().unwrap().attach().await?; + + println!("Waiting for network ready state"); + + rx.recv().await.expect("Unable to wait for veilid init"); Ok(()) } - pub async fn stop(&self) -> Result<()> { + pub async fn stop(&mut self) -> Result<()> { println!("Stopping Backend..."); - if let Some(veilid) = &self.veilid_api { - veilid.clone().shutdown().await; + if self.veilid_api.is_some() { + println!("Shutting down Veilid API"); + let veilid = self.veilid_api.take(); + veilid.unwrap().shutdown().await; + println!("Veilid API shut down successfully"); + self.groups = HashMap::new(); + self.repos = HashMap::new(); } Ok(()) } pub async fn create_group(&mut self) -> Result<Group> { - let veilid = self.veilid_api.as_ref().ok_or_else(|| anyhow!("Veilid API is not initialized"))?; + let veilid = self + .veilid_api + .as_ref() + .ok_or_else(|| anyhow!("Veilid API is not initialized"))?; let routing_context = veilid.routing_context()?; let schema = DHTSchema::dflt(1)?; let kind = Some(CRYPTO_KIND_VLD0); - + let dht_record = routing_context.create_dht_record(schema, kind).await?; let keypair = vld0_generate_keypair(); - let encryption_key = CryptoTyped::new(CRYPTO_KIND_VLD0, CryptoKey::new([0; 32])); - let crypto_system = CryptoSystemVLD0::new(veilid.crypto()?); - + + let encryption_key = crypto_system.random_shared_secret(); + let group = Group::new( - keypair.key.clone(), - dht_record, + dht_record.clone(), encryption_key, - Some(CryptoTyped::new(CRYPTO_KIND_VLD0, keypair.secret)), Arc::new(routing_context), crypto_system, ); - + let protected_store = veilid.protected_store().unwrap(); - group.store_keypair(&protected_store).await?; - - self.groups.insert(group.get_id(), Box::new(group.clone())); - + CommonKeypair { + id: group.id(), + public_key: dht_record.owner().clone(), + secret_key: group.get_secret_key(), + encryption_key: group.get_encryption_key(), + } + .store_keypair(&protected_store) + .await + .map_err(|e| anyhow!(e))?; + + self.groups.insert(group.id(), Box::new(group.clone())); + Ok(group) } - pub async fn get_group(&self, key: CryptoKey) -> Result<Box<Group>> { - if let Some(group) = self.groups.get(&key) { + pub async fn get_group(&mut self, record_key: TypedKey) -> Result<Box<Group>> { + if let Some(group) = self.groups.get(&record_key.value) { return Ok(group.clone()); } - - let protected_store = self.veilid_api.as_ref().unwrap().protected_store().unwrap(); - let keypair_data = protected_store.load_user_secret(key.to_string()).await.map_err(|_| anyhow!("Failed to load keypair"))?.ok_or_else(|| anyhow!("Keypair not found"))?; - let retrieved_keypair: GroupKeypair = serde_cbor::from_slice(&keypair_data).map_err(|_| anyhow!("Failed to deserialize keypair"))?; - + let routing_context = self.veilid_api.as_ref().unwrap().routing_context()?; - let dht_record = if let Some(secret_key) = retrieved_keypair.secret_key.clone() { - routing_context.open_dht_record(CryptoTyped::new(CRYPTO_KIND_VLD0, retrieved_keypair.public_key.clone()), Some(KeyPair { key: retrieved_keypair.public_key.clone(), secret: secret_key })).await? - } else { - routing_context.open_dht_record(CryptoTyped::new(CRYPTO_KIND_VLD0, retrieved_keypair.public_key.clone()), None).await? - }; + let protected_store = self.veilid_api.as_ref().unwrap().protected_store().unwrap(); + + // Load the keypair associated with the record_key from the protected store + let retrieved_keypair = CommonKeypair::load_keypair(&protected_store, &record_key.value) + .await + .map_err(|_| anyhow!("Failed to load keypair"))?; let crypto_system = CryptoSystemVLD0::new(self.veilid_api.as_ref().unwrap().crypto()?); - - let group = Group::new( - retrieved_keypair.public_key.clone(), - dht_record, - CryptoTyped::new(CRYPTO_KIND_VLD0, retrieved_keypair.encryption_key), - retrieved_keypair.secret_key.map(|sk| CryptoTyped::new(CRYPTO_KIND_VLD0, sk)), - Arc::new(routing_context), + + // First open the DHT record + let dht_record = routing_context + .open_dht_record(record_key.clone(), None) // Don't pass a writer here yet + .await?; + + // Use the owner key from the DHT record as the default writer + let owner_key = dht_record.owner(); // Call the owner() method to get the owner key + + // Reopen the DHT record with the owner key as the writer + let dht_record = routing_context + .open_dht_record(record_key.clone(), Some(KeyPair::new(owner_key.clone(), retrieved_keypair.secret_key.clone().unwrap()))) + .await?; + + + let group = Group { + dht_record: dht_record.clone(), + encryption_key: retrieved_keypair.encryption_key.clone(), + routing_context: Arc::new(routing_context), crypto_system, - ); - + repos: Vec::new(), + }; + self.groups.insert(group.id(), Box::new(group.clone())); + Ok(Box::new(group)) } @@ -138,7 +214,7 @@ impl Backend { pub async fn close_group(&mut self, key: CryptoKey) -> Result<()> { if let Some(group) = self.groups.remove(&key) { - group.close().await?; + group.close().await.map_err(|e| anyhow!(e))?; } else { return Err(anyhow!("Group not found")); } @@ -151,4 +227,82 @@ impl Backend { .ok_or_else(|| anyhow!("Veilid API not initialized")) .map(|api| Arc::new(api.protected_store().unwrap())) } + + pub async fn create_repo(&mut self) -> Result<Repo> { + let veilid = self + .veilid_api + .as_ref() + .ok_or_else(|| anyhow!("Veilid API is not initialized"))?; + let routing_context = veilid.routing_context()?; + let schema = DHTSchema::dflt(1)?; + let kind = Some(CRYPTO_KIND_VLD0); + + let dht_record = routing_context.create_dht_record(schema, kind).await?; + let keypair = vld0_generate_keypair(); + let crypto_system = CryptoSystemVLD0::new(veilid.crypto()?); + let encryption_key = crypto_system.random_shared_secret(); + + let repo = Repo::new( + keypair.key.clone(), + dht_record, + encryption_key, + Some(CryptoTyped::new(CRYPTO_KIND_VLD0, keypair.secret)), + Arc::new(routing_context), + crypto_system, + ); + + self.repos.insert(repo.get_id(), Box::new(repo.clone())); + + Ok(repo) + } + + pub async fn get_repo(&self, key: CryptoKey) -> Result<Box<Repo>> { + if let Some(repo) = self.repos.get(&key) { + return Ok(repo.clone()); + } + + let protected_store = self.veilid_api.as_ref().unwrap().protected_store().unwrap(); + let keypair_data = protected_store + .load_user_secret(key.to_string()) + .await + .map_err(|_| anyhow!("Failed to load keypair"))? + .ok_or_else(|| anyhow!("Keypair not found"))?; + let retrieved_keypair: CommonKeypair = serde_cbor::from_slice(&keypair_data) + .map_err(|_| anyhow!("Failed to deserialize keypair"))?; + + let routing_context = self.veilid_api.as_ref().unwrap().routing_context()?; + let dht_record = if let Some(secret_key) = retrieved_keypair.secret_key.clone() { + routing_context + .open_dht_record( + CryptoTyped::new(CRYPTO_KIND_VLD0, retrieved_keypair.public_key.clone()), + Some(KeyPair { + key: retrieved_keypair.public_key.clone(), + secret: secret_key, + }), + ) + .await? + } else { + routing_context + .open_dht_record( + CryptoTyped::new(CRYPTO_KIND_VLD0, retrieved_keypair.public_key.clone()), + None, + ) + .await? + }; + + let crypto_system = CryptoSystemVLD0::new(self.veilid_api.as_ref().unwrap().crypto()?); + + let repo = Repo { + id: retrieved_keypair.public_key.clone(), + dht_record, + encryption_key: SharedSecret::new([0; 32]), + secret_key: retrieved_keypair + .secret_key + .map(|sk| CryptoTyped::new(CRYPTO_KIND_VLD0, sk)), + routing_context: Arc::new(routing_context), + crypto_system, + }; + + Ok(Box::new(repo)) + } } diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..1d5823e --- /dev/null +++ b/src/common.rs @@ -0,0 +1,109 @@ +#![allow(async_fn_in_trait)] +#![allow(clippy::async_yields_async)] + +use serde::{Serialize, Deserialize}; +use eyre::{Result, anyhow}; +use std::sync::Arc; +use veilid_core::{ + CryptoKey, SharedSecret, CryptoTyped, DHTRecordDescriptor, RoutingContext, CryptoSystemVLD0, + ProtectedStore, Nonce, CRYPTO_KIND_VLD0, CryptoSystem +}; + +#[derive(Serialize, Deserialize)] +pub struct CommonKeypair { + pub id: CryptoKey, + pub public_key: CryptoKey, + pub secret_key: Option<CryptoKey>, + pub encryption_key: SharedSecret, +} + +impl CommonKeypair { + pub async fn store_keypair(&self, protected_store: &ProtectedStore) -> Result<()> { + let keypair_data = serde_cbor::to_vec(&self).map_err(|e| anyhow!("Failed to serialize keypair: {}", e))?; + protected_store.save_user_secret(self.id.to_string(), &keypair_data).await.map_err(|e| anyhow!("Unable to store keypair: {}", e))?; + Ok(()) + } + + pub async fn load_keypair(protected_store: &ProtectedStore, id: &CryptoKey) -> Result<Self> { + let keypair_data = protected_store.load_user_secret(id.to_string()).await.map_err(|_| anyhow!("Failed to load keypair"))?.ok_or_else(|| anyhow!("Keypair not found"))?; + let retrieved_keypair: CommonKeypair = serde_cbor::from_slice(&keypair_data).map_err(|_| anyhow!("Failed to deserialize keypair"))?; + Ok(retrieved_keypair) + } +} + + +pub trait DHTEntity { + fn get_id(&self) -> CryptoKey; + fn get_encryption_key(&self) -> SharedSecret; + fn get_routing_context(&self) -> Arc<RoutingContext>; + fn get_crypto_system(&self) -> CryptoSystemVLD0; + fn get_dht_record(&self) -> DHTRecordDescriptor; + fn get_secret_key(&self) -> Option<CryptoKey>; + + fn encrypt_aead(&self, data: &[u8], associated_data: Option<&[u8]>) -> Result<Vec<u8>> { + let nonce = self.get_crypto_system().random_nonce(); + let mut buffer = Vec::with_capacity(nonce.as_slice().len() + data.len()); + buffer.extend_from_slice(nonce.as_slice()); + buffer.extend_from_slice( + &self + .get_crypto_system() + .encrypt_aead(data, &nonce, &self.get_encryption_key(), associated_data) + .map_err(|e| anyhow!("Failed to encrypt data: {}", e))?, + ); + Ok(buffer) + } + + fn decrypt_aead(&self, data: &[u8], associated_data: Option<&[u8]>) -> Result<Vec<u8>> { + let nonce: [u8; 24] = data[..24].try_into().map_err(|_| anyhow!("Failed to convert nonce slice to array"))?; + let nonce = Nonce::new(nonce); + let encrypted_data = &data[24..]; + self.get_crypto_system() + .decrypt_aead(encrypted_data, &nonce, &self.get_encryption_key(), associated_data) + .map_err(|e| anyhow!("Failed to decrypt data: {}", e)) + } + + async fn set_name(&self, name: &str) -> Result<()> { + let routing_context = self.get_routing_context(); + let key = self.get_dht_record().key().clone(); + let encrypted_name = self.encrypt_aead(name.as_bytes(), None)?; + routing_context.set_dht_value(key, 0, encrypted_name, None).await?; + Ok(()) + } + + async fn get_name(&self) -> Result<String> { + let routing_context = self.get_routing_context(); + let key = self.get_dht_record().key().clone(); + let value = routing_context.get_dht_value(key, 0, false).await?; + match value { + Some(value) => { + let decrypted_name = self.decrypt_aead(value.data(), None)?; + Ok(String::from_utf8(decrypted_name).map_err(|e| anyhow!("Failed to convert DHT value to string: {}", e))?) + } + None => Err(anyhow!("Value not found")), + } + } + + async fn close(&self) -> Result<()> { + let routing_context = self.get_routing_context(); + let key = self.get_dht_record().key().clone(); + routing_context.close_dht_record(key).await?; + Ok(()) + } + + + fn get_write_key(&self) -> Option<CryptoKey> { + unimplemented!("WIP") + } + + async fn members(&self) -> Result<Vec<CryptoKey>> { + unimplemented!("WIP") + } + + async fn join(&self) -> Result<()> { + unimplemented!("WIP") + } + + async fn leave(&self) -> Result<()> { + unimplemented!("WIP") + } +} diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..1354be5 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,8 @@ +pub const GROUP_NOT_FOUND: &str = "Group not found"; +pub const UNABLE_TO_SET_GROUP_NAME: &str = "Unable to set group name"; +pub const UNABLE_TO_GET_GROUP_NAME: &str = "Unable to get group name"; +pub const TEST_GROUP_NAME: &str = "Test Group"; +pub const UNABLE_TO_STORE_KEYPAIR: &str = "Unable to store keypair"; +pub const FAILED_TO_LOAD_KEYPAIR: &str = "Failed to load keypair"; +pub const KEYPAIR_NOT_FOUND: &str = "Keypair not found"; +pub const FAILED_TO_DESERIALIZE_KEYPAIR: &str = "Failed to deserialize keypair"; \ No newline at end of file diff --git a/src/group.rs b/src/group.rs index 927a7d7..2a1b041 100644 --- a/src/group.rs +++ b/src/group.rs @@ -1,134 +1,90 @@ -use async_stream::stream; -use futures_core::stream::Stream; use serde::{Serialize, Deserialize}; -use eyre::{Result, anyhow}; +use eyre::{Result, Error, anyhow}; use std::sync::Arc; use veilid_core::{ - CryptoKey, DHTRecordDescriptor, CryptoTyped, VeilidUpdate, VeilidConfigInner, api_startup_config, CRYPTO_KIND_VLD0, Nonce, ProtectedStore, CryptoSystemVLD0, CryptoSystem, RoutingContext, KeyPair + CryptoKey, DHTRecordDescriptor, CryptoTyped, CryptoSystemVLD0, RoutingContext, SharedSecret, TypedKey }; -#[derive(Serialize, Deserialize)] -pub struct GroupKeypair { - pub public_key: CryptoKey, - pub secret_key: Option<CryptoKey>, - pub encryption_key: CryptoKey, -} +use crate::common::DHTEntity; +use crate::repo::Repo; #[derive(Clone)] pub struct Group { - pub id: CryptoKey, pub dht_record: DHTRecordDescriptor, - pub encryption_key: CryptoTyped<CryptoKey>, - pub secret_key: Option<CryptoTyped<CryptoKey>>, + pub encryption_key: SharedSecret, pub routing_context: Arc<RoutingContext>, pub crypto_system: CryptoSystemVLD0, + pub repos: Vec<Repo>, } impl Group { pub fn new( - id: CryptoKey, dht_record: DHTRecordDescriptor, - encryption_key: CryptoTyped<CryptoKey>, - secret_key: Option<CryptoTyped<CryptoKey>>, + encryption_key: SharedSecret, routing_context: Arc<RoutingContext>, crypto_system: CryptoSystemVLD0, ) -> Self { Self { - id, dht_record, encryption_key, - secret_key, routing_context, crypto_system, + repos: Vec::new(), } } - pub fn get_id(&self) -> CryptoKey { - self.id.clone() + pub fn id(&self) -> CryptoKey { + self.dht_record.key().value.clone() } - pub fn get_write_key(&self) -> Option<CryptoKey> { - unimplemented!("WIP") + pub fn owner_key(&self) -> CryptoKey { + self.dht_record.owner().clone() } - pub fn get_encryption_key(&self) -> CryptoKey { - self.encryption_key.value + pub fn owner_secret(&self) -> Option<CryptoKey> { + self.dht_record.owner_secret().cloned() } - - pub async fn set_name(&self, name: &str) -> Result<()> { - let routing_context = &self.routing_context; - let key = self.dht_record.key().clone(); - let encrypted_name = self.encrypt_aead(name.as_bytes(), None)?; - routing_context.set_dht_value(key, 0, encrypted_name, None).await?; + + pub async fn add_repo(&mut self, repo: Repo) -> Result<()> { + self.repos.push(repo); Ok(()) } - pub async fn get_name(&self) -> Result<String> { - let routing_context = &self.routing_context; - let key = self.dht_record.key().clone(); - let value = routing_context.get_dht_value(key, 0, false).await?; - match value { - Some(value) => { - let decrypted_name = self.decrypt_aead(value.data(), None)?; - Ok(String::from_utf8(decrypted_name).map_err(|e| anyhow!("Failed to convert DHT value to string: {}", e))?) - } - None => Err(anyhow!("Value not found")), - } - } - - pub async fn name(&self) -> Result<String> { - self.get_name().await + pub async fn list_repos(&self) -> Vec<CryptoKey> { + self.repos.iter().map(|repo| repo.get_id()).collect() } - pub async fn members(&self) -> Result<Vec<CryptoKey>> { - unimplemented!("WIP") + pub async fn get_repo_name(&self, repo_key: CryptoKey) -> Result<String> { + if let Some(repo) = self.repos.iter().find(|repo| repo.get_id() == repo_key) { + repo.get_name().await + } else { + Err(anyhow!("Repo not found")) + } } +} - pub async fn join(&self) -> Result<()> { - unimplemented!("WIP") +impl DHTEntity for Group { + fn get_id(&self) -> CryptoKey { + self.id().clone() } - pub async fn leave(&self) -> Result<()> { - unimplemented!("WIP") + fn get_encryption_key(&self) -> SharedSecret { + self.encryption_key.clone() } - pub async fn close(&self) -> Result<()> { - let routing_context = &self.routing_context; - let key = self.dht_record.key().clone(); - routing_context.close_dht_record(key).await?; - Ok(()) + fn get_routing_context(&self) -> Arc<RoutingContext> { + self.routing_context.clone() } - pub fn encrypt_aead(&self, data: &[u8], associated_data: Option<&[u8]>) -> Result<Vec<u8>> { - let nonce = self.crypto_system.random_nonce(); - let mut buffer = Vec::with_capacity(nonce.as_slice().len() + data.len()); - buffer.extend_from_slice(nonce.as_slice()); - buffer.extend_from_slice( - &self - .crypto_system - .encrypt_aead(data, &nonce, &self.encryption_key.value, associated_data) - .map_err(|e| anyhow!("Failed to encrypt data: {}", e))?, - ); - Ok(buffer) + fn get_crypto_system(&self) -> CryptoSystemVLD0 { + self.crypto_system.clone() } - pub fn decrypt_aead(&self, data: &[u8], associated_data: Option<&[u8]>) -> Result<Vec<u8>> { - let nonce: [u8; 24] = data[..24].try_into().map_err(|_| anyhow!("Failed to convert nonce slice to array"))?; - let nonce = Nonce::new(nonce); - let encrypted_data = &data[24..]; - self.crypto_system - .decrypt_aead(encrypted_data, &nonce, &self.encryption_key.value, associated_data) - .map_err(|e| anyhow!("Failed to decrypt data: {}", e)) + fn get_dht_record(&self) -> DHTRecordDescriptor { + self.dht_record.clone() } - pub async fn store_keypair(&self, protected_store: &ProtectedStore) -> Result<()> { - let keypair = GroupKeypair { - public_key: self.id.clone(), - secret_key: self.secret_key.as_ref().map(|sk| sk.value.clone()), - encryption_key: self.encryption_key.value.clone(), - }; - let keypair_data = serde_cbor::to_vec(&keypair).map_err(|e| anyhow!("Failed to serialize keypair: {}", e))?; - protected_store.save_user_secret(self.id.to_string(), &keypair_data).await.map_err(|e| anyhow!("Unable to store keypair: {}", e))?; - Ok(()) + fn get_secret_key(&self) -> Option<CryptoKey> { + self.owner_secret() } } diff --git a/src/lib.rs b/src/lib.rs index 3e9db7b..2defe1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,95 @@ pub mod group; pub mod repo; pub mod backend; +pub mod common; +pub mod constants; + +use crate::constants::{GROUP_NOT_FOUND, UNABLE_TO_SET_GROUP_NAME, UNABLE_TO_GET_GROUP_NAME, TEST_GROUP_NAME, UNABLE_TO_STORE_KEYPAIR, FAILED_TO_LOAD_KEYPAIR, KEYPAIR_NOT_FOUND, FAILED_TO_DESERIALIZE_KEYPAIR}; + +use crate::backend::Backend; +use crate::common::{CommonKeypair, DHTEntity}; +use veilid_core::{ + vld0_generate_keypair, TypedKey, CRYPTO_KIND_VLD0 +}; + +#[cfg(test)] +mod tests { + use super::*; + use tokio::fs; + use tmpdir::TmpDir; + + #[tokio::test] + async fn basic_test() { + let path = TmpDir::new("test_dweb_backend").await.unwrap(); + let port = 8080; + + fs::create_dir_all(path.as_ref()).await.expect("Failed to create base directory"); + + let mut backend = Backend::new(path.as_ref(), port).expect("Unable to create Backend"); + + backend.start().await.expect("Unable to start"); + let group = backend.create_group().await.expect("Unable to create group"); + + group.set_name(TEST_GROUP_NAME).await.expect(UNABLE_TO_SET_GROUP_NAME); + let name = group.get_name().await.expect(UNABLE_TO_GET_GROUP_NAME); + assert_eq!(name, TEST_GROUP_NAME); + + backend.stop().await.expect("Unable to stop"); + + backend.start().await.expect("Unable to restart"); + let loaded_group = backend.get_group(TypedKey::new(CRYPTO_KIND_VLD0, group.id())).await.expect(GROUP_NOT_FOUND); + + let protected_store = backend.get_protected_store().unwrap(); + let keypair_data = protected_store.load_user_secret(group.id().to_string()) + .await + .expect(FAILED_TO_LOAD_KEYPAIR) + .expect(KEYPAIR_NOT_FOUND); + let retrieved_keypair: CommonKeypair = serde_cbor::from_slice(&keypair_data).expect(FAILED_TO_DESERIALIZE_KEYPAIR); + + // Check that the id matches group.id() + assert_eq!(retrieved_keypair.id, group.id()); + + // Check that the public_key matches the owner public key from the DHT record + assert_eq!(retrieved_keypair.public_key, loaded_group.get_dht_record().owner().clone()); + + // Check that the secret and encryption keys match + assert_eq!(retrieved_keypair.secret_key, group.get_secret_key()); + assert_eq!(retrieved_keypair.encryption_key, group.get_encryption_key()); + + let mut loaded_group = backend.get_group(TypedKey::new(CRYPTO_KIND_VLD0, group.id())).await.expect(GROUP_NOT_FOUND); + + // Check if we can get group name + let group_name = loaded_group.get_name().await.expect(UNABLE_TO_GET_GROUP_NAME); + assert_eq!(group_name, TEST_GROUP_NAME); + + // Compare the loaded group's id with the retrieved id + assert_eq!(loaded_group.id(), retrieved_keypair.id); + + // Create a repo + let repo = backend.create_repo().await.expect("Unable to create repo"); + let repo_key = repo.get_id(); + let repo_name = "Test Repo"; + + // Set and get repo name + repo.set_name(repo_name).await.expect("Unable to set repo name"); + let name = repo.get_name().await.expect("Unable to get repo name"); + assert_eq!(name, repo_name); + + // Add repo to group + loaded_group.add_repo(repo).await.expect("Unable to add repo to group"); + + // List known repos + let repos = loaded_group.list_repos().await; + assert!(repos.contains(&repo_key)); + + // Retrieve repo by key + let loaded_repo = backend.get_repo(repo_key.clone()).await.expect("Repo not found"); + + // Check if repo name is correctly retrieved + let retrieved_name = loaded_repo.get_name().await.expect("Unable to get repo name after restart"); + assert_eq!(retrieved_name, repo_name); + + backend.stop().await.expect("Unable to stop"); + } + +} diff --git a/src/main.rs b/src/main.rs index 05dfef9..e2ae84e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,21 @@ use clap::{Command, Arg}; -use eyre::Result; +use anyhow::{Result, anyhow}; use xdg::BaseDirectories; use tokio::fs; use crate::backend::Backend; -use crate::group::GroupKeypair; +use crate::common::{CommonKeypair, DHTEntity}; +use crate::group::Group; +use crate::repo::Repo; +use crate::constants::{UNABLE_TO_SET_GROUP_NAME, UNABLE_TO_GET_GROUP_NAME}; +mod common; mod group; mod repo; mod backend; +mod constants; #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> anyhow::Result<()> { let matches = Command::new("Save DWeb Backend") .arg(Arg::new("pubkey") .long("pubkey") @@ -38,6 +43,7 @@ async fn main() -> Result<()> { backend.start().await?; + // Check if keys were provided, otherwise create a new group if matches.contains_id("pubkey") && matches.contains_id("seckey") && matches.contains_id("enckey") { let pubkey = matches.get_one::<String>("pubkey").unwrap(); let seckey = matches.get_one::<String>("seckey").unwrap(); @@ -47,56 +53,14 @@ async fn main() -> Result<()> { println!("Provided Encryption Key: {:?}", enckey); } else { let group = backend.create_group().await?; - println!("Group created with Public Key: {:?}", group.get_id()); - println!("Group created with Secret Key: {:?}", group.secret_key.as_ref().unwrap().value); + println!("Group created with Record Key: {:?}", group.id()); + println!("Group created with Secret Key: {:?}", group.get_secret_key().unwrap()); println!("Group created with Encryption Key: {:?}", group.get_encryption_key()); } - + // Await for ctrl-c and then stop the backend tokio::signal::ctrl_c().await?; backend.stop().await?; Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use tokio::fs; - use tmpdir::TmpDir; - - #[tokio::test] - async fn basic_test() { - let path = TmpDir::new("test_dweb_backend").await.unwrap(); - let port = 8080; - - fs::create_dir_all(path.as_ref()).await.expect("Failed to create base directory"); - - let mut backend = Backend::new(path.as_ref(), port).expect("Unable to create Backend"); - - backend.start().await.expect("Unable to start"); - let group = backend.create_group().await.expect("Unable to create group"); - - let group_key = group.get_id(); - group.set_name("Test Group").await.expect("Unable to set group name"); - let name = group.get_name().await.expect("Unable to get group name"); - assert_eq!(name, "Test Group"); - - backend.stop().await.expect("Unable to stop"); - - backend.start().await.expect("Unable to restart"); - let loaded_group = backend.get_group(group_key.clone()).await.expect("Group not found"); - - let protected_store = backend.get_protected_store().unwrap(); - let keypair_data = protected_store.load_user_secret(group_key.to_string()).await.expect("Failed to load keypair").expect("Keypair not found"); - let retrieved_keypair: GroupKeypair = serde_cbor::from_slice(&keypair_data).expect("Failed to deserialize keypair"); - - assert_eq!(retrieved_keypair.public_key, group.get_id()); - assert_eq!(retrieved_keypair.secret_key, group.secret_key.as_ref().map(|sk| sk.value.clone())); - assert_eq!(retrieved_keypair.encryption_key, group.get_encryption_key()); - - assert_eq!(loaded_group.get_id(), retrieved_keypair.public_key); - - backend.stop().await.expect("Unable to stop"); - } -} +} \ No newline at end of file diff --git a/src/repo.rs b/src/repo.rs index f0e75b0..3bd6292 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1,17 +1,39 @@ +use crate::common::DHTEntity; use async_stream::stream; -use futures_core::stream::Stream; use eyre::Result; -use veilid_core::CryptoKey; +use futures_core::stream::Stream; +use std::sync::Arc; +use veilid_core::{ + CryptoKey, CryptoSystemVLD0, CryptoTyped, DHTRecordDescriptor, RoutingContext, SharedSecret, +}; -pub struct Repo {} +#[derive(Clone)] +pub struct Repo { + pub id: CryptoKey, + pub dht_record: DHTRecordDescriptor, + pub encryption_key: SharedSecret, + pub secret_key: Option<CryptoTyped<CryptoKey>>, + pub routing_context: Arc<RoutingContext>, + pub crypto_system: CryptoSystemVLD0, +} impl Repo { - pub fn new() -> Self { - Self {} - } - - pub fn get_id(&self) -> CryptoKey { - unimplemented!("WIP") + pub fn new( + id: CryptoKey, + dht_record: DHTRecordDescriptor, + encryption_key: SharedSecret, + secret_key: Option<CryptoTyped<CryptoKey>>, + routing_context: Arc<RoutingContext>, + crypto_system: CryptoSystemVLD0, + ) -> Self { + Self { + id, + dht_record, + encryption_key, + secret_key, + routing_context, + crypto_system, + } } pub fn get_write_key(&self) -> Option<CryptoKey> { @@ -40,8 +62,28 @@ impl Repo { } } -impl Default for Repo { - fn default() -> Self { - Self::new() +impl DHTEntity for Repo { + fn get_id(&self) -> CryptoKey { + self.id.clone() + } + + fn get_encryption_key(&self) -> SharedSecret { + self.encryption_key.clone() + } + + fn get_routing_context(&self) -> Arc<RoutingContext> { + self.routing_context.clone() + } + + fn get_crypto_system(&self) -> CryptoSystemVLD0 { + self.crypto_system.clone() + } + + fn get_dht_record(&self) -> DHTRecordDescriptor { + self.dht_record.clone() + } + + fn get_secret_key(&self) -> Option<CryptoKey> { + self.secret_key.clone().map(|key| key.value) } }