diff --git a/.travis.yml b/.travis.yml index fa2c79a..2e50072 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,9 +11,10 @@ before_script: - ! ' set -e ; pip install ''travis-cargo<0.2'' --user ; export PATH=$HOME/.local/bin:$PATH ; - wget https://github.com/jedisct1/libsodium/releases/download/1.0.3/libsodium-1.0.3.tar.gz ; - tar xvfz libsodium-1.0.3.tar.gz ; - pushd libsodium-1.0.3 ; + export VERSION=1.0.6 ; + wget https://github.com/jedisct1/libsodium/releases/download/$VERSION/libsodium-$VERSION.tar.gz ; + tar xvfz libsodium-$VERSION.tar.gz ; + pushd libsodium-$VERSION ; ./configure --prefix=/usr ; make ; sudo make install ; @@ -22,9 +23,8 @@ before_script: script: - ! ' set -e ; travis-cargo build ; - travis-cargo build -- --features "crypto" ; - travis-cargo test -- --features "crypto" ; - travis-cargo bench -- --features "crypto" ; + travis-cargo test ; + travis-cargo bench ; travis-cargo coverage ; ' addons: @@ -37,7 +37,7 @@ after_success: - ! ' set -e ; rm -rf target/kcov ; rm target/debug/vpncloud-* ; - cargo test --features "crypto" ; + cargo test ; kcov/build/src/kcov --exclude-pattern=/libsodium/,/x86_64-linux-gnu/,/.cargo --coveralls-id=$TRAVIS_JOB_ID target/kcov target/debug/vpncloud-* ; ' notifications: diff --git a/Cargo.lock b/Cargo.lock index dbeade3..a8c8611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,9 +6,9 @@ dependencies = [ "epoll 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libsodium-sys 0.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "signal 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", @@ -93,15 +93,6 @@ name = "libc" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "libsodium-sys" -version = "0.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "log" version = "0.3.4" diff --git a/Cargo.toml b/Cargo.toml index f7648d7..d9e8639 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ repository = "https://github.com/dswd/vpncloud.rs" keywords = ["vpn", "p2p", "tun", "tap", "network"] readme = "README.md" - [dependencies] time = "0.1" docopt = "0.6" @@ -20,12 +19,11 @@ epoll = "0.2" signal = "0.1" nix = "0.4" libc = "0.2" -libsodium-sys = {version = "0.0.9", optional = true} [build-dependencies] gcc = "0.3" +pkg-config = "0.3.6" [features] default = [] -crypto = ["libsodium-sys"] #not default as it requires external library bench = [] diff --git a/build.rs b/build.rs index d342a35..4aed808 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,7 @@ extern crate gcc; +extern crate pkg_config; fn main() { + pkg_config::find_library("libsodium").unwrap(); gcc::Config::new().file("src/c/tuntap.c").include("src").compile("libtuntap.a"); } diff --git a/deb/Makefile b/deb/Makefile index 0fd8711..909d1b5 100644 --- a/deb/Makefile +++ b/deb/Makefile @@ -5,14 +5,11 @@ DEPENDENCIES=debhelper devscripts default: clean build .PHONY: build -build: $(PACKAGE)_*.deb $(PACKAGE)-nocrypto_*.deb +build: $(PACKAGE)_*.deb $(PACKAGE)_*.deb: (cd $(PACKAGE); make clean; debuild -b -us -uc; cd ..) -$(PACKAGE)-nocrypto_*.deb: - (cd $(PACKAGE)-nocrypto; make clean; debuild -b -us -uc; cd ..) .PHONY: clean clean: (cd $(PACKAGE); debuild clean; cd ..) - (cd $(PACKAGE)-nocrypto; debuild clean; cd ..) - rm -rf $(PACKAGE)_* $(PACKAGE)-nocrypto_* + rm -rf $(PACKAGE)_* diff --git a/deb/vpncloud-nocrypto/Makefile b/deb/vpncloud-nocrypto/Makefile deleted file mode 100644 index 1d35745..0000000 --- a/deb/vpncloud-nocrypto/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -build: vpncloud.1 vpncloud - -vpncloud.1: vpncloud.1.ronn - ronn -r vpncloud.1.ronn - -vpncloud.1.ronn: ../../vpncloud.md - cp ../../vpncloud.md vpncloud.1.ronn - -clean: - rm -f vpncloud.1* vpncloud ../../target/release/vpncloud - -vpncloud: ../../target/release/vpncloud - cp ../../target/release/vpncloud vpncloud - -../../target/release/vpncloud: ../../src/*.rs ../../Cargo.toml ../../src/usage.txt ../../src/c/* - (cd ../..; cargo build --release) - -install: - install -d $(DESTDIR)/usr/bin - install -m 755 vpncloud $(DESTDIR)/usr/bin/vpncloud diff --git a/deb/vpncloud-nocrypto/debian/changelog b/deb/vpncloud-nocrypto/debian/changelog deleted file mode 100644 index 9763dfc..0000000 --- a/deb/vpncloud-nocrypto/debian/changelog +++ /dev/null @@ -1,11 +0,0 @@ -vpncloud-nocrypto (0.2.0) stable; urgency=medium - - * More stable release - - -- Dennis Schwerdel Thu, 26 Nov 2015 17:41:40 +0100 - -vpncloud-nocrypto (0.1.0) stable; urgency=medium - - * Initial release - - -- Dennis Schwerdel Tue, 24 Nov 2015 09:31:47 +0100 diff --git a/deb/vpncloud-nocrypto/debian/compat b/deb/vpncloud-nocrypto/debian/compat deleted file mode 100644 index 7f8f011..0000000 --- a/deb/vpncloud-nocrypto/debian/compat +++ /dev/null @@ -1 +0,0 @@ -7 diff --git a/deb/vpncloud-nocrypto/debian/control b/deb/vpncloud-nocrypto/debian/control deleted file mode 100644 index f5a09e7..0000000 --- a/deb/vpncloud-nocrypto/debian/control +++ /dev/null @@ -1,12 +0,0 @@ -Source: vpncloud-nocrypto -Section: misc -Priority: extra -Maintainer: Dennis Schwerdel -Build-Depends: debhelper (>= 7), ruby-ronn, rust-stable -Standards-Version: 3.8.3 - -Package: vpncloud-nocrypto -Architecture: amd64 -Depends: ${shlibs:Depends}, ${misc:Depends} -Conflicts: vpncloud -Description: Peer-to-peer VPN (built without crypto support) diff --git a/deb/vpncloud-nocrypto/debian/copyright b/deb/vpncloud-nocrypto/debian/copyright deleted file mode 100644 index 0ea8a2f..0000000 --- a/deb/vpncloud-nocrypto/debian/copyright +++ /dev/null @@ -1,31 +0,0 @@ -Upstream Author: - - Dennis Schwerdel - -Copyright: - - Copyright (C) 2015 Dennis Schwerdel, University of Kaiserslautern - -License: - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -On Debian systems, the complete text of the GNU General -Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". - -The Debian packaging is: - - Copyright (C) 2015 Dennis Schwerdel, University of Kaiserslautern - -and is licensed under the GPL version 3, see above. diff --git a/deb/vpncloud-nocrypto/debian/dbackup.substvars b/deb/vpncloud-nocrypto/debian/dbackup.substvars deleted file mode 100644 index abd3ebe..0000000 --- a/deb/vpncloud-nocrypto/debian/dbackup.substvars +++ /dev/null @@ -1 +0,0 @@ -misc:Depends= diff --git a/deb/vpncloud-nocrypto/debian/manpages b/deb/vpncloud-nocrypto/debian/manpages deleted file mode 100644 index 6a85aae..0000000 --- a/deb/vpncloud-nocrypto/debian/manpages +++ /dev/null @@ -1 +0,0 @@ -vpncloud.1 diff --git a/deb/vpncloud-nocrypto/debian/rules b/deb/vpncloud-nocrypto/debian/rules deleted file mode 100755 index cbe925d..0000000 --- a/deb/vpncloud-nocrypto/debian/rules +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/make -f -%: - dh $@ diff --git a/deb/vpncloud/Makefile b/deb/vpncloud/Makefile index b0b77ee..1d35745 100644 --- a/deb/vpncloud/Makefile +++ b/deb/vpncloud/Makefile @@ -13,7 +13,7 @@ vpncloud: ../../target/release/vpncloud cp ../../target/release/vpncloud vpncloud ../../target/release/vpncloud: ../../src/*.rs ../../Cargo.toml ../../src/usage.txt ../../src/c/* - (cd ../..; cargo build --release --features "crypto") + (cd ../..; cargo build --release) install: install -d $(DESTDIR)/usr/bin diff --git a/deb/vpncloud/debian/control b/deb/vpncloud/debian/control index df0dede..79ac618 100644 --- a/deb/vpncloud/debian/control +++ b/deb/vpncloud/debian/control @@ -2,11 +2,10 @@ Source: vpncloud Section: misc Priority: extra Maintainer: Dennis Schwerdel -Build-Depends: debhelper (>= 7), ruby-ronn, rust-stable, libsodium-dev +Build-Depends: debhelper (>= 7), ruby-ronn, rust-stable Standards-Version: 3.8.3 Package: vpncloud Architecture: amd64 -Depends: ${shlibs:Depends}, ${misc:Depends}, libsodium13 -Conflicts: vpncloud-nocrypto +Depends: ${shlibs:Depends}, ${misc:Depends} Description: Peer-to-peer VPN diff --git a/performance.md b/performance.md index 5ad82be..993e9ec 100644 --- a/performance.md +++ b/performance.md @@ -52,11 +52,12 @@ The test is run in 3 steps: * Encrypted throughput via VpnCloud (`DST` is `10.2.1.2`) -| Throughput test | Bandwidth | CPU usage (one core) | -| -------------------- | ------------- | -------------------- | -| Without VpnCloud | 926 Mbits/sec | - | -| Unencrypted VpnCloud | 873 Mbits/sec | 80% / 95% | -| Encrypted VpnCloud | 770 Mbits/sec | 100% | +| Throughput test | Bandwidth | CPU usage (one core) | +| ----------------------------- | ------------- | -------------------- | +| Without VpnCloud | 926 Mbits/sec | - | +| Unencrypted VpnCloud | 873 Mbits/sec | 80% / 95% | +| Encrypted VpnCloud (ChaCha20) | 770 Mbits/sec | 100% | +| Encrypted VpnCloud (AES256) | 813 Mbits/sec | 90% / 100% | ### Latency @@ -70,13 +71,15 @@ For all the test, the best average RTT out of 5 runs is selected. The latency is assumed to be half of the RTT. -| Payload size | 100 bytes | 500 bytes | 1000 bytes | -| -------------------- | --------- | --------- | ---------- | -| Without VpnCloud | 158 µs | 165 µs | 178 µs | -| Unencrypted VpnCloud | 210 µs | 216 µs | 237 µs | -| Difference | +52 µs | +51 µs | +59 µs | -| Encrypted VpnCloud | 218 µs | 230 µs | 257 µs | -| Difference | +8 µs | +14 µs | +20 µs | +| Payload size | 100 bytes | 500 bytes | 1000 bytes | +| ----------------------------- | --------- | --------- | ---------- | +| Without VpnCloud | 158 µs | 165 µs | 178 µs | +| Unencrypted VpnCloud | 210 µs | 216 µs | 237 µs | +| Difference | +52 µs | +51 µs | +59 µs | +| Encrypted VpnCloud (ChaCha20) | 218 µs | 230 µs | 257 µs | +| Difference | +8 µs | +14 µs | +20 µs | +| Encrypted VpnCloud (AES256) | 224 µs | 230 µs | 255 µs | +| Difference | +14 µs | +14 µs | +18 µs | ### Conclusion diff --git a/src/crypto.rs b/src/crypto.rs new file mode 100644 index 0000000..4139837 --- /dev/null +++ b/src/crypto.rs @@ -0,0 +1,293 @@ +use std::{mem, ptr}; +use std::ffi::CStr; + +use libc::{size_t, c_char, c_ulonglong, c_int}; + +use super::types::Error; + +#[allow(non_upper_case_globals)] +const crypto_aead_chacha20poly1305_KEYBYTES: usize = 32; +#[allow(non_upper_case_globals)] +const crypto_aead_chacha20poly1305_NSECBYTES: usize = 0; +#[allow(non_upper_case_globals)] +const crypto_aead_chacha20poly1305_NPUBBYTES: usize = 8; +#[allow(non_upper_case_globals)] +const crypto_aead_chacha20poly1305_ABYTES: usize = 16; + +#[allow(non_upper_case_globals)] +const crypto_aead_aes256gcm_KEYBYTES: usize = 32; +#[allow(non_upper_case_globals)] +const crypto_aead_aes256gcm_NSECBYTES: usize = 0; +#[allow(non_upper_case_globals)] +const crypto_aead_aes256gcm_NPUBBYTES: usize = 12; +#[allow(non_upper_case_globals)] +const crypto_aead_aes256gcm_ABYTES: usize = 16; + +#[allow(non_upper_case_globals)] +const crypto_pwhash_scryptsalsa208sha256_SALTBYTES: usize = 32; +#[allow(non_upper_case_globals)] +const crypto_pwhash_scryptsalsa208sha256_STRBYTES: usize = 102; +#[allow(non_upper_case_globals)] +const crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE: usize = 524288; +#[allow(non_upper_case_globals)] +const crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE: usize = 16777216; + +#[link(name="sodium", kind="static")] +extern { + pub fn sodium_init() -> c_int; + pub fn randombytes_buf(buf: *mut u8, size: size_t); + pub fn sodium_version_string() -> *const c_char; + pub fn crypto_pwhash_scryptsalsa208sha256( + out: *mut u8, + outlen: c_ulonglong, + passwd: *const u8, + passwdlen: c_ulonglong, + salt: *const [u8; crypto_pwhash_scryptsalsa208sha256_SALTBYTES], + opslimit: c_ulonglong, + memlimit: size_t) -> c_int; + pub fn crypto_aead_chacha20poly1305_encrypt( + c: *mut u8, + clen: *mut c_ulonglong, + m: *const u8, + mlen: c_ulonglong, + ad: *const u8, + adlen: c_ulonglong, + nsec: *const [u8; crypto_aead_chacha20poly1305_NSECBYTES], + npub: *const [u8; crypto_aead_chacha20poly1305_NPUBBYTES], + k: *const [u8; crypto_aead_chacha20poly1305_KEYBYTES]) -> c_int; + pub fn crypto_aead_chacha20poly1305_decrypt( + m: *mut u8, + mlen: *mut c_ulonglong, + nsec: *mut [u8; crypto_aead_chacha20poly1305_NSECBYTES], + c: *const u8, + clen: c_ulonglong, + ad: *const u8, + adlen: c_ulonglong, + npub: *const [u8; crypto_aead_chacha20poly1305_NPUBBYTES], + k: *const [u8; crypto_aead_chacha20poly1305_KEYBYTES]) -> c_int; + pub fn crypto_aead_aes256gcm_encrypt( + c: *mut u8, + clen: *mut c_ulonglong, + m: *const u8, + mlen: c_ulonglong, + ad: *const u8, + adlen: c_ulonglong, + nsec: *const [u8; crypto_aead_aes256gcm_NSECBYTES], + npub: *const [u8; crypto_aead_aes256gcm_NPUBBYTES], + k: *const [u8; crypto_aead_aes256gcm_KEYBYTES]) -> c_int; + pub fn crypto_aead_aes256gcm_decrypt( + m: *mut u8, + mlen: *mut c_ulonglong, + nsec: *mut [u8; crypto_aead_aes256gcm_NSECBYTES], + c: *const u8, + clen: c_ulonglong, + ad: *const u8, + adlen: c_ulonglong, + npub: *const [u8; crypto_aead_aes256gcm_NPUBBYTES], + k: *const [u8; crypto_aead_aes256gcm_KEYBYTES]) -> c_int; +} + + +#[derive(RustcDecodable, Debug)] +pub enum CryptoMethod { + ChaCha20, AES256 +} + +pub enum Crypto { + None, + ChaCha20Poly1305{key: [u8; 32], nonce: [u8; 8]}, + AES256GCM{key: [u8; 32], nonce: [u8; 12]} +} + +fn inc_nonce_8(nonce: &mut [u8; 8]) { + unsafe { + let num = mem::transmute::<&mut [u8; 8], &mut u64>(nonce); + *num = num.wrapping_add(1) + } +} + +fn inc_nonce_12(nonce: &mut [u8; 12]) { + for i in 0..12 { + let mut num = nonce[11-i]; + num = num.wrapping_add(1); + nonce[11-i] = num; + if num > 0 { + break + } + } +} + + +impl Crypto { + pub fn init() { + unsafe { sodium_init() }; + } + + pub fn sodium_version() -> String { + unsafe { + CStr::from_ptr(sodium_version_string()).to_string_lossy().to_string() + } + } + + pub fn method(&self) -> u8 { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20Poly1305{key: _, nonce: _} => 1, + &Crypto::AES256GCM{key: _, nonce: _} => 2 + } + } + + pub fn nonce_bytes(&self) -> usize { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20Poly1305{key: _, ref nonce} => nonce.len(), + &Crypto::AES256GCM{key: _, ref nonce} => nonce.len() + } + } + + pub fn additional_bytes(&self) -> usize { + match self { + &Crypto::None => 0, + &Crypto::ChaCha20Poly1305{key: _, nonce: _} => crypto_aead_chacha20poly1305_ABYTES, + &Crypto::AES256GCM{key: _, nonce: _} => crypto_aead_aes256gcm_ABYTES + } + } + + pub fn from_shared_key(method: CryptoMethod, password: &str) -> Self { + let salt = "vpncloudVPNCLOUDvpncl0udVpnCloud".as_bytes(); + assert_eq!(salt.len(), crypto_pwhash_scryptsalsa208sha256_SALTBYTES); + let mut key = [0; crypto_pwhash_scryptsalsa208sha256_STRBYTES]; + let res = unsafe { crypto_pwhash_scryptsalsa208sha256( + key.as_mut_ptr(), + key.len() as u64, + password.as_bytes().as_ptr(), + password.as_bytes().len() as u64, + salt.as_ptr() as *const [u8; crypto_pwhash_scryptsalsa208sha256_SALTBYTES], + crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE as u64, + crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE + ) }; + if res != 0 { + panic!("Key derivation failed"); + } + match method { + CryptoMethod::ChaCha20 => { + let mut crypto_key = [0; crypto_aead_chacha20poly1305_KEYBYTES]; + for i in 0..crypto_key.len() { + crypto_key[i] = key[i]; + } + let mut nonce = [0u8; crypto_aead_chacha20poly1305_NPUBBYTES]; + unsafe { randombytes_buf(nonce.as_mut_ptr(), nonce.len()) }; + Crypto::ChaCha20Poly1305{key: crypto_key, nonce: nonce} + }, + CryptoMethod::AES256 => { + let mut crypto_key = [0; crypto_aead_aes256gcm_KEYBYTES]; + for i in 0..crypto_key.len() { + crypto_key[i] = key[i]; + } + let mut nonce = [0u8; crypto_aead_aes256gcm_NPUBBYTES]; + unsafe { randombytes_buf(nonce.as_mut_ptr(), nonce.len()) }; + Crypto::AES256GCM{key: crypto_key, nonce: nonce} + } + } + } + + pub fn decrypt(&self, mut buf: &mut [u8], nonce: &[u8], header: &[u8]) -> Result { + match self { + &Crypto::None => Ok(buf.len()), + &Crypto::ChaCha20Poly1305{ref key, nonce: _} => { + let mut mlen: u64 = buf.len() as u64; + let res = unsafe { crypto_aead_chacha20poly1305_decrypt( + buf.as_mut_ptr(), // Base pointer to buffer + &mut mlen, // Mutable size of buffer (will be set to used size) + ptr::null_mut::<[u8; 0]>(), // Mutable base pointer to secret nonce (always NULL) + buf.as_ptr(), // Base pointer to message + buf.len() as u64, // Size of message + header.as_ptr(), // Base pointer to additional data + header.len() as u64, // Size of additional data + nonce.as_ptr() as *const [u8; 8], // Base pointer to public nonce + key.as_ptr() as *const [u8; 32] // Base pointer to key + ) }; + match res { + 0 => Ok(mlen as usize), + _ => Err(Error::CryptoError("Failed to decrypt")) + } + }, + &Crypto::AES256GCM{ref key, nonce: _} => { + let mut mlen: u64 = buf.len() as u64; + let res = unsafe { crypto_aead_aes256gcm_decrypt( + buf.as_mut_ptr(), // Base pointer to buffer + &mut mlen, // Mutable size of buffer (will be set to used size) + ptr::null_mut::<[u8; 0]>(), // Mutable base pointer to secret nonce (always NULL) + buf.as_ptr(), // Base pointer to message + buf.len() as u64, // Size of message + header.as_ptr(), // Base pointer to additional data + header.len() as u64, // Size of additional data + nonce.as_ptr() as *const [u8; 12], // Base pointer to public nonce + key.as_ptr() as *const [u8; 32] // Base pointer to key + ) }; + match res { + 0 => Ok(mlen as usize), + _ => Err(Error::CryptoError("Failed to decrypt")) + } + } + } + } + + pub fn encrypt(&mut self, mut buf: &mut [u8], mlen: usize, nonce_bytes: &mut [u8], header: &[u8]) -> usize { + match self { + &mut Crypto::None => mlen, + &mut Crypto::ChaCha20Poly1305{ref key, ref mut nonce} => { + inc_nonce_8(nonce); + let mut clen: u64 = buf.len() as u64; + assert_eq!(nonce_bytes.len(), nonce.len()); + assert_eq!(nonce.len(), crypto_aead_chacha20poly1305_NPUBBYTES); + assert_eq!(key.len(), crypto_aead_chacha20poly1305_KEYBYTES); + assert_eq!(0, crypto_aead_chacha20poly1305_NSECBYTES); + assert!(clen as usize >= mlen + crypto_aead_chacha20poly1305_ABYTES); + let res = unsafe { crypto_aead_chacha20poly1305_encrypt( + buf.as_mut_ptr(), // Base pointer to buffer + &mut clen, // Mutable size of buffer (will be set to used size) + buf.as_ptr(), // Base pointer to message + mlen as u64, // Size of message + header.as_ptr(), // Base pointer to additional data + header.len() as u64, // Size of additional data + ptr::null::<[u8; 0]>(), // Base pointer to secret nonce (always NULL) + nonce.as_ptr() as *const [u8; 8], // Base pointer to public nonce + key.as_ptr() as *const [u8; 32] // Base pointer to key + ) }; + assert_eq!(res, 0); + assert_eq!(clen as usize, mlen + crypto_aead_chacha20poly1305_ABYTES); + unsafe { + ptr::copy_nonoverlapping(nonce.as_ptr(), nonce_bytes.as_mut_ptr(), nonce.len()); + } + clen as usize + }, + &mut Crypto::AES256GCM{ref key, ref mut nonce} => { + inc_nonce_12(nonce); + let mut clen: u64 = buf.len() as u64; + assert_eq!(nonce_bytes.len(), nonce.len()); + assert_eq!(nonce.len(), crypto_aead_aes256gcm_NPUBBYTES); + assert_eq!(key.len(), crypto_aead_aes256gcm_KEYBYTES); + assert_eq!(0, crypto_aead_aes256gcm_NSECBYTES); + assert!(clen as usize >= mlen + crypto_aead_aes256gcm_ABYTES); + let res = unsafe { crypto_aead_aes256gcm_encrypt( + buf.as_mut_ptr(), // Base pointer to buffer + &mut clen, // Mutable size of buffer (will be set to used size) + buf.as_ptr(), // Base pointer to message + mlen as u64, // Size of message + header.as_ptr(), // Base pointer to additional data + header.len() as u64, // Size of additional data + ptr::null::<[u8; 0]>(), // Base pointer to secret nonce (always NULL) + nonce.as_ptr() as *const [u8; 12], // Base pointer to public nonce + key.as_ptr() as *const [u8; 32] // Base pointer to key + ) }; + assert_eq!(res, 0); + assert_eq!(clen as usize, mlen + crypto_aead_aes256gcm_ABYTES); + unsafe { + ptr::copy_nonoverlapping(nonce.as_ptr(), nonce_bytes.as_mut_ptr(), nonce.len()); + } + clen as usize + } + } + } +} diff --git a/src/crypto/dummy.rs b/src/crypto/dummy.rs deleted file mode 100644 index 9f232c1..0000000 --- a/src/crypto/dummy.rs +++ /dev/null @@ -1,35 +0,0 @@ -use super::super::types::Error; - -pub enum Crypto { - None -} - -impl Crypto { - pub fn init() { - - } - - pub fn method(&self) -> u8 { - 0 - } - - pub fn nonce_bytes(&self) -> usize { - 0 - } - - pub fn additional_bytes(&self) -> usize { - 0 - } - - pub fn from_shared_key(_password: &str) -> Self { - panic!("This binary has no crypto support"); - } - - pub fn decrypt(&self, mut _buf: &mut [u8], _nonce: &[u8], _hash: &[u8]) -> Result { - unreachable!("This should never be called") - } - - pub fn encrypt(&mut self, mut _buf: &mut [u8], _mlen: usize, _nonce_bytes: &mut [u8], _header: &[u8]) -> usize { - unreachable!("This should never be called") - } -} diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs deleted file mode 100644 index 3fd8c8c..0000000 --- a/src/crypto/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[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 deleted file mode 100644 index 1f15558..0000000 --- a/src/crypto/sodium.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::{mem, ptr}; - -use libsodium_sys::*; - -use super::super::types::Error; - -pub enum Crypto { - None, - ChaCha20Poly1305{key: [u8; 32], nonce: [u8; 8]} -} - -fn inc_nonce(nonce: [u8; 8]) -> [u8; 8] { - unsafe { - let mut num: u64 = mem::transmute(nonce); - num = num.wrapping_add(1); - mem::transmute(num) - } -} - -impl Crypto { - pub fn init() { - unsafe { sodium_init() }; - } - - pub fn method(&self) -> u8 { - match self { - &Crypto::None => 0, - &Crypto::ChaCha20Poly1305{key: _, nonce: _} => 1 - } - } - - pub fn nonce_bytes(&self) -> usize { - match self { - &Crypto::None => 0, - &Crypto::ChaCha20Poly1305{key: _, ref nonce} => nonce.len() - } - } - - pub fn additional_bytes(&self) -> usize { - match self { - &Crypto::None => 0, - &Crypto::ChaCha20Poly1305{key: _, nonce: _} => crypto_aead_chacha20poly1305_ABYTES - } - } - - pub fn from_shared_key(password: &str) -> Self { - - let salt = "vpncloudVPNCLOUDvpncl0udVpnCloud".as_bytes(); - assert_eq!(salt.len(), crypto_pwhash_scryptsalsa208sha256_SALTBYTES); - let mut key = [0; crypto_pwhash_scryptsalsa208sha256_STRBYTES]; - let res = unsafe { crypto_pwhash_scryptsalsa208sha256( - key.as_mut_ptr(), - key.len() as u64, - password.as_bytes().as_ptr(), - password.as_bytes().len() as u64, - salt.as_ptr() as *const [u8; crypto_pwhash_scryptsalsa208sha256_SALTBYTES], - crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE as u64, - crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE - ) }; - if res != 0 { - panic!("Key derivation failed"); - } - let mut crypto_key = [0; 32]; - for i in 0..crypto_key.len() { - crypto_key[i] = key[i]; - } - let mut nonce = [0u8; 8]; - unsafe { randombytes_buf(nonce.as_mut_ptr(), nonce.len()) }; - Crypto::ChaCha20Poly1305{key: crypto_key, nonce: nonce} - } - - pub fn decrypt(&self, mut buf: &mut [u8], nonce: &[u8], header: &[u8]) -> Result { - match self { - &Crypto::None => Ok(buf.len()), - &Crypto::ChaCha20Poly1305{ref key, nonce: _} => { - let mut mlen: u64 = buf.len() as u64; - let res = unsafe { crypto_aead_chacha20poly1305_decrypt( - buf.as_mut_ptr(), // Base pointer to buffer - &mut mlen, // Mutable size of buffer (will be set to used size) - ptr::null_mut::<[u8; 0]>(), // Mutable base pointer to secret nonce (always NULL) - buf.as_ptr(), // Base pointer to message - buf.len() as u64, // Size of message - header.as_ptr(), // Base pointer to additional data - header.len() as u64, // Size of additional data - nonce.as_ptr() as *const [u8; 8], // Base pointer to public nonce - key.as_ptr() as *const [u8; 32] // Base pointer to key - ) }; - match res { - 0 => Ok(mlen as usize), - _ => Err(Error::CryptoError("Failed to decrypt")) - } - } - } - } - - pub fn encrypt(&mut self, mut buf: &mut [u8], mlen: usize, nonce_bytes: &mut [u8], header: &[u8]) -> usize { - match self { - &mut Crypto::None => mlen, - &mut Crypto::ChaCha20Poly1305{ref key, ref mut nonce} => { - *nonce = inc_nonce(*nonce); - let mut clen: u64 = buf.len() as u64; - assert_eq!(nonce_bytes.len(), nonce.len()); - assert_eq!(nonce.len(), crypto_aead_chacha20poly1305_NPUBBYTES); - assert_eq!(key.len(), crypto_aead_chacha20poly1305_KEYBYTES); - assert_eq!(0, crypto_aead_chacha20poly1305_NSECBYTES); - assert!(clen as usize >= mlen + crypto_aead_chacha20poly1305_ABYTES); - let res = unsafe { crypto_aead_chacha20poly1305_encrypt( - buf.as_mut_ptr(), // Base pointer to buffer - &mut clen, // Mutable size of buffer (will be set to used size) - buf.as_ptr(), // Base pointer to message - mlen as u64, // Size of message - header.as_ptr(), // Base pointer to additional data - header.len() as u64, // Size of additional data - ptr::null::<[u8; 0]>(), // Base pointer to secret nonce (always NULL) - nonce.as_ptr() as *const [u8; 8], // Base pointer to public nonce - key.as_ptr() as *const [u8; 32] // Base pointer to key - ) }; - assert_eq!(res, 0); - assert_eq!(clen as usize, mlen + crypto_aead_chacha20poly1305_ABYTES); - unsafe { - ptr::copy_nonoverlapping(nonce.as_ptr(), nonce_bytes.as_mut_ptr(), nonce.len()); - } - clen as usize - } - } - } -} - -#[test] -fn encrypt_decrypt() { - let mut sender = Crypto::from_shared_key("test"); - let receiver = Crypto::from_shared_key("test"); - let msg = "HelloWorld0123456789"; - let msg_bytes = msg.as_bytes(); - let mut buffer = [0u8; 1024]; - let header = [0u8; 8]; - for i in 0..msg_bytes.len() { - buffer[i] = msg_bytes[i]; - } - let mut nonce1 = [0u8; 8]; - let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce1, &header); - assert_eq!(size, msg_bytes.len() + sender.additional_bytes()); - assert!(msg_bytes != &buffer[..msg_bytes.len()] as &[u8]); - receiver.decrypt(&mut buffer[..size], &nonce1, &header).unwrap(); - assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); - let mut nonce2 = [0u8; 8]; - let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce2, &header); - assert!(nonce1 != nonce2); - receiver.decrypt(&mut buffer[..size], &nonce2, &header).unwrap(); - assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); -} diff --git a/src/main.rs b/src/main.rs index faba535..215909a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ extern crate epoll; extern crate signal; extern crate nix; extern crate libc; -#[cfg(feature = "crypto")] extern crate libsodium_sys; #[cfg(feature = "bench")] extern crate test; #[macro_use] mod util; @@ -33,7 +32,7 @@ use ip::RoutingTable; use types::{Error, Mode, Type, Range, Table, Protocol}; use cloud::GenericCloud; use udpmessage::VERSION; -use crypto::Crypto; +use crypto::{Crypto, CryptoMethod}; use util::Duration; @@ -60,6 +59,7 @@ struct Args { flag_type: Type, flag_mode: Mode, flag_shared_key: Option, + flag_crypto: CryptoMethod, flag_subnet: Vec, flag_device: String, flag_listen: String, @@ -113,7 +113,7 @@ fn run (args: Args) { }); Crypto::init(); let crypto = match args.flag_shared_key { - Some(key) => Crypto::from_shared_key(&key), + Some(key) => Crypto::from_shared_key(args.flag_crypto, &key), None => Crypto::None }; let mut cloud = GenericCloud::::new(device, args.flag_listen, network_id, table, peer_timeout, learning, broadcasting, ranges, crypto); @@ -132,9 +132,8 @@ fn run (args: Args) { fn main() { let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit()); if args.flag_version { - println!("VpnCloud v{} ({}, protocol version {})", env!("CARGO_PKG_VERSION"), - if cfg!(feature = "crypto") { "with crypto support" } else { "without crypto support" }, - VERSION + println!("VpnCloud v{} (protocol version {}, libsodium {})", env!("CARGO_PKG_VERSION"), + VERSION, Crypto::sodium_version() ); return; } diff --git a/src/tests.rs b/src/tests.rs index 8ade9dd..8b0bda8 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,7 +5,7 @@ use super::ethernet::{Frame, SwitchTable}; use super::ip::{RoutingTable, Packet}; use super::types::{Protocol, Address, Range, Table}; use super::udpmessage::{Options, Message, decode, encode}; -use super::crypto::Crypto; +use super::crypto::{Crypto, CryptoMethod}; #[test] @@ -23,11 +23,10 @@ fn udpmessage_packet() { assert_eq!(msg, msg2); } -#[cfg(feature = "crypto")] #[test] fn udpmessage_encrypted() { let mut options = Options::default(); - let mut crypto = Crypto::from_shared_key("test"); + let mut crypto = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test"); let payload = [1,2,3,4,5]; let msg = Message::Data(&payload); let mut buf = [0; 1024]; @@ -122,11 +121,9 @@ fn udpmessage_invalid() { assert!(decode(&mut [0x76,0x70,0x6e,1,0,0,1,0], &mut crypto).is_err()); } -#[cfg(feature = "crypto")] #[test] fn udpmessage_invalid_crypto() { - let mut options = Options::default(); - let mut crypto = Crypto::from_shared_key("test"); + let mut crypto = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test"); // truncated crypto assert!(decode(&mut [0x76,0x70,0x6e,1,1,0,0,0], &mut crypto).is_err()); } @@ -252,3 +249,51 @@ fn message_fmt() { ])), "Init(stage=0, [0.1.2.3/24, 00:01:02:03:04:05/16])"); assert_eq!(format!("{:?}", Message::Close), "Close"); } + +#[test] +fn encrypt_decrypt_chacha20poly1305() { + let mut sender = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test"); + let receiver = Crypto::from_shared_key(CryptoMethod::ChaCha20, "test"); + let msg = "HelloWorld0123456789"; + let msg_bytes = msg.as_bytes(); + let mut buffer = [0u8; 1024]; + let header = [0u8; 8]; + for i in 0..msg_bytes.len() { + buffer[i] = msg_bytes[i]; + } + let mut nonce1 = [0u8; 8]; + let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce1, &header); + assert_eq!(size, msg_bytes.len() + sender.additional_bytes()); + assert!(msg_bytes != &buffer[..msg_bytes.len()] as &[u8]); + receiver.decrypt(&mut buffer[..size], &nonce1, &header).unwrap(); + assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); + let mut nonce2 = [0u8; 8]; + let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce2, &header); + assert!(nonce1 != nonce2); + receiver.decrypt(&mut buffer[..size], &nonce2, &header).unwrap(); + assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); +} + +#[test] +fn encrypt_decrypt_aes256() { + let mut sender = Crypto::from_shared_key(CryptoMethod::AES256, "test"); + let receiver = Crypto::from_shared_key(CryptoMethod::AES256, "test"); + let msg = "HelloWorld0123456789"; + let msg_bytes = msg.as_bytes(); + let mut buffer = [0u8; 1024]; + let header = [0u8; 8]; + for i in 0..msg_bytes.len() { + buffer[i] = msg_bytes[i]; + } + let mut nonce1 = [0u8; 8]; + let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce1, &header); + assert_eq!(size, msg_bytes.len() + sender.additional_bytes()); + assert!(msg_bytes != &buffer[..msg_bytes.len()] as &[u8]); + receiver.decrypt(&mut buffer[..size], &nonce1, &header).unwrap(); + assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); + let mut nonce2 = [0u8; 8]; + let size = sender.encrypt(&mut buffer, msg_bytes.len(), &mut nonce2, &header); + assert!(nonce1 != nonce2); + receiver.decrypt(&mut buffer[..size], &nonce2, &header).unwrap(); + assert_eq!(msg_bytes, &buffer[..msg_bytes.len()] as &[u8]); +} diff --git a/src/usage.txt b/src/usage.txt index 115eb34..8a17e49 100644 --- a/src/usage.txt +++ b/src/usage.txt @@ -14,6 +14,8 @@ Options: --subnet The local subnets to use. --network-id Optional token that identifies the network. --shared-key The shared key to encrypt all traffic. + --crypto The encryption method to use ("aes256", or + "chacha20"). [default: aes256] --peer-timeout Peer timeout in seconds. [default: 1800] --dst-timeout Switch table entry timeout in seconds. [default: 300] diff --git a/vpncloud.md b/vpncloud.md index 3dfb5fc..662eb77 100644 --- a/vpncloud.md +++ b/vpncloud.md @@ -49,6 +49,13 @@ vpncloud(1) -- Peer-to-peer VPN An optional shared key to encrypt the VPN data. If this option is not set, the traffic will be sent unencrypted. + * `--crypto `: + + The encryption method to use ("aes256", or "chacha20"). Most current CPUs + have special support for AES256 so this should be faster. For older + computers lacking this support, CHACHA20 should be faster. + [default: `aes256`] + * `--network-id `: An optional token that identifies the network and helps to distinguish it @@ -233,6 +240,11 @@ Every packet sent over UDP contains the following header (in order): `libsodium::crypto_aead_chacha20poly1305` method, using the 8 byte header as additional data. + - Method `2`, **AES256GCM**: The header is followed by a 12 byte *nonce*. + The rest of the data is encrypted with the + `libsodium::crypto_aead_aes256gcm` method, using the 8 byte header + as additional data. + * 1 `reserved byte` that is currently unused * 1 byte for `flags`