From 240b8366245481410bb779f057b9fc4f15b6246d Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Thu, 29 Jun 2023 22:42:52 +0300 Subject: [PATCH] zeroize everything --- Cargo.lock | 4 + README.md | 2 +- ng-sdk-js/src/lib.rs | 6 +- ng-wallet/Cargo.toml | 3 +- ng-wallet/src/lib.rs | 102 ++++++++------ ng-wallet/src/types.rs | 168 +++++++++++++++++++++-- ngcli/src/main.rs | 14 +- ngd/Cargo.toml | 3 +- ngd/src/cli.rs | 2 +- ngd/src/main.rs | 78 +++++------ p2p-broker/src/server_ws.rs | 12 +- p2p-client-ws/src/remote_ws.rs | 11 +- p2p-net/Cargo.toml | 1 + p2p-net/src/broker.rs | 57 ++++---- p2p-net/src/connection.rs | 36 +++-- p2p-net/src/lib.rs | 4 +- p2p-net/src/site.rs | 66 +++++++++ p2p-net/src/types.rs | 238 ++++++++++++++++++++++++++++++--- p2p-net/src/utils.rs | 97 ++++---------- p2p-repo/Cargo.toml | 1 + p2p-repo/src/block.rs | 2 +- p2p-repo/src/branch.rs | 90 ++++++------- p2p-repo/src/commit.rs | 10 +- p2p-repo/src/lib.rs | 2 - p2p-repo/src/object.rs | 19 ++- p2p-repo/src/site.rs | 79 ----------- p2p-repo/src/types.rs | 112 +++++++--------- p2p-repo/src/utils.rs | 124 ++++++++++------- 28 files changed, 833 insertions(+), 510 deletions(-) create mode 100644 p2p-net/src/site.rs delete mode 100644 p2p-repo/src/site.rs diff --git a/Cargo.lock b/Cargo.lock index 2b925e6..cb9e557 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2784,6 +2784,7 @@ dependencies = [ "serde_bare", "serde_bytes", "web-time", + "zeroize", ] [[package]] @@ -2823,6 +2824,7 @@ dependencies = [ "serde_bare", "serde_bytes", "serde_json", + "zeroize", ] [[package]] @@ -3108,6 +3110,7 @@ dependencies = [ "async-broadcast", "async-std", "async-trait", + "base64-url", "blake3", "default-net", "ed25519-dalek", @@ -3150,6 +3153,7 @@ dependencies = [ "slice_as_array", "wasm-bindgen", "web-time", + "zeroize", ] [[package]] diff --git a/README.md b/README.md index 25ac6ba..0aba71f 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ wasm-pack test --chrome --headless Test Rust websocket ``` -cargo test --package p2p-client-ws --lib -- --nocapture +cargo test --package p2p-client-ws --lib -- remote_ws::test::test_ws --nocapture ``` ### Build release binaries diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 0ad84bb..9f1f0c1 100644 --- a/ng-sdk-js/src/lib.rs +++ b/ng-sdk-js/src/lib.rs @@ -22,7 +22,7 @@ use p2p_client_ws::remote_ws_wasm::ConnectionWebSocket; use p2p_net::broker::*; use p2p_net::connection::{ClientConfig, StartConfig}; use p2p_net::types::{DirectPeerId, IP}; -use p2p_net::utils::{gen_ed_keys, spawn_and_log_error, Receiver, ResultSend, Sender}; +use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender}; use p2p_net::WS_PORT; use p2p_repo::log::*; use p2p_repo::types::*; @@ -57,7 +57,7 @@ pub fn wallet_open_wallet_with_pazzle( ) -> Result { let wallet = serde_wasm_bindgen::from_value::(js_wallet) .map_err(|_| "Deserialization error of wallet")?; - let pin = serde_wasm_bindgen::from_value::<[u8; 4]>(js_pin) + let mut pin = serde_wasm_bindgen::from_value::<[u8; 4]>(js_pin) .map_err(|_| "Deserialization error of pin")?; let res = open_wallet_with_pazzle(wallet, pazzle, pin); match res { @@ -230,7 +230,7 @@ pub async fn start() { //let keys = p2p_net::utils::gen_dh_keys(); //let pub_key = PubKey::Ed25519PubKey(keys.1); - let keys = gen_ed_keys(); + let keys = generate_keypair(); let x_from_ed = keys.1.to_dh_from_ed(); log_info!("Pub from X {}", x_from_ed); diff --git a/ng-wallet/Cargo.toml b/ng-wallet/Cargo.toml index 532b563..158da2f 100644 --- a/ng-wallet/Cargo.toml +++ b/ng-wallet/Cargo.toml @@ -26,4 +26,5 @@ aes-gcm-siv = {version = "0.11.1", features = ["aes","heapless","getrandom","std base64-url = "2.0.0" async-std = { version = "1.12.0", features = ["attributes","unstable"] } web-time = "0.2.0" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +zeroize = { version = "1.6.0", features = ["zeroize_derive"] } \ No newline at end of file diff --git a/ng-wallet/src/lib.rs b/ng-wallet/src/lib.rs index 1fb6962..e77a040 100644 --- a/ng-wallet/src/lib.rs +++ b/ng-wallet/src/lib.rs @@ -19,7 +19,7 @@ pub mod bip39; pub mod emojis; -use std::io::Cursor; +use std::{collections::HashMap, io::Cursor}; use crate::bip39::bip39_wordlist; use crate::types::*; @@ -29,29 +29,32 @@ use aes_gcm_siv::{ }; use argon2::{Algorithm, Argon2, AssociatedData, ParamsBuilder, Version}; use chacha20poly1305::XChaCha20Poly1305; +use zeroize::{Zeroize, ZeroizeOnDrop}; use image::{imageops::FilterType, io::Reader as ImageReader, ImageOutputFormat}; use safe_transmute::transmute_to_bytes; +use p2p_net::types::{SiteType, SiteV0}; use p2p_repo::log::*; -use p2p_repo::types::{PubKey, Site, SiteType, Timestamp}; +use p2p_repo::types::{PubKey, Timestamp}; use p2p_repo::utils::{generate_keypair, now_timestamp, sign, verify}; use rand::prelude::*; use serde_bare::{from_slice, to_vec}; +use web_time::Instant; pub fn enc_master_key( - master_key: [u8; 32], - key: [u8; 32], + master_key: &[u8; 32], + key: &[u8; 32], nonce: u8, wallet_id: WalletId, ) -> Result<[u8; 48], NgWalletError> { - let cipher = Aes256GcmSiv::new(&key.into()); + let cipher = Aes256GcmSiv::new(key.into()); let mut nonce_buffer = [0u8; 12]; nonce_buffer[0] = nonce; let nonce = Nonce::from_slice(&nonce_buffer); let mut buffer: HeaplessVec = HeaplessVec::new(); // Note: buffer needs 16-bytes overhead for auth tag - buffer.extend_from_slice(&master_key); + buffer.extend_from_slice(master_key); // Encrypt `buffer` in-place, replacing the plaintext contents with ciphertext cipher @@ -65,11 +68,11 @@ pub fn enc_master_key( pub fn dec_master_key( ciphertext: [u8; 48], - key: [u8; 32], + key: &[u8; 32], nonce: u8, wallet_id: WalletId, ) -> Result<[u8; 32], NgWalletError> { - let cipher = Aes256GcmSiv::new(&key.into()); + let cipher = Aes256GcmSiv::new(key.into()); let mut nonce_buffer = [0u8; 12]; nonce_buffer[0] = nonce; let nonce = Nonce::from_slice(&nonce_buffer); @@ -97,7 +100,7 @@ fn gen_associated_data(timestamp: Timestamp, wallet_id: WalletId) -> Vec { pub fn enc_encrypted_block( block: &EncryptedWalletV0, - master_key: [u8; 32], + master_key: &[u8; 32], peer_id: PubKey, nonce: u64, timestamp: Timestamp, @@ -107,7 +110,7 @@ pub fn enc_encrypted_block( let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce); - let cipher = XChaCha20Poly1305::new(&master_key.into()); + let cipher = XChaCha20Poly1305::new(master_key.into()); let mut buffer: Vec = Vec::with_capacity(ser_encrypted_block.len() + 16); // Note: buffer needs 16-bytes overhead for auth tag buffer.extend_from_slice(&ser_encrypted_block); @@ -129,7 +132,7 @@ pub fn enc_encrypted_block( pub fn dec_encrypted_block( mut ciphertext: Vec, - master_key: [u8; 32], + master_key: &mut [u8; 32], peer_id: PubKey, nonce: u64, timestamp: Timestamp, @@ -137,7 +140,7 @@ pub fn dec_encrypted_block( ) -> Result { let nonce_buffer: [u8; 24] = gen_nonce(peer_id, nonce); - let cipher = XChaCha20Poly1305::new(&master_key.into()); + let cipher = XChaCha20Poly1305::new(master_key.as_ref().into()); // Decrypt `ciphertext` in-place, replacing its ciphertext context with the original plaintext cipher @@ -154,10 +157,11 @@ pub fn dec_encrypted_block( let decrypted_block = from_slice::(&ciphertext).map_err(|e| NgWalletError::DecryptionError)?; + master_key.zeroize(); Ok(decrypted_block) } -pub fn derive_key_from_pass(pass: Vec, salt: [u8; 16], wallet_id: WalletId) -> [u8; 32] { +pub fn derive_key_from_pass(mut pass: Vec, salt: [u8; 16], wallet_id: WalletId) -> [u8; 32] { let params = ParamsBuilder::new() .m_cost(50 * 1024) .t_cost(2) @@ -169,14 +173,14 @@ pub fn derive_key_from_pass(pass: Vec, salt: [u8; 16], wallet_id: WalletId) let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params); let mut out = [0u8; 32]; argon.hash_password_into(&pass, &salt, &mut out).unwrap(); + pass.zeroize(); out } -use web_time::Instant; pub fn open_wallet_with_pazzle( wallet: Wallet, pazzle: Vec, - pin: [u8; 4], + mut pin: [u8; 4], ) -> Result { // each digit shouldnt be greater than 9 if pin[0] > 9 || pin[1] > 9 || pin[2] > 9 || pin[3] > 9 { @@ -190,18 +194,21 @@ pub fn open_wallet_with_pazzle( match wallet { Wallet::V0(v0) => { - let pazzle_key = derive_key_from_pass( + let mut pazzle_key = derive_key_from_pass( [pazzle, pin.to_vec()].concat(), v0.content.salt_pazzle, v0.id, ); + //pazzle.zeroize(); + pin.zeroize(); - let master_key = dec_master_key( + let mut master_key = dec_master_key( v0.content.enc_master_key_pazzle, - pazzle_key, + &pazzle_key, v0.content.master_nonce, v0.id, )?; + pazzle_key.zeroize(); log_info!( "opening of wallet with pazzle took: {} ms", @@ -210,7 +217,7 @@ pub fn open_wallet_with_pazzle( Ok(EncryptedWallet::V0(dec_encrypted_block( v0.content.encrypted, - master_key, + &mut master_key, v0.content.peer_id, v0.content.nonce, v0.content.timestamp, @@ -222,30 +229,33 @@ pub fn open_wallet_with_pazzle( pub fn open_wallet_with_mnemonic( wallet: Wallet, - mnemonic: [u16; 12], - pin: [u8; 4], + mut mnemonic: [u16; 12], + mut pin: [u8; 4], ) -> Result { verify(&wallet.content_as_bytes(), wallet.sig(), wallet.id()) .map_err(|e| NgWalletError::InvalidSignature)?; match wallet { Wallet::V0(v0) => { - let mnemonic_key = derive_key_from_pass( + let mut mnemonic_key = derive_key_from_pass( [transmute_to_bytes(&mnemonic), &pin].concat(), v0.content.salt_mnemonic, v0.id, ); + mnemonic.zeroize(); + pin.zeroize(); - let master_key = dec_master_key( + let mut master_key = dec_master_key( v0.content.enc_master_key_mnemonic, - mnemonic_key, + &mnemonic_key, v0.content.master_nonce, v0.id, )?; + mnemonic_key.zeroize(); Ok(EncryptedWallet::V0(dec_encrypted_block( v0.content.encrypted, - master_key, + &mut master_key, v0.content.peer_id, v0.content.nonce, v0.content.timestamp, @@ -335,10 +345,10 @@ pub async fn create_wallet_v0( let creating_pazzle = Instant::now(); // pazzle_length can only be 9, 12, or 15 - if (params.pazzle_length != 9 + if params.pazzle_length != 9 && params.pazzle_length != 12 && params.pazzle_length != 15 - && params.pazzle_length != 0) + && params.pazzle_length != 0 { return Err(NgWalletError::InvalidPazzleLength); } @@ -395,7 +405,7 @@ pub async fn create_wallet_v0( } // check validity of image - let decoded_img = ImageReader::new(Cursor::new(params.security_img)) + let decoded_img = ImageReader::new(Cursor::new(¶ms.security_img)) .with_guessed_format() .map_err(|e| NgWalletError::InvalidSecurityImage)? .decode() @@ -419,9 +429,9 @@ pub async fn create_wallet_v0( // creating the wallet keys - let (wallet_key, wallet_id) = generate_keypair(); + let (wallet_privkey, wallet_id) = generate_keypair(); - let site = Site::create(SiteType::Individual).map_err(|e| NgWalletError::InternalError)?; + let site = SiteV0::create(SiteType::Individual).map_err(|e| NgWalletError::InternalError)?; // let mut pazzle_random = vec![0u8; pazzle_length.into()]; // getrandom::getrandom(&mut pazzle_random).map_err(|e| NgWalletError::InternalError)?; @@ -450,10 +460,15 @@ pub async fn create_wallet_v0( //.clone(), let encrypted_block = EncryptedWalletV0 { + wallet_privkey: wallet_privkey.clone(), pazzle: pazzle.clone(), mnemonic, pin: params.pin, sites: vec![site], + brokers: vec![], //TODO add the broker here + clients: vec![], // TODO add a client here + overlay_core_overrides: HashMap::new(), + third_parties: HashMap::new(), }; let mut master_key = [0u8; 32]; @@ -464,13 +479,14 @@ pub async fn create_wallet_v0( if params.pazzle_length > 0 { getrandom::getrandom(&mut salt_pazzle).map_err(|e| NgWalletError::InternalError)?; - let pazzle_key = derive_key_from_pass( + let mut pazzle_key = derive_key_from_pass( [pazzle.clone(), params.pin.to_vec()].concat(), salt_pazzle, wallet_id, ); - enc_master_key_pazzle = enc_master_key(master_key, pazzle_key, 0, wallet_id)?; + enc_master_key_pazzle = enc_master_key(&master_key, &pazzle_key, 0, wallet_id)?; + pazzle_key.zeroize(); } let mut salt_mnemonic = [0u8; 16]; @@ -479,24 +495,26 @@ pub async fn create_wallet_v0( //log_debug!("salt_pazzle {:?}", salt_pazzle); //log_debug!("salt_mnemonic {:?}", salt_mnemonic); - let mnemonic_key = derive_key_from_pass( + let mut mnemonic_key = derive_key_from_pass( [transmute_to_bytes(&mnemonic), ¶ms.pin].concat(), salt_mnemonic, wallet_id, ); - let enc_master_key_mnemonic = enc_master_key(master_key, mnemonic_key, 0, wallet_id)?; + let enc_master_key_mnemonic = enc_master_key(&master_key, &mnemonic_key, 0, wallet_id)?; + mnemonic_key.zeroize(); let timestamp = now_timestamp(); let encrypted = enc_encrypted_block( &encrypted_block, - master_key, + &master_key, params.peer_id, params.nonce, timestamp, wallet_id, )?; + master_key.zeroize(); let wallet_content = WalletContentV0 { security_img: cursor.into_inner(), @@ -515,7 +533,7 @@ pub async fn create_wallet_v0( let ser_wallet = serde_bare::to_vec(&wallet_content).unwrap(); - let sig = sign(wallet_key, wallet_id, &ser_wallet).unwrap(); + let sig = sign(&wallet_privkey, &wallet_id, &ser_wallet).unwrap(); let wallet_v0 = WalletV0 { /// ID @@ -544,7 +562,7 @@ pub async fn create_wallet_v0( creating_pazzle.elapsed().as_millis() ); let wallet = Wallet::V0(wallet_v0); - let wallet_file = match (params.result_with_wallet_file) { + let wallet_file = match params.result_with_wallet_file { false => vec![], // TODO: save locally true => to_vec(&NgFile::V0(NgFileV0::Wallet(wallet.clone()))).unwrap(), }; @@ -552,13 +570,13 @@ pub async fn create_wallet_v0( wallet: wallet, wallet_file, pazzle, - mnemonic, + mnemonic: mnemonic.clone(), wallet_name: base64_url::encode(&wallet_id.slice()), }) } #[cfg(test)] -mod tests { +mod test { use super::*; use p2p_repo::utils::generate_keypair; use std::fs::File; @@ -634,7 +652,7 @@ mod tests { log_info!("mnemonic {:?}", display_mnemonic(&res.mnemonic)); log_info!("pin {:?}", pin); - if let Wallet::V0(v0) = res.wallet { + if let Wallet::V0(v0) = &res.wallet { log_info!("security text: {:?}", v0.content.security_txt); let mut file = @@ -654,7 +672,7 @@ mod tests { let opening_mnemonic = Instant::now(); - let w = open_wallet_with_mnemonic(Wallet::V0(v0.clone()), res.mnemonic, pin) + let w = open_wallet_with_mnemonic(Wallet::V0(v0.clone()), res.mnemonic, pin.clone()) .expect("open with mnemonic"); //log_debug!("encrypted part {:?}", w); @@ -665,7 +683,7 @@ mod tests { if v0.content.pazzle_length > 0 { let opening_pazzle = Instant::now(); - let w = open_wallet_with_pazzle(Wallet::V0(v0.clone()), res.pazzle, pin) + let w = open_wallet_with_pazzle(Wallet::V0(v0.clone()), res.pazzle.clone(), pin) .expect("open with pazzle"); log_info!( "opening of wallet with pazzle took: {} ms", diff --git a/ng-wallet/src/types.rs b/ng-wallet/src/types.rs index 4d365a6..16ffaba 100644 --- a/ng-wallet/src/types.rs +++ b/ng-wallet/src/types.rs @@ -7,12 +7,14 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use std::fmt; +use std::{collections::HashMap, fmt}; +use web_time::SystemTime; +use zeroize::{Zeroize, ZeroizeOnDrop}; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; -use p2p_net::types::{BootstrapContentV0, BrokerServerV0}; +use p2p_net::types::*; use p2p_repo::types::*; /// WalletId is a PubKey @@ -58,9 +60,22 @@ impl Bootstrap { } } -/// EncryptedWallet block Version 0 +/// Device info Version 0 #[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ClientV0 { + pub priv_key: PrivKey, + + pub storage_master_key: SymKey, + + /// list of users that should be opened automatically (at launch, after wallet opened) on this device + pub auto_open: Vec, +} + +/// EncryptedWallet block Version 0 +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] pub struct EncryptedWalletV0 { + pub wallet_privkey: PrivKey, + #[serde(with = "serde_bytes")] pub pazzle: Vec, @@ -69,7 +84,24 @@ pub struct EncryptedWalletV0 { pub pin: [u8; 4], // first in the list is the main Site (Personal) - pub sites: Vec, + #[zeroize(skip)] + pub sites: Vec, + + // list of brokers and their connection details + #[zeroize(skip)] + pub brokers: Vec, + + // list of all devices of the user + #[zeroize(skip)] + pub clients: Vec, + + #[zeroize(skip)] + pub overlay_core_overrides: HashMap>, + + /// third parties data saved in the wallet. the string (key) in the hashmap should be unique among vendors. + /// the format of the byte array (value) is up to the vendor, to serde as needed. + #[zeroize(skip)] + pub third_parties: HashMap>, } /// EncryptedWallet block @@ -107,15 +139,125 @@ pub struct WalletContentV0 { pub timestamp: Timestamp, - // the peerId that update this version of the Wallet. this value is truncated by half and concatenated with the nonce + // the peerId that updated this version of the Wallet. this value is truncated by half and concatenated with the nonce pub peer_id: PubKey, pub nonce: u64, - // EncryptedWallet content encrypted with XChaCha20Poly1305, AD = timestamp and walletID + // WalletLog0 content encrypted with XChaCha20Poly1305, AD = timestamp and walletID #[serde(with = "serde_bytes")] pub encrypted: Vec, } +/// Wallet Log +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WalletLog0 { + pub log: Vec<(SystemTime, WalletOperationV0)>, +} + +/// WalletOperation +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum WalletOperationV0 { + CreateWalletV0(WalletOpCreateV0), + AddSiteV0(SiteV0), + RemoveSiteV0(Identity), + AddBrokerV0(BrokerInfoV0), + RemoveBrokerV0(BrokerInfoV0), + AddClientV0(ClientV0), + AddOverlayCoreOverrideV0((OverlayId, Vec)), + RemoveOverlayCoreOverrideV0(OverlayId), + AddSiteCoreV0((Identity, PubKey)), + RemoveSiteCoreV0((Identity, PubKey)), + AddSiteBootstrapV0((Identity, PubKey)), + RemoveSiteBootstrapV0((Identity, PubKey)), + AddThirdPartyDataV0((String, Vec)), + RemoveThirdPartyDataV0(String), + SetSiteRBDRefV0((Identity, ObjectRef)), + SetSiteRepoSecretV0((Identity, SymKey)), +} + +/// WalletOp Create V0 +/// first operation in the log +/// also serialized and encoded in Rescue QRcode +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] +pub struct WalletOpCreateV0 { + pub wallet_privkey: PrivKey, + + #[serde(with = "serde_bytes")] + pub pazzle: Vec, + + pub mnemonic: [u16; 12], + + pub pin: [u8; 4], + + #[zeroize(skip)] + pub personal_site: SiteV0, + + // list of brokers and their connection details + #[zeroize(skip)] + pub brokers: Vec, + + #[zeroize(skip)] + pub client: ClientV0, +} + +/// Reduced Wallet content Version 0, for Login QRcode +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ReducedWalletContentV0 { + /// can be 9, 12 or 15 (or 0, in this case salt_pazzle and enc_master_key_pazzle are filled with zeros and should not be used) + pub pazzle_length: u8, + + pub salt_pazzle: [u8; 16], + + pub salt_mnemonic: [u8; 16], + + // encrypted master keys. first is encrypted with pazzle, second is encrypted with mnemonic + // AD = wallet_id + #[serde(with = "BigArray")] + pub enc_master_key_pazzle: [u8; 48], + #[serde(with = "BigArray")] + pub enc_master_key_mnemonic: [u8; 48], + + // nonce for the encryption of masterkey + // incremented only if the masterkey changes + // be very careful with incrementing this, as a conflict would result in total loss of crypto guarantees. + pub master_nonce: u8, + + pub timestamp: Timestamp, + + // the peerId that updated this version of the Wallet. this value is truncated by half and concatenated with the nonce + pub peer_id: PubKey, + pub nonce: u64, + + // ReducedEncryptedWalletV0 content encrypted with XChaCha20Poly1305, AD = timestamp and walletID + #[serde(with = "serde_bytes")] + pub encrypted: Vec, +} + +/// Broker Info Version 0 +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum BrokerInfoV0 { + ServerV0(BrokerServerV0), + CoreV0(BrokerCoreV0), +} + +/// ReducedEncryptedWallet block Version 0 +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ReducedEncryptedWalletV0 { + // main Site (Personal) + pub personal_site: ReducedSiteV0, + + // list of brokers and their connection details + pub brokers: Vec, + + pub client: ClientV0, +} + +/// ReducedEncryptedWallet block +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum ReducedEncryptedWallet { + V0(ReducedEncryptedWalletV0), +} + /// Wallet Version 0 #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WalletV0 { @@ -193,18 +335,25 @@ impl AddWallet { } /// Create Wallet Version 0, used by the API create_wallet_v0 -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] pub struct CreateWalletV0 { + #[zeroize(skip)] #[serde(with = "serde_bytes")] pub security_img: Vec, pub security_txt: String, pub pin: [u8; 4], pub pazzle_length: u8, + #[zeroize(skip)] pub send_bootstrap: Option, + #[zeroize(skip)] pub send_wallet: bool, + #[zeroize(skip)] pub result_with_wallet_file: bool, + #[zeroize(skip)] pub local_save: bool, + #[zeroize(skip)] pub peer_id: PubKey, + #[zeroize(skip)] pub nonce: u64, } @@ -234,13 +383,16 @@ impl CreateWalletV0 { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize)] pub struct CreateWalletResultV0 { + #[zeroize(skip)] pub wallet: Wallet, #[serde(with = "serde_bytes")] + #[zeroize(skip)] pub wallet_file: Vec, pub pazzle: Vec, pub mnemonic: [u16; 12], + #[zeroize(skip)] pub wallet_name: String, } diff --git a/ngcli/src/main.rs b/ngcli/src/main.rs index 1c64649..159eabe 100644 --- a/ngcli/src/main.rs +++ b/ngcli/src/main.rs @@ -599,7 +599,7 @@ mod test { use async_std::task; use p2p_broker::server_ws::*; - use p2p_net::utils::{gen_dh_keys, Sensitive, U8Array}; + use p2p_net::utils::gen_dh_keys; use p2p_net::WS_PORT; use p2p_repo::log::*; use p2p_repo::types::PubKey; @@ -609,17 +609,11 @@ mod test { let keys = gen_dh_keys(); // log_debug!("Public key of node: {:?}", keys.1); // log_debug!("Private key of node: {:?}", keys.0.as_slice()); - let pubkey = PubKey::Ed25519PubKey(keys.1); - log_debug!("Public key of node: {:?}", pubkey); - log_debug!("Private key of node: {:?}", keys.0.as_slice()); + log_debug!("Public key of node: {}", keys.1); + log_debug!("Private key of node: {}", keys.0); - let thr = task::spawn(run_server_accept_one( - "127.0.0.1", - WS_PORT, - keys.0.as_slice(), - pubkey, - )); + let thr = task::spawn(run_server_accept_one("127.0.0.1", WS_PORT, keys.0, pubkey)); // time for the server to start std::thread::sleep(std::time::Duration::from_secs(2)); diff --git a/ngd/Cargo.toml b/ngd/Cargo.toml index 4bd1c15..8c3c03f 100644 --- a/ngd/Cargo.toml +++ b/ngd/Cargo.toml @@ -22,4 +22,5 @@ base64-url = "2.0.0" serde_json = "1.0" regex = "1.8.4" lazy_static = "1.4.0" -addr = "0.15.6" \ No newline at end of file +addr = "0.15.6" +zeroize = { version = "1.6.0" } \ No newline at end of file diff --git a/ngd/src/cli.rs b/ngd/src/cli.rs index 5950d1d..088e906 100644 --- a/ngd/src/cli.rs +++ b/ngd/src/cli.rs @@ -72,7 +72,7 @@ pub(crate) struct Cli { pub public: Option, /// When --public is used, this option will disallow clients to connect to the public interface too. Otherwise, by default, they can. Should be used in combination with a --domain option - #[arg(long, requires("public"), conflicts_with("private"))] + #[arg(long, conflicts_with("private"))] pub public_without_clients: bool, /// When --public is used with a public IPV6, this option will bind the IPV6 to the private interface. This is how DMZ work for IpV6 diff --git a/ngd/src/main.rs b/ngd/src/main.rs index 5bdc219..14fcb13 100644 --- a/ngd/src/main.rs +++ b/ngd/src/main.rs @@ -24,8 +24,7 @@ use p2p_net::utils::is_public_ip; use p2p_net::utils::is_public_ipv4; use p2p_net::utils::is_public_ipv6; use p2p_net::utils::{ - gen_dh_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private, keypair_from_ed, - keys_from_bytes, Dual25519Keys, Sensitive, U8Array, + gen_dh_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private, }; use p2p_net::{WS_PORT, WS_PORT_REVERSE_PROXY}; use p2p_repo::log::*; @@ -38,6 +37,7 @@ use std::fs::{read_to_string, write}; use std::io::ErrorKind; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; +use zeroize::Zeroize; use addr::parser::DnsName; use addr::psl::List; @@ -287,11 +287,12 @@ fn parse_domain_and_port( fn prepare_accept_forward_for_domain(domain: String, args: &Cli) -> Result { if args.domain_peer.is_some() { - let key_ser = base64_url::decode(args.domain_peer.as_ref().unwrap()).map_err(|_| ())?; - let key = serde_bare::from_slice::(&key_ser); + let key = decode_key(args.domain_peer.as_ref().unwrap().as_str())?; + args.domain_peer.as_mut().unwrap().zeroize(); + Ok(AcceptForwardForV0::PublicDomainPeer(( domain, - key.unwrap(), + PrivKey::Ed25519PrivKey(key), "".to_string(), ))) } else { @@ -348,16 +349,10 @@ async fn main_inner() -> Result<(), ()> { let key_from_file: Option<[u8; 32]>; let res = |key_path| -> Result<[u8; 32], &str> { let file = read_to_string(key_path).map_err(|_| "")?; - decode_key( - &file - .lines() - .nth(0) - .ok_or("empty file")? - .to_string() - .trim() - .to_string(), - ) - .map_err(|_| "invalid file") + let first_line = file.lines().nth(0).ok_or("empty file")?; + let res = decode_key(first_line.trim()).map_err(|_| "invalid file"); + first_line.zeroize(); + res }(&key_path); if res.is_err() && res.unwrap_err().len() > 0 { @@ -373,24 +368,25 @@ async fn main_inner() -> Result<(), ()> { Some(key_string) => { if key_from_file.is_some() { log_err!("provided --key option will not be used as a key file is already present"); - gen_broker_keys(Some(key_from_file.unwrap())) + gen_broker_keys(key_from_file) } else { let res = decode_key(key_string.as_str()) .map_err(|_| log_err!("provided key is invalid. cannot start"))?; - if args.save_key { let master_key = base64_url::encode(&res); write(key_path.clone(), master_key).map_err(|e| { log_err!("cannot save key to file. {}.cannot start", e.to_string()) })?; + master_key.zeroize(); log_info!("The key has been saved to {}", key_path.to_str().unwrap()); } gen_broker_keys(Some(res)) } + args.key.as_mut().unwrap().zeroize(); } None => { if key_from_file.is_some() { - gen_broker_keys(Some(key_from_file.unwrap())) + gen_broker_keys(key_from_file) } else { let res = gen_broker_keys(None); let master_key = base64_url::encode(&res[0]); @@ -405,11 +401,17 @@ async fn main_inner() -> Result<(), ()> { log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key"); log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable"); } + master_key.zeroize(); res } } }; + key_from_file.and_then(|mut key| { + key.zeroize(); + None + }); + // DEALING WITH CONFIG // reading config from file, if any @@ -701,7 +703,9 @@ async fn main_inner() -> Result<(), ()> { } Some(inter) => { overlays_config.core = BrokerOverlayPermission::AllRegisteredUser; - overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + if !args.public_without_clients { + overlays_config.server = BrokerOverlayPermission::AllRegisteredUser; + } if listeners.last().is_some() && listeners.last().unwrap().interface_name == inter.name @@ -719,6 +723,7 @@ async fn main_inner() -> Result<(), ()> { let mut listener = ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1); listener.accept_direct = false; + listener.refuse_clients = args.public_without_clients; listener.serve_app = false; listener.accept_forward_for = AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string())); @@ -939,37 +944,16 @@ async fn main_inner() -> Result<(), ()> { } } - // let keys = gen_keys(); - // let pub_key = PubKey::Ed25519PubKey(keys.1); - // let (ed_priv_key, ed_pub_key) = generate_keypair(); - - //let duals = Dual25519Keys::generate(); - // let eds = keypair_from_ed(duals.ed25519_priv, duals.ed25519_pub); - // let test_vector: Vec = vec![71, 51, 206, 126, 9, 84, 132]; - // let sig = sign(eds.0, eds.1, &test_vector).unwrap(); - // verify(&test_vector, sig, eds.1).unwrap(); - - // let privkey = duals.x25519_priv; - // let pubkey = PubKey::Ed25519PubKey(duals.x25519_public); - - let (privkey, pubkey) = keys_from_bytes(keys[1]); - - //let duals = Dual25519Keys::from_sensitive(privkey); - //let eds = keypair_from_ed(duals.ed25519_priv, duals.ed25519_pub); - //let xpriv = duals.x25519_priv; - //let xpub = PubKey::X25519PubKey(duals.x25519_public); - - // let priv_key: PrivKey = privkey - // .as_slice() - // .try_into() - // .map_err(|_| log_err!("Private key of peer has invalid array"))?; + let (privkey, pubkey) = ed_keypair_from_priv_bytes(keys[1]); + keys[1].zeroize(); + keys[0].zeroize(); log_info!("PeerId of node: {}", pubkey); - //let privkey_: PrivKey = xpriv.to_owned().try_into().unwrap(); - //debug_println!("Private key of peer: {}", privkey_.to_string()); - //let x_from_ed = eds.1.to_dh_from_ed(); - //log_info!("Pub from X {}", x_from_ed); + //debug_println!("Private key of peer: {}", privkey.to_string()); + + //let x_from_ed = pubkey.to_dh_from_ed(); + //log_info!("du Pubkey from ed: {}", x_from_ed); match config.unwrap() { DaemonConfig::V0(v0) => { diff --git a/p2p-broker/src/server_ws.rs b/p2p-broker/src/server_ws.rs index 57005b5..3aba018 100644 --- a/p2p-broker/src/server_ws.rs +++ b/p2p-broker/src/server_ws.rs @@ -514,7 +514,7 @@ impl Callback for SecurityCallback { } } -pub async fn accept(tcp: TcpStream, peer_priv_key: Sensitive<[u8; 32]>) { +pub async fn accept(tcp: TcpStream, peer_priv_key: PrivKey) { let remote_addr = tcp.peer_addr().unwrap(); let remote_bind_address: BindAddress = (&remote_addr).into(); @@ -554,7 +554,7 @@ pub async fn accept(tcp: TcpStream, peer_priv_key: Sensitive<[u8; 32]>) { pub async fn run_server_accept_one( addr: &str, port: u16, - peer_priv_key: Sensitive<[u8; 32]>, + peer_priv_key: PrivKey, peer_pub_key: PubKey, ) -> std::io::Result<()> { let addrs = format!("{}:{}", addr, port); @@ -580,7 +580,7 @@ pub async fn run_server_accept_one( } pub async fn run_server_v0( - peer_priv_key: Sensitive<[u8; 32]>, + peer_priv_key: PrivKey, peer_id: PubKey, wallet_master_key: Sensitive<[u8; 32]>, config: DaemonConfigV0, @@ -792,11 +792,7 @@ pub async fn run_server_v0( // TODO : select on the shutdown stream too while let Some(tcp) = incoming.next().await { // TODO select peer_priv_ket according to config. if --domain-peer present and the connection is for that listener (PublicDomainPeer) then use the peer configured there - accept( - tcp.unwrap(), - Sensitive::<[u8; 32]>::from_slice(peer_priv_key.deref()), - ) - .await; + accept(tcp.unwrap(), peer_priv_key.clone()).await; } Ok(()) diff --git a/p2p-client-ws/src/remote_ws.rs b/p2p-client-ws/src/remote_ws.rs index 95c8926..d40943f 100644 --- a/p2p-client-ws/src/remote_ws.rs +++ b/p2p-client-ws/src/remote_ws.rs @@ -27,7 +27,7 @@ use futures::{FutureExt, SinkExt}; use async_std::task; use p2p_net::errors::*; use p2p_net::types::*; -use p2p_net::utils::{gen_ed_keys, spawn_and_log_error, Receiver, ResultSend, Sender, Sensitive}; +use p2p_net::utils::{spawn_and_log_error, Receiver, ResultSend, Sender, Sensitive}; use p2p_net::{connection::*, WS_PORT}; use p2p_repo::log::*; use p2p_repo::types::*; @@ -130,7 +130,7 @@ impl IAccept for ConnectionWebSocket { &self, remote_bind_address: BindAddress, local_bind_address: BindAddress, - peer_privk: Sensitive<[u8; 32]>, + peer_privk: PrivKey, socket: Self::Socket, ) -> Result { let mut cnx = ConnectionBase::new(ConnectionDir::Server, TransportProtocol::WS); @@ -311,15 +311,10 @@ mod test { #[async_std::test] pub async fn test_ws() -> Result<(), NgError> { - // let mut random_buf = [0u8; 32]; - // getrandom::getrandom(&mut random_buf).unwrap(); - let server_key: PubKey = "X0nh-gOTGKSx0yL0LYJviOWRNacyqIzjQW_LKdK6opU".try_into()?; log_debug!("server_key:{}", server_key); - //let keys = p2p_net::utils::gen_dh_keys(); - //let pub_key = PubKey::Ed25519PubKey(keys.1); - let keys = gen_ed_keys(); + let keys = generate_keypair(); let x_from_ed = keys.1.to_dh_from_ed(); log_info!("Pub from X {}", x_from_ed); diff --git a/p2p-net/Cargo.toml b/p2p-net/Cargo.toml index e6960f4..5b9bb9d 100644 --- a/p2p-net/Cargo.toml +++ b/p2p-net/Cargo.toml @@ -27,6 +27,7 @@ ed25519-dalek = "1.0.1" either = "1.8.1" reqwest = { version = "0.11.18", features = ["json"] } url = "2.4.0" +base64-url = "2.0.0" [target.'cfg(target_arch = "wasm32")'.dependencies.getrandom] version = "0.2.7" diff --git a/p2p-net/src/broker.rs b/p2p-net/src/broker.rs index b9be363..2170fcb 100644 --- a/p2p-net/src/broker.rs +++ b/p2p-net/src/broker.rs @@ -63,7 +63,8 @@ pub static BROKER: Lazy>> = Lazy::new(|| Arc::new(RwLock::new pub struct Broker { direct_connections: HashMap, - peers: HashMap, + /// tuple of optional userId and peer key in montgomery form. userId is always None on the server side. + peers: HashMap<(Option, X25519PubKey), BrokerPeerInfo>, /// (local,remote) -> ConnectionBase anonymous_connections: HashMap<(BindAddress, BindAddress), ConnectionBase>, #[cfg(not(target_arch = "wasm32"))] @@ -145,9 +146,10 @@ impl Broker { } } Authorization::ExtMessage => Err(ProtocolError::AccessDenied), - Authorization::Client => Err(ProtocolError::AccessDenied), + Authorization::Client(_) => Err(ProtocolError::AccessDenied), Authorization::Core => Err(ProtocolError::AccessDenied), - Authorization::Admin => Err(ProtocolError::AccessDenied), + Authorization::Admin(_) => Err(ProtocolError::AccessDenied), + Authorization::OverlayJoin(_) => Err(ProtocolError::AccessDenied), } } @@ -215,7 +217,7 @@ impl Broker { 112, 95, 150, 144, 137, 9, 57, 106, 5, 39, 202, 146, 94, ]), }; - let refs = vec![obj_ref]; + let refs = vec![obj_ref.clone()]; let metadata = vec![5u8; 55]; let expiry = None; @@ -225,12 +227,12 @@ impl Broker { member_privkey, member_pubkey, 1, - obj_ref, + obj_ref.clone(), vec![], vec![], refs, metadata, - obj_ref, + obj_ref.clone(), expiry, ) .unwrap(); @@ -247,8 +249,8 @@ impl Broker { (rx, tx.clone()) } - pub fn reconnecting(&mut self, peer_id: &DirectPeerId) { - let peerinfo = self.peers.get_mut(&peer_id.to_dh_slice()); + pub fn reconnecting(&mut self, peer_id: &DirectPeerId, user: Option) { + let peerinfo = self.peers.get_mut(&(user, peer_id.to_dh_slice())); match peerinfo { Some(info) => match &info.connected { PeerConnection::NONE => {} @@ -263,8 +265,8 @@ impl Broker { None => {} } } - pub fn remove_peer_id(&mut self, peer_id: &DirectPeerId) { - let removed = self.peers.remove(&peer_id.to_dh_slice()); + pub fn remove_peer_id(&mut self, peer_id: &DirectPeerId, user: Option) { + let removed = self.peers.remove(&(user, peer_id.to_dh_slice())); match removed { Some(info) => match info.connected { PeerConnection::NONE => {} @@ -366,7 +368,11 @@ impl Broker { anonymous = Vec::from_iter(broker.anonymous_connections.keys().cloned()); } for peer_id in peer_ids { - BROKER.write().await.close_peer_connection_x(&peer_id).await; + BROKER + .write() + .await + .close_peer_connection_x(peer_id.1, peer_id.0) + .await; } for anon in anonymous { BROKER.write().await.close_anonymous(anon.1, anon.0).await; @@ -422,7 +428,7 @@ impl Broker { Some(Either::Right(remote_peer_id)) => { let res = join.next().await; log_info!("SOCKET IS CLOSED {:?} peer_id: {:?}", res, remote_peer_id); - BROKER.write().await.remove_peer_id(&remote_peer_id); + BROKER.write().await.remove_peer_id(&remote_peer_id, None); } _ => { log_info!( @@ -478,7 +484,7 @@ impl Broker { lastPeerAdvert: None, connected, }; - self.peers.insert(remote_peer_id.to_dh_slice(), bpi); + self.peers.insert((None, remote_peer_id.to_dh_slice()), bpi); Ok(()) } @@ -498,7 +504,7 @@ impl Broker { pub async fn connect( &mut self, cnx: Box, - peer_privk: Sensitive<[u8; 32]>, + peer_privk: PrivKey, peer_pubk: PubKey, remote_peer_id: DirectPeerId, config: StartConfig, @@ -514,7 +520,7 @@ impl Broker { let mut connection = cnx .open( config.get_url(), - Sensitive::<[u8; 32]>::from_slice(peer_privk.deref()), + peer_privk.clone(), peer_pubk, remote_peer_id, config.clone(), @@ -544,12 +550,13 @@ impl Broker { lastPeerAdvert: None, connected, }; - self.peers.insert(remote_peer_id.to_dh_slice(), bpi); + self.peers + .insert((config.get_user(), remote_peer_id.to_dh_slice()), bpi); async fn watch_close( mut join: Receiver>, cnx: Box, - peer_privk: Sensitive<[u8; 32]>, + peer_privk: PrivKey, peer_pubkey: PubKey, remote_peer_id: DirectPeerId, config: StartConfig, @@ -563,7 +570,7 @@ impl Broker { { // we intend to reconnect let mut broker = BROKER.write().await; - broker.reconnecting(&remote_peer_id); + broker.reconnecting(&remote_peer_id, config.get_user()); // TODO: deal with cycle error https://users.rust-lang.org/t/recursive-async-method-causes-cycle-error/84628/5 // let result = broker // .connect(cnx, ip, core, peer_pubk, peer_privk, remote_peer_id) @@ -572,7 +579,10 @@ impl Broker { // TODO: deal with error and incremental backoff } else { log_info!("REMOVED"); - BROKER.write().await.remove_peer_id(&remote_peer_id); + BROKER + .write() + .await + .remove_peer_id(&remote_peer_id, config.get_user()); } } .await; @@ -589,8 +599,8 @@ impl Broker { Ok(()) } - pub async fn close_peer_connection_x(&mut self, peer_id: &X25519PubKey) { - if let Some(peer) = self.peers.get_mut(peer_id) { + pub async fn close_peer_connection_x(&mut self, peer_id: X25519PubKey, user: Option) { + if let Some(peer) = self.peers.get_mut(&(user, peer_id)) { match &mut peer.connected { PeerConnection::Core(_) => { //TODO @@ -605,8 +615,9 @@ impl Broker { } } - pub async fn close_peer_connection(&mut self, peer_id: &DirectPeerId) { - self.close_peer_connection_x(&peer_id.to_dh_slice()).await + pub async fn close_peer_connection(&mut self, peer_id: &DirectPeerId, user: Option) { + self.close_peer_connection_x(peer_id.to_dh_slice(), user) + .await } pub async fn close_anonymous( diff --git a/p2p-net/src/connection.rs b/p2p-net/src/connection.rs index e8479e7..86a6a27 100644 --- a/p2p-net/src/connection.rs +++ b/p2p-net/src/connection.rs @@ -53,7 +53,7 @@ pub trait IConnect: Send + Sync { async fn open( &self, url: String, - peer_privk: Sensitive<[u8; 32]>, + peer_privk: PrivKey, peer_pubk: PubKey, remote_peer: DirectPeerId, config: StartConfig, @@ -70,7 +70,7 @@ pub trait IAccept: Send + Sync { &self, remote_bind_address: BindAddress, local_bind_address: BindAddress, - peer_privk: Sensitive<[u8; 32]>, + peer_privk: PrivKey, socket: Self::Socket, ) -> Result; } @@ -120,7 +120,7 @@ pub struct NoiseFSM { noise_cipher_state_enc: Option>, noise_cipher_state_dec: Option>, - local: Option>, + local: Option, remote: Option, nonce_for_hello: Vec, @@ -182,6 +182,12 @@ impl StartConfig { _ => unimplemented!(), } } + pub fn get_user(&self) -> Option { + match self { + Self::Client(config) => Some(config.user), + _ => None, + } + } } impl NoiseFSM { @@ -191,7 +197,7 @@ impl NoiseFSM { dir: ConnectionDir, actors: Arc>>>, sender: Sender, - local: Option>, + local: Option, remote: Option, ) -> Self { Self { @@ -280,7 +286,7 @@ impl NoiseFSM { noise_xk(), false, &[], - Some(from_ed_priv_to_dh_priv(self.local.take().unwrap())), + Some(sensitive_from_privkey(self.local.take().unwrap().to_dh())), None, None, None, @@ -363,7 +369,9 @@ impl NoiseFSM { noise_xk(), true, &[], - Some(from_ed_priv_to_dh_priv(self.local.take().unwrap())), + Some(sensitive_from_privkey( + self.local.take().unwrap().to_dh(), + )), None, Some(*self.remote.unwrap().to_dh_from_ed().slice()), None, @@ -410,12 +418,12 @@ impl NoiseFSM { ) .is_ok() { - probe_response.peer_id = - Some(ed_sensitive_privkey_to_pubkey( - self.local - .as_ref() - .ok_or(ProtocolError::BrokerError)?, - )); + probe_response.peer_id = Some( + self.local + .as_ref() + .ok_or(ProtocolError::BrokerError)? + .to_pub(), + ); } self.send(ProtocolMessage::ProbeResponse(probe_response)) .await?; @@ -586,7 +594,7 @@ impl NoiseFSM { }; let ser = serde_bare::to_vec(&content)?; let sig = - sign(client_config.client_priv, client_config.client, &ser)?; + sign(&client_config.client_priv, &client_config.client, &ser)?; let client_auth = ClientAuth::V0(ClientAuthV0 { content, /// Signature by client key @@ -963,7 +971,7 @@ impl ConnectionBase { pub fn start_read_loop( &mut self, bind_addresses: Option<(BindAddress, BindAddress)>, - local: Option>, + local: Option, remote: Option, ) { let (sender_tx, sender_rx) = mpsc::unbounded(); diff --git a/p2p-net/src/lib.rs b/p2p-net/src/lib.rs index a4d4172..9f20fef 100644 --- a/p2p-net/src/lib.rs +++ b/p2p-net/src/lib.rs @@ -15,7 +15,7 @@ pub mod types; pub mod errors; -pub mod broker_connection; +//pub mod broker_connection; pub mod broker; @@ -29,6 +29,8 @@ pub mod utils; pub mod tests; +pub mod site; + #[cfg(debug_assertions)] pub static WS_PORT: u16 = 14400; diff --git a/p2p-net/src/site.rs b/p2p-net/src/site.rs new file mode 100644 index 0000000..b6f1228 --- /dev/null +++ b/p2p-net/src/site.rs @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers + * All rights reserved. + * Licensed under the Apache License, Version 2.0 + * + * or the MIT license , + * at your option. All files in the project carrying such + * notice may not be copied, modified, or distributed except + * according to those terms. +*/ + +use crate::types::{SiteStore, SiteType, SiteV0}; + +use p2p_repo::errors::NgError; +use p2p_repo::types::{BlockRef, PrivKey, SymKey}; +use p2p_repo::utils::{generate_keypair, sign, verify}; + +impl SiteV0 { + // pub fn site_identity(&self) -> &Identity { + // match site_type { + // SiteType::Individual => { + // Identity::IndividualSite(self.site_key); + // } + // SiteType::Org => { + // Identity::OrgPublic(self.public_key) + // } + // } + // } + pub fn create(site_type: SiteType) -> Result { + let site_key = PrivKey::random_ed(); + + let public_key = PrivKey::random_ed(); + + let protected_key = PrivKey::random_ed(); + + let private_key = PrivKey::random_ed(); + + let public = SiteStore { + key: PrivKey::dummy(), + root_branch_def_ref: BlockRef::dummy(), + repo_secret: SymKey::random(), + }; + + let protected = SiteStore { + key: PrivKey::dummy(), + root_branch_def_ref: BlockRef::dummy(), + repo_secret: SymKey::random(), + }; + + let private = SiteStore { + key: PrivKey::dummy(), + root_branch_def_ref: BlockRef::dummy(), + repo_secret: SymKey::random(), + }; + + Ok(Self { + site_type, + site_key, + public, + protected, + private, + cores: vec![], + bootstraps: vec![], + }) + } +} diff --git a/p2p-net/src/types.rs b/p2p-net/src/types.rs index 99013eb..fa45c32 100644 --- a/p2p-net/src/types.rs +++ b/p2p-net/src/types.rs @@ -21,6 +21,7 @@ use crate::{actor::EActor, actors::*, errors::ProtocolError}; use core::fmt; use p2p_repo::types::*; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::{ any::{Any, TypeId}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, @@ -81,6 +82,80 @@ pub struct Interface { pub ipv6: Vec, } +/// List of Identity types +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum Identity { + OrgSite(PubKey), + IndividualSite(PubKey), + OrgPublic(PubKey), + OrgProtected(PubKey), + OrgPrivate(PubKey), + IndividualPublic(PubKey), + IndividualProtected(PubKey), + IndividualPrivate(PubKey), + Group(RepoId), + Dialog(RepoId), + Document(RepoId), + DialogOverlay(Digest), +} + +/// Site type +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub enum SiteType { + Org, + Individual, // formerly Personal +} + +/// Site Store +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SiteStore { + // pub identity: Identity, + pub key: PrivKey, + // signature with site_key + // pub sig: Sig, + pub root_branch_def_ref: ObjectRef, + + pub repo_secret: SymKey, +} + +/// Site V0 +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct SiteV0 { + pub site_type: SiteType, + // Identity::OrgSite or Identity::IndividualSite + // pub site_identity: Identity, + pub site_key: PrivKey, + + // Identity::OrgPublic or Identity::IndividualPublic + pub public: SiteStore, + + // Identity::OrgProtected or Identity::IndividualProtected + pub protected: SiteStore, + + // Identity::OrgPrivate or Identity::IndividualPrivate + pub private: SiteStore, + + pub cores: Vec, + + pub bootstraps: Vec, +} + +/// Reduced Site (for QRcode) +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ReducedSiteV0 { + pub site_key: PrivKey, + + pub private_site_key: PrivKey, + + pub private_site_root_branch_def_ref: ObjectRef, + + pub private_site_repo_secret: SymKey, + + pub cores: Vec, + + pub bootstraps: Vec, +} + /// Bind address #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct BindAddress { @@ -108,6 +183,22 @@ impl From<&SocketAddr> for BindAddress { } } +/// Core Broker connection details Version 0 +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +pub struct BrokerCoreV0 { + /// peerId of the server + pub peer_id: PubKey, + + /// network addresses of the broker, typically an IpV4 and an optional IPV6 addr. core broker should not be multi-homed. + pub addrs: Vec, +} + +/// Core Broker connection details +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Hash)] +pub enum BrokerCore { + V0(BrokerCoreV0), +} + /// BrokerServerTypeV0 type #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum BrokerServerTypeV0 { @@ -116,6 +207,7 @@ pub enum BrokerServerTypeV0 { BoxPublic(Vec), BoxPublicDyn(Vec), // can be empty Domain(String), // accepts an option trailing ":port" number + //Core(Vec), } /// BrokerServer details Version 0 @@ -186,6 +278,107 @@ impl BrokerServerV0 { }) } + fn first_ipv6_or_ipv4( + ipv4: bool, + ipv6: bool, + addrs: &Vec, + ) -> Option<&BindAddress> { + if ipv6 { + for addr in addrs { + if addr.ip.is_v6() { + return Some(addr); + } + } + } + if ipv4 { + for addr in addrs { + if addr.ip.is_v4() { + return Some(addr); + } + } + } + return None; + } + + fn app_ng_one_bootstrap_url(addr: &BindAddress, key: PubKey) -> Option { + let payload = (addr, key); + let payload_ser = serde_bare::to_vec(&payload).ok(); + if payload_ser.is_none() { + return None; + } + Some(format!( + "{}?b={}", + APP_NG_ONE_WS_URL, + base64_url::encode(&payload_ser.unwrap()) + )) + } + + fn app_ng_one_bootstrap_url_with_first_ipv6_or_ipv4( + ipv4: bool, + ipv6: bool, + addrs: &Vec, + key: PubKey, + ) -> Option { + if let Some(addr) = Self::first_ipv6_or_ipv4(ipv4, ipv6, addrs) { + return Self::app_ng_one_bootstrap_url(addr, key); + } + None + } + + /// set ipv6 only if the browser connected with a remote IPV6. always set ipv4 as a fallback (for now). + pub async fn get_url_for_ngone(&self, ipv4: bool, ipv6: bool) -> Option { + match &self.server_type { + BrokerServerTypeV0::BoxPublic(addrs) => { + Self::app_ng_one_bootstrap_url_with_first_ipv6_or_ipv4( + ipv4, + ipv6, + addrs, + self.peer_id, + ) + } + BrokerServerTypeV0::BoxPublicDyn(addrs) => { + let resp = reqwest::get(api_dyn_peer_url(&self.peer_id)).await; + if resp.is_ok() { + let resp = resp.unwrap().json::>().await; + if resp.is_ok() { + return Self::app_ng_one_bootstrap_url_with_first_ipv6_or_ipv4( + ipv4, + ipv6, + &resp.unwrap(), + self.peer_id, + ); + } + } + if addrs.len() > 0 { + Self::app_ng_one_bootstrap_url_with_first_ipv6_or_ipv4( + ipv4, + ipv6, + &addrs, + self.peer_id, + ) + } else { + None + } + } + BrokerServerTypeV0::Domain(domain) => Some(format!("https://{}", domain)), + BrokerServerTypeV0::Localhost(port) => Some(local_ws_url(&port)), + BrokerServerTypeV0::BoxPrivate(_) => { + if ipv6 { + let v6 = self.first_ipv6().map(|v| v.0); + if v6.is_some() { + return v6; + } + } + if ipv4 { + self.first_ipv4().map(|v| v.0) + } else { + None + } + } + _ => None, + } + } + /// on web browser, returns the connection URL and an optional list of BindAddress if a relay is needed /// filtered by the current location url of the webpage /// on native apps, returns or the connection URL without optional BindAddress or an empty string with @@ -261,6 +454,7 @@ impl BrokerServerV0 { } else { // From native / tauri app match &self.server_type { + //BrokerServerTypeV0::Core(_) => None, BrokerServerTypeV0::Localhost(port) => Some((local_ws_url(port), vec![])), BrokerServerTypeV0::BoxPrivate(addrs) => Some((String::new(), addrs.clone())), BrokerServerTypeV0::BoxPublic(addrs) => Some((String::new(), addrs.clone())), @@ -503,21 +697,20 @@ impl ListenerV0 { let mut res: Vec = vec![]; match self.accept_forward_for { AcceptForwardForV0::PublicStatic(_) => { + let pub_addrs = self.accept_forward_for.get_public_bind_addresses(); + //res.push(BrokerServerTypeV0::Core(pub_addrs.clone())); if !self.refuse_clients { - res.push(BrokerServerTypeV0::BoxPublic( - self.accept_forward_for.get_public_bind_addresses(), - )); + res.push(BrokerServerTypeV0::BoxPublic(pub_addrs)); } if self.accept_direct { res.push(BrokerServerTypeV0::BoxPrivate(addrs)); } } AcceptForwardForV0::PublicDyn(_) => { + let pub_addrs = self.accept_forward_for.get_public_bind_addresses(); + //res.push(BrokerServerTypeV0::Core(pub_addrs.clone())); if !self.refuse_clients { - res.push(BrokerServerTypeV0::BoxPublicDyn( - // self.accept_forward_for.get_public_bind_addresses(), //FIXME. we should use this, but for now it isnt implemented - vec![], - )); + res.push(BrokerServerTypeV0::BoxPublicDyn(pub_addrs)); } if self.accept_direct { res.push(BrokerServerTypeV0::BoxPrivate(addrs)); @@ -545,8 +738,11 @@ impl ListenerV0 { AcceptForwardForV0::No => { if self.if_type == InterfaceType::Loopback { res.push(BrokerServerTypeV0::Localhost(addrs[0].port)); - } else if self.if_type == InterfaceType::Public && !self.refuse_clients { - res.push(BrokerServerTypeV0::BoxPublic(addrs)); + } else if self.if_type == InterfaceType::Public { + //res.push(BrokerServerTypeV0::Core(addrs.clone())); + if !self.refuse_clients { + res.push(BrokerServerTypeV0::BoxPublic(addrs)); + } } else if self.if_type == InterfaceType::Private { res.push(BrokerServerTypeV0::BoxPrivate(addrs)); } @@ -1546,9 +1742,9 @@ impl OverlayJoin { OverlayJoin::V0(o) => o.repo_pubkey, } } - pub fn secret(&self) -> SymKey { + pub fn secret(&self) -> &SymKey { match self { - OverlayJoin::V0(o) => o.secret, + OverlayJoin::V0(o) => &o.secret, } } pub fn peers(&self) -> &Vec { @@ -2317,9 +2513,10 @@ pub static MAGIC_NG_RESPONSE: [u8; 4] = [89u8, 88u8, 78u8, 75u8]; pub enum Authorization { Discover, ExtMessage, - Client, Core, - Admin, + Client(PubKey), + OverlayJoin(PubKey), + Admin(PubKey), } /// ProbeResponse @@ -2557,6 +2754,9 @@ pub struct RepoLinkV0 { /// Repository secret pub secret: SymKey, + /// current root branch definition commit + pub root_branch_def_ref: ObjectRef, + /// Peers to connect to pub peers: Vec, } @@ -2568,19 +2768,19 @@ pub enum RepoLink { } impl RepoLink { - pub fn id(&self) -> PubKey { + pub fn id(&self) -> &PubKey { match self { - RepoLink::V0(o) => o.id, + RepoLink::V0(o) => &o.id, } } - pub fn secret(&self) -> SymKey { + pub fn secret(&self) -> &SymKey { match self { - RepoLink::V0(o) => o.secret, + RepoLink::V0(o) => &o.secret, } } - pub fn peers(&self) -> Vec { + pub fn peers(&self) -> &Vec { match self { - RepoLink::V0(o) => o.peers.clone(), + RepoLink::V0(o) => &o.peers, } } } diff --git a/p2p-net/src/utils.rs b/p2p-net/src/utils.rs index 70c2efc..1f298ff 100644 --- a/p2p-net/src/utils.rs +++ b/p2p-net/src/utils.rs @@ -12,11 +12,11 @@ use async_std::task; use ed25519_dalek::*; use futures::{channel::mpsc, select, Future, FutureExt, SinkExt}; -pub use noise_protocol::U8Array; +use noise_protocol::U8Array; use noise_protocol::DH; -pub use noise_rust_crypto::sensitive::Sensitive; -use p2p_repo::log::*; +use noise_rust_crypto::sensitive::Sensitive; use p2p_repo::types::PubKey; +use p2p_repo::{log::*, types::PrivKey}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; #[cfg(target_arch = "wasm32")] @@ -48,50 +48,28 @@ where }) } -pub type Sender = mpsc::UnboundedSender; -pub type Receiver = mpsc::UnboundedReceiver; - -pub fn keypair_from_ed(secret: SecretKey, public: PublicKey) -> (Sensitive<[u8; 32]>, PubKey) { - let ed_priv_key = secret.to_bytes(); - let ed_pub_key = public.to_bytes(); - //let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key); - let pub_key = PubKey::Ed25519PubKey(ed_pub_key); - let priv_key = Sensitive::<[u8; 32]>::from_slice(&ed_priv_key); - (priv_key, pub_key) +pub fn sensitive_from_privkey(privkey: PrivKey) -> Sensitive<[u8; 32]> { + // we copy the key here, because otherwise the 2 zeroize would conflict. as the drop of the PrivKey might be called before the one of Sensitive + let mut bits: [u8; 32] = [0u8; 32]; + bits.copy_from_slice(privkey.slice()); + Sensitive::<[u8; 32]>::from_slice(&bits) } -pub fn ed_sensitive_privkey_to_pubkey(privkey: &Sensitive<[u8; 32]>) -> PubKey { - //TODO FIXME do not create a SecretKey or call into() on it, as this is not using Sensitive<> - let sk = SecretKey::from_bytes(privkey.as_slice()).unwrap(); - let pk: PublicKey = (&sk).into(); - PubKey::Ed25519PubKey(pk.to_bytes()) +pub fn dh_privkey_from_sensitive(privkey: Sensitive<[u8; 32]>) -> PrivKey { + // we copy the key here, because otherwise the 2 zeroize would conflict. as the drop of the Sensitive might be called before the one of PrivKey + let mut bits: [u8; 32] = [0u8; 32]; + bits.copy_from_slice(privkey.as_slice()); + PrivKey::X25519PrivKey(bits) } -pub fn keys_from_bytes(secret_key: [u8; 32]) -> (Sensitive<[u8; 32]>, PubKey) { - let sk = SecretKey::from_bytes(&secret_key).unwrap(); - let pk: PublicKey = (&sk).into(); - - let pub_key = PubKey::Ed25519PubKey(pk.to_bytes()); - - let priv_key = Sensitive::<[u8; 32]>::from_slice(&secret_key); - (priv_key, pub_key) -} +pub type Sender = mpsc::UnboundedSender; +pub type Receiver = mpsc::UnboundedReceiver; -pub fn gen_dh_keys() -> (Sensitive<[u8; 32]>, [u8; 32]) { +pub fn gen_dh_keys() -> (PrivKey, PubKey) { let pri = noise_rust_crypto::X25519::genkey(); let publ = noise_rust_crypto::X25519::pubkey(&pri); - (pri, publ) -} - -pub fn gen_ed_keys() -> (Sensitive<[u8; 32]>, PubKey) { - let mut ed25519_priv = Sensitive::<[u8; 32]>::new(); - getrandom::getrandom(&mut *ed25519_priv).expect("getrandom failed"); - //TODO FIXME do not create a SecretKey or call into() on it, as this is not using Sensitive<> - let secret = SecretKey::from_bytes(&ed25519_priv.as_slice()).unwrap(); - let ed25519_pub: PublicKey = (&secret).into(); - - (ed25519_priv, PubKey::Ed25519PubKey(ed25519_pub.to_bytes())) + (dh_privkey_from_sensitive(pri), PubKey::X25519PubKey(publ)) } pub struct Dual25519Keys { @@ -101,47 +79,18 @@ pub struct Dual25519Keys { pub ed25519_pub: PublicKey, } -pub fn from_ed_priv_to_dh_priv(private: Sensitive<[u8; 32]>) -> Sensitive<[u8; 32]> { - let ed25519_priv = SecretKey::from_bytes(&private.as_slice()).unwrap(); - let exp: ExpandedSecretKey = (&ed25519_priv).into(); - let exp_bytes = exp.to_bytes(); - let mut bits = Sensitive::<[u8; 32]>::from_slice(&exp_bytes[0..32]); - bits[0] &= 248; - bits[31] &= 127; - bits[31] |= 64; - bits -} - impl Dual25519Keys { pub fn generate() -> Self { - let mut x25519_priv = Sensitive::<[u8; 32]>::new(); - getrandom::getrandom(&mut *x25519_priv).expect("getrandom failed"); + let mut random = Sensitive::<[u8; 32]>::new(); + getrandom::getrandom(&mut *random).expect("getrandom failed"); - let ed25519_priv = SecretKey::from_bytes(&x25519_priv.as_slice()).unwrap(); + let ed25519_priv = SecretKey::from_bytes(&random.as_slice()).unwrap(); let exp: ExpandedSecretKey = (&ed25519_priv).into(); - let exp_bytes = exp.to_bytes(); + let mut exp_bytes = exp.to_bytes(); let ed25519_pub: PublicKey = (&ed25519_priv).into(); - - let mut bits = Sensitive::<[u8; 32]>::from_slice(&exp_bytes[0..32]); - bits[0] &= 248; - bits[31] &= 127; - bits[31] |= 64; - - let x25519_public = noise_rust_crypto::X25519::pubkey(&bits); - - Self { - x25519_priv: bits, - x25519_public, - ed25519_priv, - ed25519_pub, + for byte in &mut exp_bytes[32..] { + *byte = 0; } - } - pub fn from_sensitive(sensitive: Sensitive<[u8; 32]>) -> Self { - let ed25519_priv = SecretKey::from_bytes(&sensitive.as_slice()).unwrap(); - let exp: ExpandedSecretKey = (&ed25519_priv).into(); - let exp_bytes = exp.to_bytes(); - let ed25519_pub: PublicKey = (&ed25519_priv).into(); - let mut bits = Sensitive::<[u8; 32]>::from_slice(&exp_bytes[0..32]); bits[0] &= 248; bits[31] &= 127; diff --git a/p2p-repo/Cargo.toml b/p2p-repo/Cargo.toml index d74801e..9c932fa 100644 --- a/p2p-repo/Cargo.toml +++ b/p2p-repo/Cargo.toml @@ -27,6 +27,7 @@ web-time = "0.2.0" wasm-bindgen = "0.2" slice_as_array = "1.1.0" curve25519-dalek = "3.2.0" +zeroize = { version = "1.6.0", features = ["zeroize_derive"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] debug_print = "1.0.0" diff --git a/p2p-repo/src/block.rs b/p2p-repo/src/block.rs index fc8776e..9e2c93b 100644 --- a/p2p-repo/src/block.rs +++ b/p2p-repo/src/block.rs @@ -110,7 +110,7 @@ impl Block { /// Get the key pub fn key(&self) -> Option { match self { - Block::V0(b) => b.key, + Block::V0(b) => b.key.clone(), } } diff --git a/p2p-repo/src/branch.rs b/p2p-repo/src/branch.rs index cdcb13d..8f5fc0c 100644 --- a/p2p-repo/src/branch.rs +++ b/p2p-repo/src/branch.rs @@ -429,128 +429,128 @@ mod test { repo_secret.clone(), &mut store, ); - let ack_body = add_body_ack(vec![], repo_pubkey, repo_secret, &mut store); - let trans_body = add_body_trans(vec![], repo_pubkey, repo_secret, &mut store); + let ack_body = add_body_ack(vec![], repo_pubkey, repo_secret.clone(), &mut store); + let trans_body = add_body_trans(vec![], repo_pubkey, repo_secret.clone(), &mut store); // create & add commits to store log_debug!(">> br"); let br = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 0, vec![], vec![], - branch_body, + branch_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> t1"); let t1 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 1, - vec![br], + vec![br.clone()], vec![], - trans_body, + trans_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> t2"); let t2 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 2, - vec![br], + vec![br.clone()], vec![], - trans_body, + trans_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> a3"); let a3 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 3, - vec![t1], + vec![t1.clone()], vec![], - ack_body, + ack_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> t4"); let t4 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 4, - vec![t2], - vec![t1], - trans_body, + vec![t2.clone()], + vec![t1.clone()], + trans_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> t5"); let t5 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 5, - vec![t1, t2], - vec![t4], - trans_body, + vec![t1.clone(), t2.clone()], + vec![t4.clone()], + trans_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> a6"); let a6 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 6, - vec![t4], + vec![t4.clone()], vec![], - ack_body, + ack_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); log_debug!(">> a7"); let a7 = add_commit( - branch_body, - member_privkey, + branch_body.clone(), + member_privkey.clone(), member_pubkey, 7, - vec![t4], + vec![t4.clone()], vec![], - ack_body, + ack_body.clone(), repo_pubkey, - repo_secret, + repo_secret.clone(), &mut store, ); - let c7 = Commit::load(a7, &store).unwrap(); + let c7 = Commit::load(a7.clone(), &store).unwrap(); c7.verify(&branch, &store).unwrap(); let mut filter = Filter::new(FilterBuilder::new(10, 0.01)); - for commit_ref in [br, t1, t2, a3, t5, a6] { + for commit_ref in [br, t1, t2, a3.clone(), t5.clone(), a6.clone()] { match commit_ref.id { ObjectId::Blake3Digest32(d) => filter.add(&d), } diff --git a/p2p-repo/src/commit.rs b/p2p-repo/src/commit.rs index 7697da2..6974e3d 100644 --- a/p2p-repo/src/commit.rs +++ b/p2p-repo/src/commit.rs @@ -132,7 +132,7 @@ impl Commit { /// Load commit from store pub fn load(commit_ref: ObjectRef, store: &impl RepoStore) -> Result { let (id, key) = (commit_ref.id, commit_ref.key); - match Object::load(id, Some(key), store) { + match Object::load(id, Some(key.clone()), store) { Ok(obj) => { let content = obj .content() @@ -142,7 +142,7 @@ impl Commit { _ => return Err(CommitLoadError::DeserializeError), }; commit.set_id(id); - commit.set_key(key); + commit.set_key(key.clone()); Ok(commit) } Err(ObjectParseError::MissingBlocks(missing)) => { @@ -155,7 +155,7 @@ impl Commit { /// Load commit body from store pub fn load_body(&self, store: &impl RepoStore) -> Result { let content = self.content(); - let (id, key) = (content.body.id, content.body.key); + let (id, key) = (content.body.id, content.body.key.clone()); let obj = Object::load(id.clone(), Some(key.clone()), store).map_err(|e| match e { ObjectParseError::MissingBlocks(missing) => CommitLoadError::MissingBlocks(missing), _ => CommitLoadError::ObjectParseError, @@ -186,7 +186,7 @@ impl Commit { /// Get key of parent `Object` pub fn key(&self) -> Option { match self { - Commit::V0(c) => c.key, + Commit::V0(c) => c.key.clone(), } } @@ -381,7 +381,7 @@ mod test { id: ObjectId::Blake3Digest32([1; 32]), key: SymKey::ChaCha20Key([2; 32]), }; - let obj_refs = vec![obj_ref]; + let obj_refs = vec![obj_ref.clone()]; let branch = obj_ref.clone(); let deps = obj_refs.clone(); let acks = obj_refs.clone(); diff --git a/p2p-repo/src/lib.rs b/p2p-repo/src/lib.rs index 5cfe58c..8f4c1fb 100644 --- a/p2p-repo/src/lib.rs +++ b/p2p-repo/src/lib.rs @@ -18,8 +18,6 @@ pub mod errors; pub mod kcv_store; -pub mod site; - #[macro_use] extern crate slice_as_array; diff --git a/p2p-repo/src/object.rs b/p2p-repo/src/object.rs index 7a0cc88..e18b378 100644 --- a/p2p-repo/src/object.rs +++ b/p2p-repo/src/object.rs @@ -198,9 +198,14 @@ impl Object { let data_chunk_size = valid_block_size - EMPTY_BLOCK_SIZE - DATA_VARINT_EXTRA; let mut blocks: Vec = vec![]; - let conv_key = Self::convergence_key(repo_pubkey, repo_secret); + let conv_key = Self::convergence_key(repo_pubkey, repo_secret.clone()); - let obj_deps = Self::make_deps(deps.clone(), valid_block_size, repo_pubkey, repo_secret); + let obj_deps = Self::make_deps( + deps.clone(), + valid_block_size, + repo_pubkey, + repo_secret.clone(), + ); let content_ser = serde_bare::to_vec(&content).unwrap(); @@ -481,7 +486,7 @@ impl Object { BlockContentV0::DataChunk(chunk) => { if leaves.is_some() { let mut leaf = block.clone(); - leaf.set_key(Some(*key)); + leaf.set_key(Some(key.clone())); let l = &mut **leaves.as_mut().unwrap(); l.push(leaf); } @@ -657,7 +662,7 @@ mod test { exp, max_object_size, repo_pubkey, - repo_secret, + repo_secret.clone(), ); log_debug!("obj.id: {:?}", obj.id()); @@ -800,13 +805,13 @@ mod test { let id = Digest::Blake3Digest32([0u8; 32]); let key = SymKey::ChaCha20Key([0u8; 32]); - let one_key = BlockContentV0::InternalNode(vec![key]); + let one_key = BlockContentV0::InternalNode(vec![key.clone()]); let one_key_ser = serde_bare::to_vec(&one_key).unwrap(); - let two_keys = BlockContentV0::InternalNode(vec![key, key]); + let two_keys = BlockContentV0::InternalNode(vec![key.clone(), key.clone()]); let two_keys_ser = serde_bare::to_vec(&two_keys).unwrap(); - let max_keys = BlockContentV0::InternalNode(vec![key; MAX_ARITY_LEAVES]); + let max_keys = BlockContentV0::InternalNode(vec![key.clone(); MAX_ARITY_LEAVES]); let max_keys_ser = serde_bare::to_vec(&max_keys).unwrap(); let data = BlockContentV0::DataChunk(vec![]); diff --git a/p2p-repo/src/site.rs b/p2p-repo/src/site.rs deleted file mode 100644 index 4bc8c8a..0000000 --- a/p2p-repo/src/site.rs +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers - * All rights reserved. - * Licensed under the Apache License, Version 2.0 - * - * or the MIT license , - * at your option. All files in the project carrying such - * notice may not be copied, modified, or distributed except - * according to those terms. -*/ - -use crate::errors::NgError; -use crate::types::{Identity, Site, SiteType}; -use crate::utils::{generate_keypair, sign, verify}; - -impl Site { - pub fn create(site_type: SiteType) -> Result { - let (site_key, side_id) = generate_keypair(); - - let (public_key, public_id) = generate_keypair(); - - let (protected_key, protected_id) = generate_keypair(); - - let (private_key, private_id) = generate_keypair(); - - let site_identity; - let public_identity; - let protected_identity; - let private_identity; - - match site_type { - SiteType::Individual => { - site_identity = Identity::IndividualSite(side_id); - public_identity = Identity::IndividualPublic(public_id); - protected_identity = Identity::IndividualProtected(protected_id); - private_identity = Identity::IndividualPrivate(private_id); - } - SiteType::Org => { - site_identity = Identity::OrgSite(side_id); - public_identity = Identity::OrgPublic(public_id); - protected_identity = Identity::OrgProtected(protected_id); - private_identity = Identity::OrgPrivate(private_id); - } - } - - let public_sig = sign( - site_key, - side_id, - &serde_bare::to_vec(&public_identity).unwrap(), - )?; - - let protected_sig = sign( - site_key, - side_id, - &serde_bare::to_vec(&protected_identity).unwrap(), - )?; - - let private_sig = sign( - site_key, - side_id, - &serde_bare::to_vec(&private_identity).unwrap(), - )?; - - Ok(Self { - site_type, - site_identity, - site_key, - public_identity, - public_key, - public_sig, - protected_identity, - protected_key, - protected_sig, - private_identity, - private_key, - private_sig, - }) - } -} diff --git a/p2p-repo/src/types.rs b/p2p-repo/src/types.rs index 26ea068..39703dc 100644 --- a/p2p-repo/src/types.rs +++ b/p2p-repo/src/types.rs @@ -15,13 +15,15 @@ use crate::errors::NgError; use crate::utils::{ - decode_key, dh_pubkey_from_ed_slice, dh_slice_from_ed_slice, ed_privkey_to_pubkey, + decode_key, dh_pubkey_array_from_ed_pubkey_slice, dh_pubkey_from_ed_pubkey_slice, + ed_privkey_to_ed_pubkey, from_ed_privkey_to_dh_privkey, random_key, }; use core::fmt; use serde::{Deserialize, Serialize}; use serde_bare::to_vec; use std::collections::HashMap; use std::hash::Hash; +use zeroize::{Zeroize, ZeroizeOnDrop}; // // COMMON TYPES @@ -48,7 +50,7 @@ impl fmt::Display for Digest { pub type ChaCha20Key = [u8; 32]; /// Symmetric cryptographic key -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum SymKey { ChaCha20Key(ChaCha20Key), } @@ -59,6 +61,9 @@ impl SymKey { SymKey::ChaCha20Key(o) => o, } } + pub fn random() -> Self { + SymKey::ChaCha20Key(random_key()) + } } /// Curve25519 public key Edwards form @@ -67,9 +72,12 @@ pub type Ed25519PubKey = [u8; 32]; /// Curve25519 public key Montgomery form pub type X25519PubKey = [u8; 32]; -/// Curve25519 private key +/// Curve25519 private key Edwards form pub type Ed25519PrivKey = [u8; 32]; +/// Curve25519 private key Montgomery form +pub type X25519PrivKey = [u8; 32]; + /// Public key #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PubKey { @@ -85,17 +93,17 @@ impl PubKey { } pub fn to_dh_from_ed(&self) -> PubKey { match self { - PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_slice(ed), + PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_pubkey_slice(ed), _ => panic!( - "cannot convert a Montgomery key to Montgomery. it is already one. check your code" + "there is no need to convert a Montgomery key to Montgomery. it is already one. check your code" ), } } pub fn dh_from_ed_slice(slice: &[u8]) -> PubKey { - dh_pubkey_from_ed_slice(slice) + dh_pubkey_from_ed_pubkey_slice(slice) } pub fn to_dh_slice(&self) -> [u8; 32] { - dh_slice_from_ed_slice(self.slice()) + dh_pubkey_array_from_ed_pubkey_slice(self.slice()) } } @@ -118,19 +126,36 @@ impl TryFrom<&str> for PubKey { } /// Private key -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PrivKey { Ed25519PrivKey(Ed25519PrivKey), + X25519PrivKey(X25519PrivKey), } impl PrivKey { pub fn slice(&self) -> &[u8; 32] { match self { - PrivKey::Ed25519PrivKey(o) => o, + PrivKey::Ed25519PrivKey(o) | PrivKey::X25519PrivKey(o) => o, } } pub fn to_pub(&self) -> PubKey { - ed_privkey_to_pubkey(self) + match self { + PrivKey::Ed25519PrivKey(_) => ed_privkey_to_ed_pubkey(self), + _ => panic!("X25519PrivKey to pub not implemented"), + } + } + + #[deprecated(note = "**Don't use dummy method**")] + pub fn dummy() -> PrivKey { + PrivKey::Ed25519PrivKey([0u8; 32]) + } + + pub fn to_dh(&self) -> PrivKey { + from_ed_privkey_to_dh_privkey(self) + } + + pub fn random_ed() -> Self { + PrivKey::Ed25519PrivKey(random_key()) } } @@ -220,57 +245,6 @@ pub enum PermissionType { CHANGE_ACK_CONFIG, } -/// List of Identity types -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum Identity { - OrgSite(PubKey), - IndividualSite(PubKey), - OrgPublic(PubKey), - OrgProtected(PubKey), - OrgPrivate(PubKey), - IndividualPublic(PubKey), - IndividualProtected(PubKey), - IndividualPrivate(PubKey), - Group(RepoId), - Dialog(RepoId), - Document(RepoId), - DialogOverlay(Digest), -} - -/// Site type -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum SiteType { - Org, - Individual, // formerly Personal -} - -/// Site -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub struct Site { - pub site_type: SiteType, - // Identity::OrgSite or Identity::IndividualSite - pub site_identity: Identity, - pub site_key: PrivKey, - - // Identity::OrgPublic or Identity::IndividualPublic - pub public_identity: Identity, - pub public_key: PrivKey, - // signature of public_identity with site_key - pub public_sig: Sig, - - // Identity::OrgProtected or Identity::IndividualProtected - pub protected_identity: Identity, - pub protected_key: PrivKey, - // signature of protected_identity with site_key - pub protected_sig: Sig, - - // Identity::OrgPrivate or Identity::IndividualPrivate - pub private_identity: Identity, - pub private_key: PrivKey, - // signature of private_identity with site_key - pub private_sig: Sig, -} - /// RepoHash: /// BLAKE3 hash of the RepoId pub type RepoHash = Digest; @@ -289,7 +263,7 @@ pub type RepoId = PubKey; pub type BlockId = Digest; /// Block reference -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct BlockRef { /// Object ID pub id: BlockId, @@ -298,6 +272,16 @@ pub struct BlockRef { pub key: SymKey, } +impl BlockRef { + #[deprecated(note = "**Don't use dummy method**")] + pub fn dummy() -> Self { + BlockRef { + id: Digest::Blake3Digest32([0u8; 32]), + key: SymKey::ChaCha20Key([0u8; 32]), + } + } +} + /// Object ID pub type ObjectId = BlockId; @@ -405,13 +389,13 @@ pub enum Repository { } /// Add a branch to the repository -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum AddBranch { V0(ObjectRef), } /// Remove a branch from the repository -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub enum RemoveBranch { V0(ObjectRef), } diff --git a/p2p-repo/src/utils.rs b/p2p-repo/src/utils.rs index 6b21f40..7550f3f 100644 --- a/p2p-repo/src/utils.rs +++ b/p2p-repo/src/utils.rs @@ -17,8 +17,37 @@ use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use ed25519_dalek::*; use futures::channel::mpsc; use rand::rngs::OsRng; +use rand::RngCore; use web_time::{SystemTime, UNIX_EPOCH}; +use zeroize::Zeroize; +pub fn ed_keypair_from_priv_bytes(secret_key: [u8; 32]) -> (PrivKey, PubKey) { + let sk = SecretKey::from_bytes(&secret_key).unwrap(); + let pk: PublicKey = (&sk).into(); + let pub_key = PubKey::Ed25519PubKey(pk.to_bytes()); + let priv_key = PrivKey::Ed25519PrivKey(secret_key); + (priv_key, pub_key) +} + +pub fn from_ed_privkey_to_dh_privkey(private: &PrivKey) -> PrivKey { + //SecretKey and ExpandedSecretKey are Zeroized at drop + if let PrivKey::Ed25519PrivKey(slice) = private { + let ed25519_priv = SecretKey::from_bytes(slice).unwrap(); + let exp: ExpandedSecretKey = (&ed25519_priv).into(); + let mut exp_bytes = exp.to_bytes(); + exp_bytes[32..].zeroize(); + let mut bits = *slice_as_array!(&exp_bytes[0..32], [u8; 32]).unwrap(); + bits[0] &= 248; + bits[31] &= 127; + bits[31] |= 64; + // PrivKey takes ownership and will zeroize on drop + PrivKey::X25519PrivKey(bits) + } else { + panic!("this is not an Edmonds privkey") + } +} + +/// don't forget to zeroize the string later on pub fn decode_key(key_string: &str) -> Result<[u8; 32], ()> { let vec = base64_url::decode(key_string).map_err(|_| log_err!("key has invalid content"))?; Ok(*slice_as_array!(&vec, [u8; 32]) @@ -26,62 +55,75 @@ pub fn decode_key(key_string: &str) -> Result<[u8; 32], ()> { .map_err(|_| log_err!("key has invalid content array"))?) } -pub fn ed_privkey_to_pubkey(privkey: &PrivKey) -> PubKey { +pub fn ed_privkey_to_ed_pubkey(privkey: &PrivKey) -> PubKey { + // SecretKey is zeroized on drop (3 lines below) se we are safe let sk = SecretKey::from_bytes(privkey.slice()).unwrap(); let pk: PublicKey = (&sk).into(); PubKey::Ed25519PubKey(pk.to_bytes()) } -pub fn generate_null_keypair() -> (PrivKey, PubKey) { +/// use with caution. it should be embedded in a zeroize struct in order to be safe +pub fn random_key() -> [u8; 32] { + let mut sk = [0u8; 32]; + let mut csprng = OsRng {}; + csprng.fill_bytes(&mut sk); + sk +} + +pub fn generate_null_ed_keypair() -> (PrivKey, PubKey) { + // we don't use zeroize because... well, it is already a zeroized privkey ;) let master_key: [u8; 32] = [0; 32]; let sk = SecretKey::from_bytes(&master_key).unwrap(); let pk: PublicKey = (&sk).into(); - - let keypair = Keypair { - public: pk, - secret: sk, - }; - - // log_debug!( - // "private key: ({}) {:?}", - // keypair.secret.as_bytes().len(), - // keypair.secret.as_bytes() - // ); - // log_debug!( - // "public key: ({}) {:?}", - // keypair.public.as_bytes().len(), - // keypair.public.as_bytes() - // ); - let ed_priv_key = keypair.secret.to_bytes(); - let ed_pub_key = keypair.public.to_bytes(); - let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key); - let pub_key = PubKey::Ed25519PubKey(ed_pub_key); + let priv_key = PrivKey::Ed25519PrivKey(sk.to_bytes()); + let pub_key = PubKey::Ed25519PubKey(pk.to_bytes()); (priv_key, pub_key) } -pub fn dh_pubkey_from_ed_slice(public: &[u8]) -> PubKey { - PubKey::X25519PubKey(dh_slice_from_ed_slice(public)) +pub fn dh_pubkey_from_ed_pubkey_slice(public: &[u8]) -> PubKey { + PubKey::X25519PubKey(dh_pubkey_array_from_ed_pubkey_slice(public)) } -pub fn dh_slice_from_ed_slice(public: &[u8]) -> X25519PubKey { +pub fn dh_pubkey_array_from_ed_pubkey_slice(public: &[u8]) -> X25519PubKey { + // the zeroize are not mandatory, because it is a PubKey. let mut bits: [u8; 32] = [0u8; 32]; bits.copy_from_slice(public); - let compressed = CompressedEdwardsY(bits); - let ed_point: EdwardsPoint = compressed.decompress().unwrap(); - let mon_point = ed_point.to_montgomery(); - mon_point.to_bytes() + let mut compressed = CompressedEdwardsY(bits); + let mut ed_point: EdwardsPoint = compressed.decompress().unwrap(); + compressed.zeroize(); + let mut mon_point = ed_point.to_montgomery(); + ed_point.zeroize(); + let array = mon_point.to_bytes(); + mon_point.zeroize(); + array +} + +pub fn pubkey_privkey_to_keypair(pubkey: &PubKey, privkey: &PrivKey) -> Keypair { + match (privkey, pubkey) { + (PrivKey::Ed25519PrivKey(sk), PubKey::Ed25519PubKey(pk)) => { + let secret = SecretKey::from_bytes(sk).unwrap(); + let public = PublicKey::from_bytes(pk).unwrap(); + + Keypair { secret, public } + } + (_, _) => panic!("cannot sign with Montgomery keys"), + } +} + +pub fn keypair_from_ed(secret: SecretKey, public: PublicKey) -> (PrivKey, PubKey) { + let ed_priv_key = secret.to_bytes(); + let ed_pub_key = public.to_bytes(); + let pub_key = PubKey::Ed25519PubKey(ed_pub_key); + let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key); + (priv_key, pub_key) } pub fn sign( - author_privkey: PrivKey, - author_pubkey: PubKey, + author_privkey: &PrivKey, + author_pubkey: &PubKey, content: &Vec, ) -> Result { - let kp = match (author_privkey, author_pubkey) { - (PrivKey::Ed25519PrivKey(sk), PubKey::Ed25519PubKey(pk)) => [sk, pk].concat(), - (_, _) => panic!("cannot sign with Montgomery keys"), - }; - let keypair = Keypair::from_bytes(kp.as_slice())?; + let keypair = pubkey_privkey_to_keypair(author_pubkey, author_privkey); let sig_bytes = keypair.sign(content.as_slice()).to_bytes(); let mut it = sig_bytes.chunks_exact(32); let mut ss: Ed25519Sig = [[0; 32], [0; 32]]; @@ -106,16 +148,6 @@ pub fn verify(content: &Vec, sig: Sig, pub_key: PubKey) -> Result<(), NgErro pub fn generate_keypair() -> (PrivKey, PubKey) { let mut csprng = OsRng {}; let keypair: Keypair = Keypair::generate(&mut csprng); - // log_debug!( - // "private key: ({}) {:?}", - // keypair.secret.as_bytes().len(), - // keypair.secret.as_bytes() - // ); - // log_debug!( - // "public key: ({}) {:?}", - // keypair.public.as_bytes().len(), - // keypair.public.as_bytes() - // ); let ed_priv_key = keypair.secret.to_bytes(); let ed_pub_key = keypair.public.to_bytes(); let priv_key = PrivKey::Ed25519PrivKey(ed_priv_key);