vpncloud/src/crypto/core.rs

454 lines
16 KiB
Rust

// VpnCloud - Peer-to-Peer VPN
// Copyright (C) 2015-2021 Dennis Schwerdel
// This software is licensed under GPL-3 or newer (see LICENSE.md)
// This module implements a crypto core for encrypting and decrypting message streams
//
// The crypto core only encrypts and decrypts messages, using given keys. Negotiating and rotating the keys is out of
// scope of the crypto core. The crypto core assumes that the remote node will always have the necessary key to decrypt
// the message.
//
// The crypto core encrypts messages in place, writes some extra data (key id and nonce) into a given space and
// includes the given header data in the authentication tag. When decrypting messages, the crypto core reads the extra
// data, uses the key id to find the right key to decrypting the message and then decrypts the message, using the given
// nonce and including the given header data in the verification of the authentication tag.
//
// While the core only uses a single key at a time for encrypting messages, it is ready to decrypt messages based on
// one of 4 stored keys (the encryption key being one of them). An external key rotation is responsible for adding the
// key to the remote peer before switching to the key on the local peer for encryption.
//
// As mentioned, the encryption and decryption works in place. Therefore the parameter payload_and_tag contains (when
// decrypting) or provides space for (when encrypting) the payload and the authentication tag. When encrypting, that
// means, that the last TAG_LEN bytes of payload_and_tag must be reserved for the tag and must not contain payload
// bytes.
//
// The nonce is a value of 12 bytes (192 bits). Since both nodes can use the same key for encryption, the most
// significant byte (msb) of the nonce is initialized differently on both peers: one peer uses the value 0x00 and the
// other one 0x80. That means that the nonce space is essentially divided in two halves, one for each node.
//
// To save space and keep the encrypted data aligned to 64 bits, not all bytes of the nonce are transferred. Instead,
// only 7 bytes are included in messages (another byte is used for the key id, hence 64 bit alignment). The rest of the
// nonce is deduced by the nodes: All other bytes are assumed to be 0x00, except for the most significant byte, which
// is assumed to be the opposite ones own msb. This has two nice effects:
// 1) Long before the nonce could theoretically repeat, the messages can no longer be decrypted by the peer as the
// higher bytes are no longer zero as assumed.
// 2) By deducing the msb to be the opposite of ones own msb, it is no longer possible for an attacker to redirect a
// message back to the sender because then the assumed nonce will be wrong and the message fails to decrypt. Otherwise,
// this could lead to problems as nodes would be able to accidentally decrypt their own messages.
//
// In order to be resistent against replay attacks but allow for reordering of messages, the crypto core uses nonce
// pinning. For every active key, the biggest nonce seen so far is being tracked. Every second, the biggest nonce seen
// one second ago plus 1 becomes the minimum nonce that is accepted for that key. That means, that reordering can
// happen within one second but after a second, old messages will not be accepted anymore.
use byteorder::{ReadBytesExt, WriteBytesExt};
use ring::{
aead::{self, LessSafeKey, UnboundKey},
rand::{SecureRandom, SystemRandom},
};
use std::{
io::{Cursor, Read, Write},
mem,
time::{Duration, Instant},
};
use crate::{error::Error, util::MsgBuffer};
const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
pub const EXTRA_LEN: usize = 8;
fn random_data(size: usize) -> Vec<u8> {
let rand = SystemRandom::new();
let mut data = vec![0; size];
rand.fill(&mut data).expect("Failed to obtain random bytes");
data
}
#[derive(PartialOrd, Ord, PartialEq, Debug, Eq, Clone)]
struct Nonce([u8; NONCE_LEN]);
impl Nonce {
fn zero() -> Self {
Nonce([0; NONCE_LEN])
}
fn random(rand: &SystemRandom) -> Self {
let mut nonce = Nonce::zero();
rand.fill(&mut nonce.0[6..]).expect("Failed to obtain random bytes");
nonce
}
fn set_msb(&mut self, val: u8) {
self.0[0] = val
}
fn as_bytes(&self) -> &[u8; NONCE_LEN] {
&self.0
}
fn increment(&mut self) {
for i in (0..NONCE_LEN).rev() {
let mut num = self.0[i];
num = num.wrapping_add(1);
self.0[i] = num;
if num > 0 {
return;
}
}
}
}
struct CryptoKey {
key: LessSafeKey,
send_nonce: Nonce,
min_nonce: Nonce,
next_min_nonce: Nonce,
seen_nonce: Nonce,
}
impl CryptoKey {
fn new(rand: &SystemRandom, key: LessSafeKey, nonce_half: bool) -> Self {
let mut send_nonce = Nonce::random(rand);
send_nonce.set_msb(if nonce_half { 0x80 } else { 0x00 });
CryptoKey {
key,
send_nonce,
min_nonce: Nonce::zero(),
next_min_nonce: Nonce::zero(),
seen_nonce: Nonce::zero(),
}
}
fn update_min_nonce(&mut self) {
mem::swap(&mut self.min_nonce, &mut self.next_min_nonce);
self.next_min_nonce = self.seen_nonce.clone();
self.next_min_nonce.increment();
}
}
pub struct CryptoCore {
rand: SystemRandom,
keys: [CryptoKey; 4],
current_key: usize,
nonce_half: bool,
}
impl CryptoCore {
pub fn new(key: LessSafeKey, nonce_half: bool) -> Self {
let rand = SystemRandom::new();
let dummy_key_data = random_data(key.algorithm().key_len());
let dummy_key1 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
let dummy_key2 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
let dummy_key3 = LessSafeKey::new(UnboundKey::new(key.algorithm(), &dummy_key_data).unwrap());
Self {
keys: [
CryptoKey::new(&rand, key, nonce_half),
CryptoKey::new(&rand, dummy_key1, nonce_half),
CryptoKey::new(&rand, dummy_key2, nonce_half),
CryptoKey::new(&rand, dummy_key3, nonce_half),
],
current_key: 0,
nonce_half,
rand,
}
}
pub fn encrypt(&mut self, buffer: &mut MsgBuffer) {
let data_start = buffer.get_start();
let data_length = buffer.len();
assert!(buffer.get_start() >= EXTRA_LEN);
buffer.set_start(data_start - EXTRA_LEN);
buffer.set_length(data_length + EXTRA_LEN + TAG_LEN);
let (extra, data_and_tag) = buffer.message_mut().split_at_mut(EXTRA_LEN);
let (data, tag_space) = data_and_tag.split_at_mut(data_length);
let key = &mut self.keys[self.current_key];
key.send_nonce.increment();
{
let mut extra = Cursor::new(extra);
extra.write_u8(self.current_key as u8).unwrap();
extra.write_all(&key.send_nonce.as_bytes()[5..]).unwrap();
}
let nonce = aead::Nonce::assume_unique_for_key(*key.send_nonce.as_bytes());
let tag = key.key.seal_in_place_separate_tag(nonce, aead::Aad::empty(), data).expect("Failed to encrypt");
tag_space.clone_from_slice(tag.as_ref());
}
fn decrypt_with_key(key: &mut CryptoKey, nonce: Nonce, data_and_tag: &mut [u8]) -> Result<(), Error> {
if nonce < key.min_nonce {
return Err(Error::Crypto("Old nonce rejected"));
}
// decrypt
let crypto_nonce = aead::Nonce::assume_unique_for_key(*nonce.as_bytes());
key.key
.open_in_place(crypto_nonce, aead::Aad::empty(), data_and_tag)
.map_err(|_| Error::Crypto("Failed to decrypt data"))?;
// last seen nonce
if key.seen_nonce < nonce {
key.seen_nonce = nonce;
}
Ok(())
}
pub fn decrypt(&mut self, buffer: &mut MsgBuffer) -> Result<(), Error> {
assert!(buffer.len() >= EXTRA_LEN + TAG_LEN);
let (extra, data_and_tag) = buffer.message_mut().split_at_mut(EXTRA_LEN);
let key_id;
let mut nonce;
{
let mut extra = Cursor::new(extra);
key_id = extra.read_u8().map_err(|_| Error::Crypto("Input data too short"))? % 4;
nonce = Nonce::zero();
extra.read_exact(&mut nonce.0[5..]).map_err(|_| Error::Crypto("Input data too short"))?;
nonce.set_msb(if self.nonce_half { 0x00 } else { 0x80 });
}
let key = &mut self.keys[key_id as usize];
let result = Self::decrypt_with_key(key, nonce, data_and_tag);
buffer.set_start(buffer.get_start() + EXTRA_LEN);
buffer.set_length(buffer.len() - TAG_LEN);
result
}
pub fn rotate_key(&mut self, key: LessSafeKey, id: u64, use_for_sending: bool) {
debug!("Rotated key {} (use for sending: {})", id, use_for_sending);
let id = (id % 4) as usize;
self.keys[id] = CryptoKey::new(&self.rand, key, self.nonce_half);
if use_for_sending {
self.current_key = id
}
}
pub fn algorithm(&self) -> &'static aead::Algorithm {
self.keys[self.current_key].key.algorithm()
}
pub fn every_second(&mut self) {
// Set min nonce on all keys
for k in &mut self.keys {
k.update_min_nonce();
}
}
}
pub fn create_dummy_pair(algo: &'static aead::Algorithm) -> (CryptoCore, CryptoCore) {
let key_data = random_data(algo.key_len());
let sender = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), true);
let receiver = CryptoCore::new(LessSafeKey::new(UnboundKey::new(algo, &key_data).unwrap()), false);
(sender, receiver)
}
pub fn test_speed(algo: &'static aead::Algorithm, max_time: &Duration) -> f64 {
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.set_length(1000);
let (mut sender, mut receiver) = create_dummy_pair(algo);
let mut iterations = 0;
let start = Instant::now();
while (Instant::now() - start).as_nanos() < max_time.as_nanos() {
for _ in 0..1000 {
sender.encrypt(&mut buffer);
receiver.decrypt(&mut buffer).unwrap();
}
iterations += 1000;
}
let duration = (Instant::now() - start).as_secs_f64();
let data = iterations * 1000 * 2;
data as f64 / duration / 1_000_000.0
}
#[cfg(test)]
mod tests {
use super::*;
use ring::aead::{self, LessSafeKey, UnboundKey};
#[test]
fn test_nonce() {
let mut nonce = Nonce::zero();
assert_eq!(nonce.as_bytes(), &[0; 12]);
nonce.increment();
assert_eq!(nonce.as_bytes(), &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);
nonce.increment();
assert_eq!(nonce.as_bytes(), &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]);
}
fn test_encrypt_decrypt(algo: &'static aead::Algorithm) {
let (mut sender, mut receiver) = create_dummy_pair(algo);
let plain = random_data(1000);
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.clone_from(&plain);
assert_eq!(&plain[..], buffer.message());
sender.encrypt(&mut buffer);
assert_ne!(&plain[..], buffer.message());
receiver.decrypt(&mut buffer).unwrap();
assert_eq!(&plain[..], buffer.message());
}
#[test]
fn test_encrypt_decrypt_aes128() {
test_encrypt_decrypt(&aead::AES_128_GCM)
}
#[test]
fn test_encrypt_decrypt_aes256() {
test_encrypt_decrypt(&aead::AES_256_GCM)
}
#[test]
fn test_encrypt_decrypt_chacha() {
test_encrypt_decrypt(&aead::CHACHA20_POLY1305)
}
fn test_tampering(algo: &'static aead::Algorithm) {
let (mut sender, mut receiver) = create_dummy_pair(algo);
let plain = random_data(1000);
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.clone_from(&plain);
sender.encrypt(&mut buffer);
let mut d = buffer.clone();
assert!(receiver.decrypt(&mut d,).is_ok());
// Tamper with extra data byte 1 (subkey id)
d = buffer.clone();
d.message_mut()[0] ^= 1;
assert!(receiver.decrypt(&mut d).is_err());
// Tamper with extra data byte 2 (nonce)
d = buffer.clone();
d.message_mut()[1] ^= 1;
assert!(receiver.decrypt(&mut d).is_err());
// Tamper with data itself
d = buffer.clone();
d.message_mut()[EXTRA_LEN] ^= 1;
assert!(receiver.decrypt(&mut d).is_err());
// Check everything still works
d = buffer;
assert!(receiver.decrypt(&mut d).is_ok());
}
#[test]
fn test_tampering_aes128() {
test_tampering(&aead::AES_128_GCM)
}
#[test]
fn test_tampering_aes256() {
test_tampering(&aead::AES_256_GCM)
}
#[test]
fn test_tampering_chacha() {
test_tampering(&aead::CHACHA20_POLY1305)
}
fn test_nonce_pinning(algo: &'static aead::Algorithm) {
let (mut sender, mut receiver) = create_dummy_pair(algo);
let plain = random_data(1000);
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.clone_from(&plain);
sender.encrypt(&mut buffer);
{
let mut d = buffer.clone();
assert!(receiver.decrypt(&mut d).is_ok());
}
receiver.every_second();
{
let mut d = buffer.clone();
assert!(receiver.decrypt(&mut d).is_ok());
}
receiver.every_second();
{
let mut d = buffer;
assert!(receiver.decrypt(&mut d).is_err());
}
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.clone_from(&plain);
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_ok());
}
#[test]
fn test_nonce_pinning_aes128() {
test_nonce_pinning(&aead::AES_128_GCM)
}
#[test]
fn test_nonce_pinning_aes256() {
test_nonce_pinning(&aead::AES_256_GCM)
}
#[test]
fn test_nonce_pinning_chacha() {
test_nonce_pinning(&aead::CHACHA20_POLY1305)
}
fn test_key_rotation(algo: &'static aead::Algorithm) {
let (mut sender, mut receiver) = create_dummy_pair(algo);
let plain = random_data(1000);
let mut buffer = MsgBuffer::new(EXTRA_LEN);
buffer.clone_from(&plain);
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_ok());
let new_key = random_data(algo.key_len());
receiver.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 1, false);
receiver.encrypt(&mut buffer);
assert!(sender.decrypt(&mut buffer).is_ok());
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_ok());
sender.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 1, true);
receiver.encrypt(&mut buffer);
assert!(sender.decrypt(&mut buffer).is_ok());
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_ok());
let new_key = random_data(algo.key_len());
sender.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 2, true);
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_err());
receiver.encrypt(&mut buffer);
assert!(sender.decrypt(&mut buffer).is_ok());
receiver.rotate_key(LessSafeKey::new(UnboundKey::new(algo, &new_key).unwrap()), 2, false);
receiver.encrypt(&mut buffer);
assert!(sender.decrypt(&mut buffer).is_ok());
sender.encrypt(&mut buffer);
assert!(receiver.decrypt(&mut buffer).is_ok());
}
#[test]
fn test_key_rotation_aes128() {
test_key_rotation(&aead::AES_128_GCM);
}
#[test]
fn test_key_rotation_aes256() {
test_key_rotation(&aead::AES_256_GCM);
}
#[test]
fn test_key_rotation_chacha() {
test_key_rotation(&aead::CHACHA20_POLY1305);
}
#[test]
fn test_core_size() {
assert_eq!(2384, mem::size_of::<CryptoCore>());
}
#[test]
fn test_speed_aes128() {
let speed = test_speed(&aead::AES_128_GCM, &Duration::from_secs_f32(0.2));
assert!(speed > 10.0);
}
#[test]
fn test_speed_aes256() {
let speed = test_speed(&aead::AES_256_GCM, &Duration::from_secs_f32(0.2));
assert!(speed > 10.0);
}
#[test]
fn test_speed_chacha() {
let speed = test_speed(&aead::CHACHA20_POLY1305, &Duration::from_secs_f32(0.2));
assert!(speed > 10.0);
}
}