diff --git a/flake.lock b/flake.lock index 2920402..ce2e37c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -16,12 +19,15 @@ } }, "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, "locked": { - "lastModified": 1659877975, - "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=", + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -32,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1677624842, - "narHash": "sha256-4DF9DbDuK4/+KYx0L6XcPBeDHUFVCtzok2fWtwXtb5w=", + "lastModified": 1683478192, + "narHash": "sha256-7f7RR71w0jRABDgBwjq3vE1yY3nrVJyXk8hDzu5kl1E=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d70f5cd5c3bef45f7f52698f39e7cc7a89daa7f0", + "rev": "c568239bcc990050b7aedadb7387832440ad8fb1", "type": "github" }, "original": { @@ -48,11 +54,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1677407201, - "narHash": "sha256-3blwdI9o1BAprkvlByHvtEm5HAIRn/XPjtcfiunpY7s=", + "lastModified": 1683408522, + "narHash": "sha256-9kcPh6Uxo17a3kK3XCHhcWiV1Yu1kYj22RHiymUhMkU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "7f5639fa3b68054ca0b062866dc62b22c3f11505", + "rev": "897876e4c484f1e8f92009fd11b7d988a121a4e7", "type": "github" }, "original": { @@ -64,11 +70,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1665296151, - "narHash": "sha256-uOB0oxqxN9K7XGF1hcnY+PQnlQJ+3bP2vCn/+Ru/bbc=", + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "14ccaaedd95a488dd7ae142757884d8e125b3363", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", "type": "github" }, "original": { @@ -92,11 +98,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1677638104, - "narHash": "sha256-vbdOoDYnQ1QYSchMb3fYGCLYeta3XwmGvMrlXchST5s=", + "lastModified": 1683512408, + "narHash": "sha256-QMJGp/37En+d5YocJuSU89GL14bBYkIJQ6mqhRfqkkc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "f388187efb41ce4195b2f4de0b6bb463d3cd0a76", + "rev": "75b07756c3feb22cf230e75fb064c1b4c725b9bc", "type": "github" }, "original": { @@ -104,6 +110,36 @@ "repo": "rust-overlay", "type": "github" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 89bfdb9..37e97ab 100644 --- a/flake.nix +++ b/flake.nix @@ -15,6 +15,9 @@ pkgs = import nixpkgs { inherit system overlays; }; + pkgs-unstable = import nixpkgs-unstable { + inherit system overlays; + }; in { devShells.default = pkgs.mkShell { @@ -36,7 +39,7 @@ cargo-watch cargo-license lld - rust-bin.stable.latest.default + rust-bin.stable."1.69.0".default rustfmt rust-analyzer ]; diff --git a/src/connection/handshake/dirsync.rs b/src/connection/handshake/dirsync.rs index 9c0a299..902e4bd 100644 --- a/src/connection/handshake/dirsync.rs +++ b/src/connection/handshake/dirsync.rs @@ -63,6 +63,7 @@ pub struct Req { pub exchange_key: ExchangePubKey, /// encrypted data pub data: ReqInner, + // Security: Add padding to min: 1200 bytes to avoid amplification attaks } impl Req { @@ -79,7 +80,7 @@ impl Req { + self.data.len() } /// Serialize into raw bytes - /// NOTE: assumes that there is exactly asa much buffer as needed + /// NOTE: assumes that there is exactly as much buffer as needed pub fn serialize(&self, out: &mut [u8]) { //assert!(out.len() > , ": not enough buffer to serialize"); todo!() @@ -280,35 +281,94 @@ impl ReqData { } } +/// Quick way to avoid mixing cipher and clear text +#[derive(Debug, Clone)] +pub enum RespInner { + /// Server data, still in ciphertext + CipherText(VecDeque), + /// Server data, decrypted but unprocessed + ClearText(VecDeque), + /// Parsed server data + Data(RespData), +} +impl RespInner { + /// The length of the data + pub fn len(&self) -> usize { + match self { + RespInner::CipherText(c) => c.len(), + RespInner::ClearText(c) => c.len(), + RespInner::Data(d) => RespData::len(), + } + } + /// Get the ciptertext, or panic + pub fn ciphertext<'a>(&'a mut self) -> &'a mut VecDeque { + match self { + RespInner::CipherText(data) => data, + _ => panic!(), + } + } + /// switch from ciphertext to cleartext + pub fn mark_as_cleartext(&mut self) { + let mut newdata: VecDeque; + match self { + RespInner::CipherText(data) => { + newdata = VecDeque::new(); + ::core::mem::swap(&mut newdata, data); + } + _ => return, + } + *self = RespInner::ClearText(newdata); + } + /// serialize, but only if ciphertext + pub fn serialize(&self, out: &mut [u8]) { + todo!() + } +} + /// Server response in a directory synchronized handshake #[derive(Debug, Clone)] pub struct Resp { /// Tells the client with which key the exchange was done pub client_key_id: KeyID, - /// encrypted data - pub enc: Vec, + /// actual response data, might be encrypted + pub data: RespInner, } impl super::HandshakeParsing for Resp { fn deserialize(raw: &[u8]) -> Result { - todo!() + const MIN_PKT_LEN: usize = 68; + if raw.len() < MIN_PKT_LEN { + return Err(Error::NotEnoughData); + } + let client_key_id: KeyID = + KeyID(u16::from_le_bytes(raw[0..1].try_into().unwrap())); + Ok(HandshakeData::DirSync(DirSync::Resp(Self { + client_key_id, + data: RespInner::CipherText(raw[KeyID::len()..].to_vec().into()), + }))) } } impl Resp { /// Total length of the response handshake pub fn len(&self) -> usize { - KeyID::len() + self.enc.len() + KeyID::len() + self.data.len() } /// Serialize into raw bytes - /// NOTE: assumes that there is exactly asa much buffer as needed + /// NOTE: assumes that there is exactly as much buffer as needed + /// NOTE: assumes that the data is encrypted pub fn serialize(&self, out: &mut [u8]) { assert!( - out.len() == KeyID::len() + self.enc.len(), + out.len() == KeyID::len() + self.data.len(), "DirSync Resp: not enough buffer to serialize" ); self.client_key_id.serialize(array_mut_ref![out, 0, 2]); - out[2..].copy_from_slice(&self.enc[..]); + let end_data = 2 + self.data.len(); + self.data.serialize(&mut out[2..end_data]); + } + /// Set the cleartext data after it was parsed + pub fn set_data(&mut self, data: RespData) { + self.data = RespInner::Data(data); } } @@ -348,4 +408,8 @@ impl RespData { end = end + Self::NONCE_LEN; out[start..end].copy_from_slice(self.service_key.as_ref()); } + /// Parse the cleartext raw data + pub fn deserialize(raw: &RespInner) -> Result { + todo!(); + } } diff --git a/src/connection/handshake/mod.rs b/src/connection/handshake/mod.rs index 763191d..6e91c28 100644 --- a/src/connection/handshake/mod.rs +++ b/src/connection/handshake/mod.rs @@ -24,11 +24,19 @@ pub enum Error { NotEnoughData, } -pub(crate) struct HandshakeKey { +pub(crate) struct HandshakeServer { pub id: crate::enc::asym::KeyID, pub key: crate::enc::asym::PrivKey, } +#[derive(Clone)] +pub(crate) struct HandshakeClient { + pub id: crate::enc::asym::KeyID, + pub key: crate::enc::asym::PrivKey, + pub hkdf: crate::enc::hkdf::HkdfSha3, + pub cipher: crate::enc::sym::CipherKind, +} + /// Parsed handshake #[derive(Debug, Clone)] pub enum HandshakeData { @@ -117,14 +125,14 @@ impl Handshake { }) } /// serialize the handshake into bytes - /// NOTE: assumes that there is exactly asa much buffer as needed + /// NOTE: assumes that there is exactly as much buffer as needed pub fn serialize(&self, out: &mut [u8]) { assert!(out.len() > 1, "Handshake: not enough buffer to serialize"); self.fenrir_version.serialize(&mut out[0]); self.data.serialize(&mut out[1..]); } - pub(crate) fn work(&self, keys: &[HandshakeKey]) -> Result<(), Error> { + pub(crate) fn work(&self, keys: &[HandshakeServer]) -> Result<(), Error> { todo!() } } diff --git a/src/lib.rs b/src/lib.rs index c9543fb..b6965e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ use crate::enc::{ }; pub use config::Config; use connection::{ - handshake::{self, Handshake, HandshakeKey}, + handshake::{self, Handshake, HandshakeClient, HandshakeServer}, Connection, }; @@ -79,7 +79,10 @@ pub enum HandshakeAction { struct FenrirInner { key_exchanges: ArcSwapAny>>, ciphers: ArcSwapAny>>, - keys: ArcSwapAny>>, + /// ephemeral keys used server side in key exchange + keys_srv: ArcSwapAny>>, + /// ephemeral keys used client side in key exchange + hshake_cli: ArcSwapAny>>, } #[allow(unsafe_code)] @@ -102,8 +105,8 @@ impl FenrirInner { DirSync::Req(ref mut req) => { let ephemeral_key = { // Keep this block short to avoid contention - // on self.keys - let keys = self.keys.load(); + // on self.keys_srv + let keys = self.keys_srv.load(); if let Some(h_k) = keys.iter().find(|k| k.id == req.key_id) { @@ -120,7 +123,10 @@ impl FenrirInner { } }; if ephemeral_key.is_none() { - ::tracing::debug!("No such key id: {:?}", req.key_id); + ::tracing::debug!( + "No such server key id: {:?}", + req.key_id + ); return Err(handshake::Error::UnknownKeyID.into()); } let ephemeral_key = ephemeral_key.unwrap(); @@ -170,6 +176,40 @@ impl FenrirInner { })); } DirSync::Resp(resp) => { + let hshake = { + // Keep this block short to avoid contention + // on self.hshake_cli + let hshake_cli_lock = self.hshake_cli.load(); + match hshake_cli_lock + .iter() + .find(|h| h.id == resp.client_key_id) + { + Some(h) => Some(h.clone()), + None => None, + } + }; + if hshake.is_none() { + ::tracing::debug!( + "No such client key id: {:?}", + resp.client_key_id + ); + return Err(handshake::Error::UnknownKeyID.into()); + } + let hshake = hshake.unwrap(); + let secret_recv = hshake.hkdf.get_secret(b"to_client"); + let cipher_recv = + CipherRecv::new(hshake.cipher, secret_recv); + use crate::enc::sym::AAD; + let aad = AAD(&mut []); // no aad for now + match cipher_recv.decrypt(aad, &mut resp.data.ciphertext()) + { + Ok(()) => resp.data.mark_as_cleartext(), + Err(e) => { + return Err(handshake::Error::Key(e).into()); + } + } + resp.set_data(dirsync::RespData::deserialize(&resp.data)?); + todo!(); } }, @@ -391,7 +431,8 @@ impl Fenrir { _inner: Arc::new(FenrirInner { ciphers: ArcSwapAny::new(Arc::new(Vec::new())), key_exchanges: ArcSwapAny::new(Arc::new(Vec::new())), - keys: ArcSwapAny::new(Arc::new(Vec::new())), + keys_srv: ArcSwapAny::new(Arc::new(Vec::new())), + hshake_cli: ArcSwapAny::new(Arc::new(Vec::new())), }), token_check: Arc::new(ArcSwapOption::from(None)), work_send: Arc::new(work_send), @@ -692,9 +733,12 @@ impl Fenrir { ::tracing::error!("can't encrypt: {:?}", e); return; } + use dirsync::RespInner; let resp = dirsync::Resp { client_key_id: req_data.client_key_id, - enc: data.get_raw(), + data: RespInner::CipherText( + data.get_raw().into(), + ), }; let resp_handshake = Handshake::new( HandshakeData::DirSync(DirSync::Resp(resp)), @@ -710,6 +754,9 @@ impl Fenrir { self.send_packet(raw_out, udp.src, udp.dst) .await; } + DirSync::Resp(resp) => { + todo!() + } _ => { todo!() }