From 553526299cf535a21b9b555baee84be5379e3e90 Mon Sep 17 00:00:00 2001 From: Dennis Schwerdel Date: Tue, 24 Nov 2015 20:55:14 +0100 Subject: [PATCH] Changed encryption a little --- src/cloud.rs | 2 +- src/crypto.rs | 92 ---------------------- src/{no_crypto.rs => crypto/dummy.rs} | 18 +++-- src/crypto/mod.rs | 5 ++ src/crypto/sodium.rs | 106 ++++++++++++++++++++++++++ src/main.rs | 10 +-- src/udpmessage.rs | 70 ++++++++--------- 7 files changed, 161 insertions(+), 142 deletions(-) delete mode 100644 src/crypto.rs rename src/{no_crypto.rs => crypto/dummy.rs} (54%) create mode 100644 src/crypto/mod.rs create mode 100644 src/crypto/sodium.rs diff --git a/src/cloud.rs b/src/cloud.rs index 95c60e9..ec80718 100644 --- a/src/cloud.rs +++ b/src/cloud.rs @@ -14,7 +14,7 @@ use super::types::{Table, Protocol, Range, Error, NetworkId}; use super::device::Device; use super::udpmessage::{encode, decode, Options, Message}; use super::{ethernet, ip}; -use super::Crypto; +use super::crypto::Crypto; struct PeerList { timeout: Duration, diff --git a/src/crypto.rs b/src/crypto.rs deleted file mode 100644 index c1a057d..0000000 --- a/src/crypto.rs +++ /dev/null @@ -1,92 +0,0 @@ -use sodiumoxide::crypto::stream::chacha20::{Key as CryptoKey, Nonce, stream_xor_inplace, gen_nonce, - KEYBYTES}; -use sodiumoxide::crypto::auth::hmacsha512256::{Key as AuthKey, Tag, authenticate, verify}; -use sodiumoxide::crypto::pwhash::{derive_key, SALTBYTES, Salt, HASHEDPASSWORDBYTES, - OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE}; - -use super::types::Error; - -pub enum Crypto { - None, - ChaCha20HmacSha512256{key: Vec, nonce: Vec} -} - -fn inc_nonce(nonce: &mut [u8]) { - let len = nonce.len(); - for i in 1..len+1 { - let mut val = nonce[len-i]; - val = val.wrapping_add(1); - nonce[len-i] = val; - if val != 0 { - break; - } - } -} - -impl Crypto { - pub fn is_secure(&self) -> bool { - match self { - &Crypto::None => false, - _ => true - } - } - - pub fn from_shared_key(password: &str) -> Self { - let salt = "vpncloudVPNCLOUDvpncl0udVpnCloud"; - assert_eq!(salt.len(), SALTBYTES); - let mut key = [0; HASHEDPASSWORDBYTES]; - derive_key(&mut key, password.as_bytes(), &Salt::from_slice(salt.as_bytes()).unwrap(), - OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE).unwrap(); - let key = key[..KEYBYTES].iter().map(|b| *b).collect(); - let nonce = gen_nonce().0.iter().map(|b| *b).collect(); - Crypto::ChaCha20HmacSha512256{key: key, nonce: nonce} - } - - pub fn decrypt(&self, mut buf: &mut [u8], nonce: &[u8], hash: &[u8]) -> Result<(), Error> { - match self { - &Crypto::None => Ok(()), - &Crypto::ChaCha20HmacSha512256{ref key, nonce: _} => { - let crypto_key = CryptoKey::from_slice(key).unwrap(); - let nonce = Nonce::from_slice(nonce).unwrap(); - let auth_key = AuthKey::from_slice(key).unwrap(); - let hash = Tag::from_slice(hash).unwrap(); - stream_xor_inplace(&mut buf, &nonce, &crypto_key); - match verify(&hash, &buf, &auth_key) { - true => Ok(()), - false => Err(Error::CryptoError("Decryption failed")) - } - } - } - } - - pub fn encrypt(&mut self, mut buf: &mut [u8]) -> (Vec, Vec) { - match self { - &mut Crypto::None => (Vec::new(), Vec::new()), - &mut Crypto::ChaCha20HmacSha512256{ref key, ref mut nonce} => { - let crypto_key = CryptoKey::from_slice(key).unwrap(); - let auth_key = AuthKey::from_slice(key).unwrap(); - inc_nonce(nonce); - let hash = authenticate(&buf, &auth_key); - stream_xor_inplace(&mut buf, &Nonce::from_slice(&nonce).unwrap(), &crypto_key); - (nonce.clone(), hash.0.iter().map(|v| *v).collect()) - } - } - } -} - -#[test] -fn encrypt_decrypt() { - let mut sender = Crypto::from_shared_key("test"); - let receiver = Crypto::from_shared_key("test"); - let msg = "HelloWorld0123456789"; - let mut buffer: Vec = msg.bytes().collect(); - let (nonce1, hash1) = sender.encrypt(&mut buffer); - assert!(msg.as_bytes() != &buffer as &[u8]); - receiver.decrypt(&mut buffer, &nonce1, &hash1).unwrap(); - assert_eq!(msg.as_bytes(), &buffer as &[u8]); - let (nonce2, hash2) = sender.encrypt(&mut buffer); - assert!(nonce1 != nonce2); - assert!(hash1 == hash2); - receiver.decrypt(&mut buffer, &nonce2, &hash2).unwrap(); - assert_eq!(msg.as_bytes(), &buffer as &[u8]); -} diff --git a/src/no_crypto.rs b/src/crypto/dummy.rs similarity index 54% rename from src/no_crypto.rs rename to src/crypto/dummy.rs index fc1123f..8fbf5b2 100644 --- a/src/no_crypto.rs +++ b/src/crypto/dummy.rs @@ -1,12 +1,20 @@ -use super::types::Error; +use super::super::types::Error; pub enum Crypto { None } impl Crypto { - pub fn is_secure(&self) -> bool { - false + pub fn method(&self) -> u8 { + 0 + } + + pub fn nonce_bytes(&self) -> usize { + 0 + } + + pub fn auth_bytes(&self) -> usize { + 0 } pub fn from_shared_key(_password: &str) -> Self { @@ -14,10 +22,10 @@ impl Crypto { } pub fn decrypt(&self, mut _buf: &mut [u8], _nonce: &[u8], _hash: &[u8]) -> Result<(), Error> { - Ok(()) + unreachable!("This should never be called") } pub fn encrypt(&mut self, mut _buf: &mut [u8]) -> (Vec, Vec) { - (Vec::new(), Vec::new()) + unreachable!("This should never be called") } } diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..3fd8c8c --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "crypto")] mod sodium; +#[cfg(not(feature = "crypto"))] mod dummy; + +#[cfg(feature = "crypto")] pub use self::sodium::Crypto; +#[cfg(not(feature = "crypto"))] pub use self::dummy::Crypto; diff --git a/src/crypto/sodium.rs b/src/crypto/sodium.rs new file mode 100644 index 0000000..9af3dd5 --- /dev/null +++ b/src/crypto/sodium.rs @@ -0,0 +1,106 @@ +use std::mem; + +use sodiumoxide::crypto::stream::chacha20::{Key as CryptoKey, Nonce, stream_xor_inplace, gen_nonce, + KEYBYTES, NONCEBYTES}; +use sodiumoxide::crypto::auth::hmacsha512256::{Key as AuthKey, Tag, authenticate, verify, TAGBYTES}; +use sodiumoxide::crypto::pwhash::{derive_key, SALTBYTES, Salt, HASHEDPASSWORDBYTES, + OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE}; + +use super::super::types::Error; + +pub enum Crypto { + None, + ChaCha20HmacSha512256{crypto_key: CryptoKey, auth_key: AuthKey, nonce: Nonce} +} + +fn inc_nonce(nonce: Nonce) -> Nonce { + unsafe { + let mut num: u64 = mem::transmute(nonce); + num = num.wrapping_add(1); + mem::transmute(num) + } +} + +impl Crypto { + pub fn method(&self) -> u8 { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20HmacSha512256{crypto_key: _, auth_key: _, nonce: _} => 1 + } + } + + pub fn nonce_bytes(&self) -> usize { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20HmacSha512256{crypto_key: _, auth_key: _, nonce: _} => NONCEBYTES + } + } + + pub fn auth_bytes(&self) -> usize { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20HmacSha512256{crypto_key: _, auth_key: _, nonce: _} => TAGBYTES + } + } + + pub fn from_shared_key(password: &str) -> Self { + let salt = "vpncloudVPNCLOUDvpncl0udVpnCloud"; + assert_eq!(salt.len(), SALTBYTES); + let mut key = [0; HASHEDPASSWORDBYTES]; + derive_key(&mut key, password.as_bytes(), &Salt::from_slice(salt.as_bytes()).unwrap(), + OPSLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE).unwrap(); + let mut crypto_key = CryptoKey([0; KEYBYTES]); + let mut auth_key = AuthKey([0; KEYBYTES]); + for i in 0..KEYBYTES { + crypto_key.0[i] = key[i]; + } + for i in 0..KEYBYTES { + auth_key.0[i] = key[KEYBYTES+i]; + } + Crypto::ChaCha20HmacSha512256{crypto_key: crypto_key, auth_key: auth_key, nonce: gen_nonce()} + } + + pub fn decrypt(&self, mut buf: &mut [u8], nonce: &[u8], hash: &[u8]) -> Result<(), Error> { + match self { + &Crypto::None => unreachable!("This should never be called"), + &Crypto::ChaCha20HmacSha512256{ref crypto_key, ref auth_key, nonce: _} => { + let nonce = Nonce::from_slice(nonce).unwrap(); + let hash = Tag::from_slice(hash).unwrap(); + stream_xor_inplace(&mut buf, &nonce, crypto_key); + match verify(&hash, &buf, auth_key) { + true => Ok(()), + false => Err(Error::CryptoError("Decryption failed")) + } + } + } + } + + pub fn encrypt(&mut self, mut buf: &mut [u8]) -> (Vec, Vec) { + match self { + &mut Crypto::None => unreachable!("This should never be called"), + &mut Crypto::ChaCha20HmacSha512256{ref crypto_key, ref auth_key, ref mut nonce} => { + *nonce = inc_nonce(*nonce); + let hash = authenticate(&buf, auth_key); + stream_xor_inplace(&mut buf, nonce, crypto_key); + (nonce.0.iter().map(|v| *v).collect(), hash.0.iter().map(|v| *v).collect()) + } + } + } +} + +#[test] +fn encrypt_decrypt() { + let mut sender = Crypto::from_shared_key("test"); + let receiver = Crypto::from_shared_key("test"); + let msg = "HelloWorld0123456789"; + let mut buffer: Vec = msg.bytes().collect(); + let (nonce1, hash1) = sender.encrypt(&mut buffer); + assert!(msg.as_bytes() != &buffer as &[u8]); + receiver.decrypt(&mut buffer, &nonce1, &hash1).unwrap(); + assert_eq!(msg.as_bytes(), &buffer as &[u8]); + let (nonce2, hash2) = sender.encrypt(&mut buffer); + assert!(nonce1 != nonce2); + assert!(hash1 == hash2); + receiver.decrypt(&mut buffer, &nonce2, &hash2).unwrap(); + assert_eq!(msg.as_bytes(), &buffer as &[u8]); +} diff --git a/src/main.rs b/src/main.rs index 9b9945e..dd252c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,8 +7,7 @@ extern crate epoll; mod util; mod types; -#[cfg(feature = "crypto")] mod crypto; -#[cfg(not(feature = "crypto"))] mod no_crypto; +mod crypto; mod udpmessage; mod ethernet; mod ip; @@ -28,12 +27,13 @@ use ip::RoutingTable; use types::{Error, Mode, Type, Range, Table}; use cloud::{TapCloud, TunCloud}; use udpmessage::VERSION; -#[cfg(feature = "crypto")] pub use crypto::Crypto; -#[cfg(not(feature = "crypto"))] pub use no_crypto::Crypto; +use crypto::Crypto; //TODO: Implement IPv6 //TODO: Call close - +//FIXME: The crypto method should be signaled as part of the protocol +//FIXME: The HMAC should also include the header +//FIXME: Encryption should also include all following additional headers struct SimpleLogger; diff --git a/src/udpmessage.rs b/src/udpmessage.rs index 1598304..c94fd17 100644 --- a/src/udpmessage.rs +++ b/src/udpmessage.rs @@ -4,7 +4,7 @@ use std::u16; use super::types::{Error, NetworkId, Range, Address}; use super::util::{as_obj, as_bytes, to_vec}; -use super::Crypto; +use super::crypto::Crypto; const MAGIC: [u8; 3] = [0x76, 0x70, 0x6e]; pub const VERSION: u8 = 1; @@ -13,14 +13,15 @@ pub const VERSION: u8 = 1; struct TopHeader { magic: [u8; 3], version: u8, - _reserved: [u8; 2], + crypto_method : u8, + _reserved: u8, flags: u8, msgtype: u8 } impl Default for TopHeader { fn default() -> Self { - TopHeader{magic: MAGIC, version: VERSION, _reserved: [0; 2], flags: 0, msgtype: 0} + TopHeader{magic: MAGIC, version: VERSION, crypto_method: 0, _reserved: 0, flags: 0, msgtype: 0} } } @@ -73,6 +74,21 @@ pub fn decode<'a>(data: &'a mut [u8], crypto: &mut Crypto) -> Result<(Options, M if header.version != VERSION { return Err(Error::ParseError("Wrong version")); } + if header.crypto_method != crypto.method() { + return Err(Error::CryptoError("Wrong crypto method")); + } + if crypto.method() > 0 { + if data.len() < pos + crypto.nonce_bytes() + crypto.auth_bytes() { + return Err(Error::ParseError("Truncated crypto header")); + } + let nonce = &data[pos..pos+crypto.nonce_bytes()]; + pos += crypto.nonce_bytes(); + let hash = &data[pos..pos+crypto.auth_bytes()]; + pos += crypto.auth_bytes(); + // Cheat data mutable to make the borrow checker happy + let data = unsafe { slice::from_raw_parts_mut(mem::transmute(data[pos..].as_ptr()), data.len()-pos) }; + try!(crypto.decrypt(data, nonce, hash)); + } let mut options = Options::default(); if header.flags & 0x01 > 0 { if data.len() < pos + 8 { @@ -82,23 +98,6 @@ pub fn decode<'a>(data: &'a mut [u8], crypto: &mut Crypto) -> Result<(Options, M options.network_id = Some(id); pos += 8; } - if header.flags & 0x02 > 0 { - if data.len() < pos + 40 { - return Err(Error::ParseError("Truncated options")); - } - if !crypto.is_secure() { - return Err(Error::CryptoError("Unexpected encrypted data")); - } - let nonce = &data[pos..pos+8]; - pos += 8; - let hash = &data[pos..pos+32]; - pos += 32; - debug!("{:?}", nonce); - debug!("{:?}", hash); - // Cheat data mutable to make the borrow checker happy - let data = unsafe { slice::from_raw_parts_mut(mem::transmute(data[pos..].as_ptr()), data.len()-pos) }; - try!(crypto.decrypt(data, nonce, hash)); - } let msg = match header.msgtype { 0 => Message::Data(&data[pos..]), 1 => { @@ -169,15 +168,18 @@ pub fn encode(options: &Options, msg: &Message, buf: &mut [u8], crypto: &mut Cry &Message::Init(_) => 2, &Message::Close => 3 }; + header.crypto_method = crypto.method(); if options.network_id.is_some() { header.flags |= 0x01; } - if crypto.is_secure() { - header.flags |= 0x02; - } let header_dat = unsafe { as_bytes(&header) }; unsafe { ptr::copy_nonoverlapping(header_dat.as_ptr(), buf[pos..].as_mut_ptr(), header_dat.len()) }; pos += header_dat.len(); + let nonce_pos = pos; + pos += crypto.nonce_bytes(); + let hash_pos = pos; + pos += crypto.auth_bytes(); + let crypto_pos = pos; if let Some(id) = options.network_id { assert!(buf.len() >= pos + 8); unsafe { @@ -186,16 +188,6 @@ pub fn encode(options: &Options, msg: &Message, buf: &mut [u8], crypto: &mut Cry } pos += 8; } - let (nonce_pos, hash_pos) = if crypto.is_secure() { - let nonce_pos = pos; - pos += 8; - let hash_pos = pos; - pos += 32; - (nonce_pos, hash_pos) - } else { - (0, 0) - }; - let crypto_pos = pos; match msg { &Message::Data(ref data) => { assert!(buf.len() >= pos + data.len()); @@ -249,13 +241,13 @@ pub fn encode(options: &Options, msg: &Message, buf: &mut [u8], crypto: &mut Cry &Message::Close => { } } - if crypto.is_secure() { + if crypto.method() > 0 { let (nonce, hash) = crypto.encrypt(&mut buf[crypto_pos..pos]); - assert_eq!(nonce.len(), 8); - assert_eq!(hash.len(), 32); + assert_eq!(nonce.len(), crypto.nonce_bytes()); + assert_eq!(hash.len(), crypto.auth_bytes()); unsafe { - ptr::copy_nonoverlapping(nonce.as_ptr(), buf[nonce_pos..].as_mut_ptr(), 8); - ptr::copy_nonoverlapping(hash.as_ptr(), buf[hash_pos..].as_mut_ptr(), 32); + ptr::copy_nonoverlapping(nonce.as_ptr(), buf[nonce_pos..].as_mut_ptr(), crypto.nonce_bytes()); + ptr::copy_nonoverlapping(hash.as_ptr(), buf[hash_pos..].as_mut_ptr(), crypto.auth_bytes()); } } pos @@ -287,7 +279,7 @@ fn encode_message_encrypted() { let mut buf = [0; 1024]; let size = encode(&mut options, &msg, &mut buf[..], &mut crypto); assert_eq!(size, 53); - assert_eq!(&buf[..8], &[118,112,110,1,0,0,2,0]); + assert_eq!(&buf[..8], &[118,112,110,1,1,0,0,0]); let (options2, msg2) = decode(&mut buf[..size], &mut crypto).unwrap(); assert_eq!(options, options2); assert_eq!(msg, msg2);