From d98d8d602731b251a1bdadd96778e8a0fc602385 Mon Sep 17 00:00:00 2001 From: Niko PLP Date: Sat, 24 Jun 2023 16:40:40 +0300 Subject: [PATCH] converting peerIds and privekeys to montgomery for DH purposes --- Cargo.lock | 3 +- ng-sdk-js/src/lib.rs | 17 ++--- ngcli/src/main.rs | 5 +- ngd/Cargo.toml | 1 - ngd/src/main.rs | 47 ++++++------- p2p-broker/src/server_ws.rs | 10 ++- p2p-client-ws/src/remote_ws.rs | 12 +++- p2p-client-ws/src/remote_ws_wasm.rs | 2 +- p2p-net/src/broker.rs | 101 +++++++++++++++++----------- p2p-net/src/connection.rs | 38 +++++++++-- p2p-net/src/errors.rs | 4 +- p2p-net/src/utils.rs | 66 ++++++++++++++++-- p2p-repo/Cargo.toml | 2 + p2p-repo/src/commit.rs | 2 + p2p-repo/src/errors.rs | 12 ++++ p2p-repo/src/lib.rs | 3 + p2p-repo/src/object.rs | 9 +++ p2p-repo/src/types.rs | 54 ++++++++++++++- p2p-repo/src/utils.rs | 34 +++++----- 19 files changed, 308 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07caf94..e7aa4a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2792,7 +2792,6 @@ dependencies = [ "serde_bare", "serde_bytes", "serde_json", - "slice_as_array", ] [[package]] @@ -3057,6 +3056,7 @@ dependencies = [ "base64-url", "blake3", "chacha20", + "curve25519-dalek 3.2.0", "debug_print", "ed25519-dalek", "fastbloom-rs", @@ -3068,6 +3068,7 @@ dependencies = [ "serde", "serde_bare", "serde_bytes", + "slice_as_array", "wasm-bindgen", "web-time", ] diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs index 40bcfe5..1dedfab 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::{spawn_and_log_error, Receiver, ResultSend, Sender}; +use p2p_net::utils::{gen_ed_keys, spawn_and_log_error, Receiver, ResultSend, Sender}; use p2p_repo::log::*; use p2p_repo::types::*; use p2p_repo::utils::generate_keypair; @@ -207,13 +207,14 @@ pub async fn start() { // getrandom::getrandom(&mut random_buf).unwrap(); async fn inner_task() -> ResultSend<()> { - let server_key = PubKey::Ed25519PubKey([ - 95, 155, 249, 202, 41, 105, 71, 51, 206, 126, 9, 84, 132, 92, 60, 7, 74, 179, 46, 21, - 21, 242, 171, 27, 249, 79, 76, 176, 168, 43, 83, 2, - ]); + let server_key: PubKey = "KWdmwr4_oO62IFGfKzuyotQOixqXGNWv59CRAGvPTjM".try_into()?; + log_debug!("server_key:{}", server_key); - let keys = p2p_net::utils::gen_keys(); - let pub_key = PubKey::Ed25519PubKey(keys.1); + //let keys = p2p_net::utils::gen_dh_keys(); + //let pub_key = PubKey::Ed25519PubKey(keys.1); + let keys = gen_ed_keys(); + let x_from_ed = keys.1.to_dh_from_ed(); + log_info!("Pub from X {}", x_from_ed); let (client_priv_key, client_pub_key) = generate_keypair(); let (user_priv_key, user_pub_key) = generate_keypair(); @@ -228,7 +229,7 @@ pub async fn start() { IP::try_from(&IpAddr::from_str("127.0.0.1").unwrap()).unwrap(), None, keys.0, - pub_key, + keys.1, server_key, StartConfig::Client(ClientConfig { user: user_pub_key, diff --git a/ngcli/src/main.rs b/ngcli/src/main.rs index 0b73ffb..1c64649 100644 --- a/ngcli/src/main.rs +++ b/ngcli/src/main.rs @@ -599,13 +599,14 @@ mod test { use async_std::task; use p2p_broker::server_ws::*; - use p2p_net::utils::{gen_keys, Sensitive, U8Array}; + use p2p_net::utils::{gen_dh_keys, Sensitive, U8Array}; use p2p_net::WS_PORT; + use p2p_repo::log::*; use p2p_repo::types::PubKey; #[async_std::test] pub async fn test_remote_cnx() -> Result<(), Box> { - let keys = gen_keys(); + 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); diff --git a/ngd/Cargo.toml b/ngd/Cargo.toml index 52fc6a3..4bd1c15 100644 --- a/ngd/Cargo.toml +++ b/ngd/Cargo.toml @@ -19,7 +19,6 @@ log = "0.4" env_logger = "0.10" clap = { version = "4.3.4", features = ["derive","env","string"] } base64-url = "2.0.0" -slice_as_array = "1.1.0" serde_json = "1.0" regex = "1.8.4" lazy_static = "1.4.0" diff --git a/ngd/src/main.rs b/ngd/src/main.rs index d470d48..3b1872e 100644 --- a/ngd/src/main.rs +++ b/ngd/src/main.rs @@ -6,8 +6,6 @@ // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. -#[macro_use] -extern crate slice_as_array; pub mod types; @@ -26,14 +24,14 @@ 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_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private, keys_from_bytes, - Dual25519Keys, Sensitive, U8Array, + gen_dh_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private, keypair_from_ed, + keys_from_bytes, Dual25519Keys, Sensitive, U8Array, }; use p2p_net::{WS_PORT, WS_PORT_REVERSE_PROXY}; use p2p_repo::log::*; use p2p_repo::{ types::{PrivKey, PubKey}, - utils::{generate_keypair, keypair_from_ed, sign, verify}, + utils::{decode_key, generate_keypair, sign, verify}, }; use serde_json::{from_str, to_string_pretty}; use std::fs::{read_to_string, write}; @@ -46,13 +44,6 @@ use addr::psl::List; use lazy_static::lazy_static; use regex::Regex; -fn decode_key(key_string: &String) -> 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]) - .ok_or(()) - .map_err(|_| log_err!("key has invalid content array"))?) -} - //For windows: {846EE342-7039-11DE-9D20-806E6F6E6963} //For the other OSes: en0 lo ... #[cfg(not(target_os = "windows"))] @@ -384,7 +375,7 @@ async fn main_inner() -> Result<(), ()> { log_err!("provided --key option will not be used as a key file is already present"); gen_broker_keys(Some(key_from_file.unwrap())) } else { - let res = decode_key(key_string) + let res = decode_key(key_string.as_str()) .map_err(|_| log_err!("provided key is invalid. cannot start"))?; if args.save_key { @@ -853,13 +844,8 @@ async fn main_inner() -> Result<(), ()> { ); return Err(()); } - let vec = base64_url::decode(parts[1]) + let pub_key_array = decode_key(parts[1]) .map_err(|_| log_err!("The PEERID provided in the --forward option is invalid"))?; - let pub_key_array = *slice_as_array!(vec.as_slice(), [u8; 32]) - .ok_or(()) - .map_err(|_| { - log_err!("PEERID provided in the --forward option, has invalid array") - })?; let peer_id = PubKey::Ed25519PubKey(pub_key_array); let server_type = if parts[0].len() > 0 { @@ -944,7 +930,7 @@ async fn main_inner() -> Result<(), ()> { // let pub_key = PubKey::Ed25519PubKey(keys.1); // let (ed_priv_key, ed_pub_key) = generate_keypair(); - // let duals = Dual25519Keys::generate(); + //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(); @@ -955,15 +941,22 @@ async fn main_inner() -> Result<(), ()> { let (privkey, pubkey) = keys_from_bytes(keys[1]); - let priv_key_array = *slice_as_array!(privkey.as_slice(), [u8; 32]) - .ok_or(()) - .map_err(|_| log_err!("Private key of peer has invalid array"))?; - let priv_key = PrivKey::Ed25519PrivKey(priv_key_array); - let priv_key_ser = serde_bare::to_vec(&priv_key).unwrap(); - let prix_key_encoded = base64_url::encode(&priv_key_ser); + //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"))?; log_info!("PeerId of node: {}", pubkey); - debug_println!("Private key of peer: {}", prix_key_encoded); + //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); match config.unwrap() { DaemonConfig::V0(v0) => { diff --git a/p2p-broker/src/server_ws.rs b/p2p-broker/src/server_ws.rs index f22e24a..c5df72e 100644 --- a/p2p-broker/src/server_ws.rs +++ b/p2p-broker/src/server_ws.rs @@ -517,7 +517,15 @@ pub async fn accept(tcp: TcpStream, peer_priv_key: Sensitive<[u8; 32]>) { log_debug!("websocket accepted"); let cws = ConnectionWebSocket {}; - let base = cws.accept(peer_priv_key, ws.unwrap()).await.unwrap(); + let base = cws + .accept( + remote_bind_address, + local_bind_address, + peer_priv_key, + ws.unwrap(), + ) + .await + .unwrap(); let res = BROKER .write() diff --git a/p2p-client-ws/src/remote_ws.rs b/p2p-client-ws/src/remote_ws.rs index d33ccf9..81dbee9 100644 --- a/p2p-client-ws/src/remote_ws.rs +++ b/p2p-client-ws/src/remote_ws.rs @@ -60,7 +60,7 @@ impl IConnect for ConnectionWebSocket { Err(NetError::ConnectionError) } Ok((mut websocket, _)) => { - cnx.start_read_loop(peer_privk, Some(remote_peer)); + cnx.start_read_loop(None, peer_privk, Some(remote_peer)); let s = cnx.take_sender(); let r = cnx.take_receiver(); let mut shutdown = cnx.set_shutdown(); @@ -90,12 +90,18 @@ impl IAccept for ConnectionWebSocket { type Socket = WebSocketStream; async fn accept( &self, + remote_bind_address: BindAddress, + local_bind_address: BindAddress, peer_privk: Sensitive<[u8; 32]>, socket: Self::Socket, ) -> Result { let mut cnx = ConnectionBase::new(ConnectionDir::Server, TransportProtocol::WS); - cnx.start_read_loop(peer_privk, None); + cnx.start_read_loop( + Some((local_bind_address, remote_bind_address)), + peer_privk, + None, + ); let s = cnx.take_sender(); let r = cnx.take_receiver(); let mut shutdown = cnx.set_shutdown(); @@ -272,7 +278,7 @@ mod test { 21, 242, 171, 27, 249, 79, 76, 176, 168, 43, 83, 2, ]); - let keys = p2p_net::utils::gen_keys(); + let keys = p2p_net::utils::gen_dh_keys(); let pub_key = PubKey::Ed25519PubKey(keys.1); let (client_priv_key, client_pub_key) = generate_keypair(); diff --git a/p2p-client-ws/src/remote_ws_wasm.rs b/p2p-client-ws/src/remote_ws_wasm.rs index c1d29ad..c3a5a49 100644 --- a/p2p-client-ws/src/remote_ws_wasm.rs +++ b/p2p-client-ws/src/remote_ws_wasm.rs @@ -51,7 +51,7 @@ impl IConnect for ConnectionWebSocket { NetError::ConnectionError })?; - cnx.start_read_loop(peer_privk, Some(remote_peer)); + cnx.start_read_loop(None, peer_privk, Some(remote_peer)); let mut shutdown = cnx.set_shutdown(); spawn_and_log_error(ws_loop( diff --git a/p2p-net/src/broker.rs b/p2p-net/src/broker.rs index 03bb2ef..2ea6857 100644 --- a/p2p-net/src/broker.rs +++ b/p2p-net/src/broker.rs @@ -63,6 +63,7 @@ pub static BROKER: Lazy>> = Lazy::new(|| Arc::new(RwLock::new pub struct Broker { direct_connections: HashMap, peers: HashMap, + /// (local,remote) -> ConnectionBase incoming_anonymous_connections: HashMap<(BindAddress, BindAddress), ConnectionBase>, #[cfg(not(target_arch = "wasm32"))] listeners: HashMap, @@ -338,7 +339,7 @@ impl Broker { pub async fn accept( &mut self, - mut connection: ConnectionBase, + connection: ConnectionBase, remote_bind_address: BindAddress, local_bind_address: BindAddress, ) -> Result<(), NetError> { @@ -346,43 +347,67 @@ impl Broker { return Err(NetError::Closing); } - //self.incoming_anonymous_connections - - // let join = connection.take_shutdown(); - - // let connected = if core.is_some() { - // let dc = DirectConnection { - // ip, - // interface: core.clone().unwrap(), - // remote_peer_id, - // tp: connection.transport_protocol(), - // cnx: connection, - // }; - // self.direct_connections.insert(ip, dc); - // PeerConnection::Core(ip) - // } else { - // PeerConnection::Client(connection) - // }; - // let bpi = BrokerPeerInfo { - // lastPeerAdvert: None, - // connected, - // }; - // self.peers.insert(remote_peer_id, bpi); - - // async fn watch_close( - // mut join: Receiver, - // remote_peer_id: DirectPeerId, - // ) -> ResultSend<()> { - // async move { - // let res = join.next().await; - // log_info!("SOCKET IS CLOSED {:?} {:?}", res, &remote_peer_id); - // log_info!("REMOVED"); - // BROKER.write().await.remove(&remote_peer_id); - // } - // .await; - // Ok(()) - // } - // spawn_and_log_error(watch_close(join, remote_peer_id)); + if self + .incoming_anonymous_connections + .insert((local_bind_address, remote_bind_address), connection) + .is_some() + { + log_err!( + "internal error. duplicate connection {:?} {:?}", + local_bind_address, + remote_bind_address + ); + } + Ok(()) + } + + pub async fn attach_peer_id( + &mut self, + remote_bind_address: BindAddress, + local_bind_address: BindAddress, + remote_peer_id: PubKey, + core: Option, + ) -> Result<(), NetError> { + log_debug!("ATTACH PEER_ID {}", remote_peer_id); + let mut connection = self + .incoming_anonymous_connections + .remove(&(local_bind_address, remote_bind_address)) + .ok_or(NetError::InternalError)?; + let join = connection.take_shutdown(); + let ip = remote_bind_address.ip; + let connected = if core.is_some() { + let dc = DirectConnection { + ip, + interface: core.clone().unwrap(), + remote_peer_id, + tp: connection.transport_protocol(), + cnx: connection, + }; + self.direct_connections.insert(ip, dc); + PeerConnection::Core(ip) + } else { + PeerConnection::Client(connection) + }; + let bpi = BrokerPeerInfo { + lastPeerAdvert: None, + connected, + }; + self.peers.insert(remote_peer_id, bpi); + + async fn watch_close( + mut join: Receiver, + remote_peer_id: DirectPeerId, + ) -> ResultSend<()> { + async move { + let res = join.next().await; + log_info!("SOCKET IS CLOSED {:?} {:?}", res, &remote_peer_id); + log_info!("REMOVED"); + BROKER.write().await.remove(&remote_peer_id); + } + .await; + Ok(()) + } + spawn_and_log_error(watch_close(join, remote_peer_id)); Ok(()) } diff --git a/p2p-net/src/connection.rs b/p2p-net/src/connection.rs index 9a8af4a..4cffe06 100644 --- a/p2p-net/src/connection.rs +++ b/p2p-net/src/connection.rs @@ -17,6 +17,7 @@ use std::sync::Arc; use crate::actor::{Actor, SoS}; use crate::actors::*; +use crate::broker::BROKER; use crate::errors::NetError; use crate::errors::ProtocolError; use crate::types::*; @@ -63,6 +64,8 @@ pub trait IAccept: Send + Sync { type Socket; async fn accept( &self, + remote_bind_address: BindAddress, + local_bind_address: BindAddress, peer_privk: Sensitive<[u8; 32]>, socket: Self::Socket, ) -> Result; @@ -100,6 +103,8 @@ pub struct NoiseFSM { dir: ConnectionDir, sender: Sender, + bind_addresses: Option<(BindAddress, BindAddress)>, + actors: Arc>>>, noise_handshake_state: Option>, @@ -149,6 +154,7 @@ pub enum StartConfig { impl NoiseFSM { pub fn new( + bind_addresses: Option<(BindAddress, BindAddress)>, tp: TransportProtocol, dir: ConnectionDir, actors: Arc>>>, @@ -163,6 +169,7 @@ impl NoiseFSM { FSMstate::Noise0 }, dir, + bind_addresses, actors, sender, noise_handshake_state: None, @@ -276,9 +283,9 @@ impl NoiseFSM { noise_xk(), true, &[], - Some(self.from.take().unwrap()), + Some(from_ed_priv_to_dh_priv(self.from.take().unwrap())), None, - Some(*self.to.unwrap().slice()), + Some(*self.to.unwrap().to_dh_from_ed().slice()), None, ); @@ -304,7 +311,7 @@ impl NoiseFSM { noise_xk(), false, &[], - Some(self.from.take().unwrap()), + Some(from_ed_priv_to_dh_priv(self.from.take().unwrap())), None, None, None, @@ -400,8 +407,21 @@ impl NoiseFSM { if !handshake.completed() { return Err(ProtocolError::NoiseHandshakeFailed); } - - self.to = Some(PubKey::Ed25519PubKey(handshake.get_rs().unwrap())); + let peer_id = PubKey::Ed25519PubKey(handshake.get_rs().unwrap()); + self.to = Some(peer_id); + let (local_bind_address, remote_bind_address) = + self.bind_addresses.ok_or(ProtocolError::BrokerError)?; + BROKER + .write() + .await + .attach_peer_id( + remote_bind_address, + local_bind_address, + peer_id, + None, + ) + .await + .map_err(|_| ProtocolError::BrokerError)?; let ciphers = handshake.get_ciphers(); self.noise_cipher_state_enc = Some(ciphers.1); @@ -739,7 +759,12 @@ impl ConnectionBase { } } - pub fn start_read_loop(&mut self, from: Sensitive<[u8; 32]>, to: Option) { + pub fn start_read_loop( + &mut self, + bind_addresses: Option<(BindAddress, BindAddress)>, + from: Sensitive<[u8; 32]>, + to: Option, + ) { let (sender_tx, sender_rx) = mpsc::unbounded(); let (receiver_tx, receiver_rx) = mpsc::unbounded(); self.sender = Some(sender_rx); @@ -748,6 +773,7 @@ impl ConnectionBase { self.receiver_tx = Some(receiver_tx); let fsm = Arc::new(Mutex::new(NoiseFSM::new( + bind_addresses, self.tp, self.dir.clone(), Arc::clone(&self.actors), diff --git a/p2p-net/src/errors.rs b/p2p-net/src/errors.rs index b147b9b..6798205 100644 --- a/p2p-net/src/errors.rs +++ b/p2p-net/src/errors.rs @@ -29,7 +29,8 @@ pub enum NetError { ConnectionError, SerializationError, ProtocolError, - ConnectionDenied, + AccessDenied, + InternalError, Closing, } //MAX 50 NetErrors @@ -104,6 +105,7 @@ impl From for ProtocolError { match e { p2p_repo::errors::NgError::InvalidSignature => ProtocolError::InvalidSignature, p2p_repo::errors::NgError::SerializationError => ProtocolError::SerializationError, + _ => ProtocolError::OtherError, } } } diff --git a/p2p-net/src/utils.rs b/p2p-net/src/utils.rs index cde9c4f..87e47c2 100644 --- a/p2p-net/src/utils.rs +++ b/p2p-net/src/utils.rs @@ -51,6 +51,15 @@ 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 keys_from_bytes(secret_key: [u8; 32]) -> (Sensitive<[u8; 32]>, PubKey) { let sk = SecretKey::from_bytes(&secret_key).unwrap(); let pk: PublicKey = (&sk).into(); @@ -61,12 +70,23 @@ pub fn keys_from_bytes(secret_key: [u8; 32]) -> (Sensitive<[u8; 32]>, PubKey) { (priv_key, pub_key) } -pub fn gen_keys() -> (Sensitive<[u8; 32]>, [u8; 32]) { +pub fn gen_dh_keys() -> (Sensitive<[u8; 32]>, [u8; 32]) { 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())) +} + pub struct Dual25519Keys { pub x25519_priv: Sensitive<[u8; 32]>, pub x25519_public: [u8; 32], @@ -74,22 +94,56 @@ 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 ed25519_priv = SecretKey::from_bytes(&x25519_priv.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; + bits[31] |= 64; + + let x25519_public = noise_rust_crypto::X25519::pubkey(&bits); + + Self { + x25519_priv: bits, + x25519_public, + ed25519_priv, + ed25519_pub, + } + } + 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(); - x25519_priv[0] &= 248; - x25519_priv[31] &= 127; - x25519_priv[31] |= 64; + 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(&x25519_priv); + let x25519_public = noise_rust_crypto::X25519::pubkey(&bits); Self { - x25519_priv, + x25519_priv: bits, x25519_public, ed25519_priv, ed25519_pub, diff --git a/p2p-repo/Cargo.toml b/p2p-repo/Cargo.toml index f2befae..d74801e 100644 --- a/p2p-repo/Cargo.toml +++ b/p2p-repo/Cargo.toml @@ -25,6 +25,8 @@ futures = "0.3.24" base64-url = "2.0.0" web-time = "0.2.0" wasm-bindgen = "0.2" +slice_as_array = "1.1.0" +curve25519-dalek = "3.2.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] debug_print = "1.0.0" diff --git a/p2p-repo/src/commit.rs b/p2p-repo/src/commit.rs index 835638d..7697da2 100644 --- a/p2p-repo/src/commit.rs +++ b/p2p-repo/src/commit.rs @@ -82,6 +82,7 @@ impl CommitV0 { // sign commit let kp = match (author_privkey, author_pubkey) { (PrivKey::Ed25519PrivKey(sk), PubKey::Ed25519PubKey(pk)) => [sk, pk].concat(), + (_, _) => panic!("cannot sign with Montgomery key"), }; let keypair = Keypair::from_bytes(kp.as_slice())?; let sig_bytes = keypair.sign(content_ser.as_slice()).to_bytes(); @@ -246,6 +247,7 @@ impl Commit { let content_ser = serde_bare::to_vec(&c.content).unwrap(); let pubkey = match c.content.author { PubKey::Ed25519PubKey(pk) => pk, + _ => panic!("author cannot have a Montgomery key"), }; let pk = PublicKey::from_bytes(&pubkey)?; let sig_bytes = match c.sig { diff --git a/p2p-repo/src/errors.rs b/p2p-repo/src/errors.rs index 935d74e..8d4a8ca 100644 --- a/p2p-repo/src/errors.rs +++ b/p2p-repo/src/errors.rs @@ -11,11 +11,23 @@ //! Errors +use core::fmt; +use std::error::Error; + #[derive(Debug, Eq, PartialEq, Clone)] #[repr(u16)] pub enum NgError { InvalidSignature, SerializationError, + InvalidKey, +} + +impl Error for NgError {} + +impl fmt::Display for NgError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } } impl From for NgError { diff --git a/p2p-repo/src/lib.rs b/p2p-repo/src/lib.rs index ddc6f6d..5cfe58c 100644 --- a/p2p-repo/src/lib.rs +++ b/p2p-repo/src/lib.rs @@ -20,6 +20,9 @@ pub mod kcv_store; pub mod site; +#[macro_use] +extern crate slice_as_array; + pub mod log { #[cfg(not(target_arch = "wasm32"))] diff --git a/p2p-repo/src/object.rs b/p2p-repo/src/object.rs index 531b18c..7a0cc88 100644 --- a/p2p-repo/src/object.rs +++ b/p2p-repo/src/object.rs @@ -80,6 +80,7 @@ impl Object { (PubKey::Ed25519PubKey(pubkey), SymKey::ChaCha20Key(secret)) => { [pubkey, secret].concat() } + (_, _) => panic!("cannot sign with Montgomery key"), }; blake3::derive_key("NextGraph Data BLAKE3 key", key_material.as_slice()) } @@ -575,6 +576,14 @@ mod test { /// Maximum data that can fit in object.content const MAX_DATA_PAYLOAD_SIZE: usize = 2097112; + #[test] + pub fn test_pubkey_from_str() { + let pubkey = PubKey::Ed25519PubKey([1u8; 32]); + let str = pubkey.to_string(); + let server_key: PubKey = str.as_str().try_into().unwrap(); + assert_eq!(server_key, pubkey); + } + /// Test JPEG file #[test] pub fn test_jpg() { diff --git a/p2p-repo/src/types.rs b/p2p-repo/src/types.rs index 93140ee..76d5df8 100644 --- a/p2p-repo/src/types.rs +++ b/p2p-repo/src/types.rs @@ -13,6 +13,8 @@ //! //! Corresponds to the BARE schema +use crate::errors::NgError; +use crate::utils::{decode_key, dh_pubkey_from_ed_slice}; use core::fmt; use serde::{Deserialize, Serialize}; use serde_bare::to_vec; @@ -61,7 +63,7 @@ impl SymKey { pub type Ed25519PubKey = [u8; 32]; /// Curve25519 public key Montgomery form -pub type Mo25519PubKey = [u8; 32]; +pub type X25519PubKey = [u8; 32]; /// Curve25519 private key pub type Ed25519PrivKey = [u8; 32]; @@ -70,24 +72,46 @@ pub type Ed25519PrivKey = [u8; 32]; #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PubKey { Ed25519PubKey(Ed25519PubKey), + X25519PubKey(X25519PubKey), } impl PubKey { pub fn slice(&self) -> &[u8; 32] { match self { - PubKey::Ed25519PubKey(o) => o, + PubKey::Ed25519PubKey(o) | PubKey::X25519PubKey(o) => o, } } + pub fn to_dh_from_ed(&self) -> PubKey { + match self { + PubKey::Ed25519PubKey(ed) => dh_pubkey_from_ed_slice(ed), + _ => panic!( + "cannot 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) + } } impl fmt::Display for PubKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - PubKey::Ed25519PubKey(d) => write!(f, "{}", base64_url::encode(d)), + PubKey::Ed25519PubKey(d) | PubKey::X25519PubKey(d) => { + write!(f, "{}", base64_url::encode(d)) + } } } } +impl TryFrom<&str> for PubKey { + type Error = NgError; + fn try_from(str: &str) -> Result { + let key = decode_key(str).map_err(|_| NgError::InvalidKey)?; + Ok(PubKey::Ed25519PubKey(key)) + } +} + /// Private key #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub enum PrivKey { @@ -102,6 +126,30 @@ impl PrivKey { } } +impl From<[u8; 32]> for PrivKey { + fn from(buf: [u8; 32]) -> Self { + let priv_key = PrivKey::Ed25519PrivKey(buf); + priv_key + } +} + +impl TryFrom<&[u8]> for PrivKey { + type Error = NgError; + fn try_from(buf: &[u8]) -> Result { + let priv_key_array = *slice_as_array!(buf, [u8; 32]).ok_or(NgError::InvalidKey)?; + let priv_key = PrivKey::Ed25519PrivKey(priv_key_array); + Ok(priv_key) + } +} + +impl fmt::Display for PrivKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let priv_key_ser = serde_bare::to_vec(self).unwrap(); + let prix_key_encoded = base64_url::encode(&priv_key_ser); + write!(f, "{}", prix_key_encoded) + } +} + /// Ed25519 signature pub type Ed25519Sig = [[u8; 32]; 2]; diff --git a/p2p-repo/src/utils.rs b/p2p-repo/src/utils.rs index bafe879..ffcef32 100644 --- a/p2p-repo/src/utils.rs +++ b/p2p-repo/src/utils.rs @@ -10,13 +10,22 @@ // according to those terms. use crate::errors::*; +use crate::log::*; use crate::types::*; +use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use ed25519_dalek::*; use futures::channel::mpsc; use rand::rngs::OsRng; use web_time::{SystemTime, UNIX_EPOCH}; +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]) + .ok_or(()) + .map_err(|_| log_err!("key has invalid content array"))?) +} + pub fn generate_null_keypair() -> (PrivKey, PubKey) { let master_key: [u8; 32] = [0; 32]; let sk = SecretKey::from_bytes(&master_key).unwrap(); @@ -44,22 +53,13 @@ pub fn generate_null_keypair() -> (PrivKey, PubKey) { (priv_key, pub_key) } -pub fn keypair_from_ed(secret: SecretKey, public: PublicKey) -> (PrivKey, PubKey) { - // 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 = 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); - (priv_key, pub_key) +pub fn dh_pubkey_from_ed_slice(public: &[u8]) -> 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(); + PubKey::X25519PubKey(mon_point.to_bytes()) } pub fn sign( @@ -69,6 +69,7 @@ pub fn sign( ) -> 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 sig_bytes = keypair.sign(content.as_slice()).to_bytes(); @@ -82,6 +83,7 @@ pub fn sign( pub fn verify(content: &Vec, sig: Sig, pub_key: PubKey) -> Result<(), NgError> { let pubkey = match pub_key { PubKey::Ed25519PubKey(pk) => pk, + _ => panic!("cannot verify with Montgomery keys"), }; let pk = PublicKey::from_bytes(&pubkey)?; let sig_bytes = match sig {