From e2684a93de585bf6cce6255f857c0f12cd00db06 Mon Sep 17 00:00:00 2001 From: Luna Yao <40349250+ZnqbuZ@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:42:34 +0100 Subject: [PATCH] refactor: use strum on EncryptionAlgorithm, use Xor as default when AesGcm not available (#1923) --- Cargo.lock | 24 +- easytier/Cargo.toml | 2 + easytier/src/common/config.rs | 104 +++---- easytier/src/core.rs | 13 +- easytier/src/peers/encrypt/aes_gcm.rs | 89 +++--- easytier/src/peers/encrypt/mod.rs | 104 +++---- easytier/src/peers/encrypt/openssl.rs | 201 ++++++++++++ easytier/src/peers/encrypt/openssl_cipher.rs | 288 ------------------ easytier/src/peers/encrypt/ring.rs | 252 +++++++++++++++ easytier/src/peers/encrypt/ring_aes_gcm.rs | 205 ------------- easytier/src/peers/encrypt/ring_chacha20.rs | 170 ----------- .../peers/encrypt/{xor_cipher.rs => xor.rs} | 2 +- easytier/src/peers/peer_session.rs | 22 +- easytier/src/tunnel/packet_def.rs | 22 +- 14 files changed, 642 insertions(+), 856 deletions(-) create mode 100644 easytier/src/peers/encrypt/openssl.rs delete mode 100644 easytier/src/peers/encrypt/openssl_cipher.rs create mode 100644 easytier/src/peers/encrypt/ring.rs delete mode 100644 easytier/src/peers/encrypt/ring_aes_gcm.rs delete mode 100644 easytier/src/peers/encrypt/ring_chacha20.rs rename easytier/src/peers/encrypt/{xor_cipher.rs => xor.rs} (97%) diff --git a/Cargo.lock b/Cargo.lock index acda4b25..74142212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2257,6 +2257,7 @@ dependencies = [ "smoltcp", "snow", "socket2 0.5.10", + "strum 0.27.2", "stun_codec", "sys-locale", "tabled", @@ -7644,7 +7645,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "strum", + "strum 0.26.3", "thiserror 1.0.63", "time", "tracing", @@ -8679,6 +8680,27 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "stun_codec" version = "0.3.5" diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index b20002cb..e5da9038 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -50,6 +50,8 @@ time = "0.3" toml = "0.8.12" chrono = { version = "0.4.37", features = ["serde"] } +strum = { version = "0.27.2", features = ["derive"] } + gethostname = "0.5.0" futures = { version = "0.3", features = ["bilock", "unstable"] } diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index b24187f2..c071bd98 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -7,7 +7,11 @@ use std::{ use anyhow::Context; use base64::{prelude::BASE64_STANDARD, Engine as _}; +use cfg_if::cfg_if; +use clap::builder::PossibleValue; +use clap::ValueEnum; use serde::{Deserialize, Serialize}; +use strum::{Display, EnumString, VariantArray}; use tokio::io::AsyncReadExt as _; use crate::{ @@ -59,7 +63,7 @@ pub fn gen_default_flags() -> Flags { enable_relay_foreign_network_quic: false, foreign_relay_bps_limit: u64::MAX, multi_thread_count: 2, - encryption_algorithm: "aes-gcm".to_string(), + encryption_algorithm: EncryptionAlgorithm::default().to_string(), disable_sym_hole_punching: false, tld_dns_zone: DEFAULT_ET_DNS_ZONE.to_string(), @@ -68,75 +72,53 @@ pub fn gen_default_flags() -> Flags { } } +#[derive(Debug, Clone, PartialEq, Eq, Display, EnumString, VariantArray)] +#[strum(ascii_case_insensitive)] pub enum EncryptionAlgorithm { - AesGcm, - Aes256Gcm, + #[strum(serialize = "xor")] Xor, - #[cfg(feature = "wireguard")] + + #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] + #[strum(serialize = "aes-gcm")] + AesGcm, + #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] + #[strum(serialize = "aes-256-gcm")] + Aes256Gcm, + #[cfg(any(feature = "wireguard", feature = "openssl-crypto"))] + #[strum(serialize = "chacha20")] ChaCha20, - - #[cfg(feature = "openssl-crypto")] - OpensslAesGcm, - #[cfg(feature = "openssl-crypto")] - OpensslChacha20, - #[cfg(feature = "openssl-crypto")] - OpensslAes256Gcm, } -impl std::fmt::Display for EncryptionAlgorithm { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::AesGcm => write!(f, "aes-gcm"), - Self::Aes256Gcm => write!(f, "aes-256-gcm"), - Self::Xor => write!(f, "xor"), - #[cfg(feature = "wireguard")] - Self::ChaCha20 => write!(f, "chacha20"), - #[cfg(feature = "openssl-crypto")] - Self::OpensslAesGcm => write!(f, "openssl-aes-gcm"), - #[cfg(feature = "openssl-crypto")] - Self::OpensslChacha20 => write!(f, "openssl-chacha20"), - #[cfg(feature = "openssl-crypto")] - Self::OpensslAes256Gcm => write!(f, "openssl-aes-256-gcm"), +impl ValueEnum for EncryptionAlgorithm { + fn value_variants<'a>() -> &'a [Self] { + Self::VARIANTS + } + + fn from_str(input: &str, _ignore_case: bool) -> Result { + input + .parse() + .map_err(|_| format!("'{}' is not a valid encryption algorithm", input)) + } + + fn to_possible_value(&self) -> Option { + Some(PossibleValue::new(self.to_string())) + } +} + +#[allow(clippy::derivable_impls)] +impl Default for EncryptionAlgorithm { + fn default() -> Self { + cfg_if! { + if #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] { + EncryptionAlgorithm::AesGcm + } else { + crate::common::log::warn!("no AEAD encryption algorithm is available, using INSECURE XOR"); + EncryptionAlgorithm::Xor + } } } } -impl TryFrom<&str> for EncryptionAlgorithm { - type Error = anyhow::Error; - - fn try_from(value: &str) -> Result { - match value { - "aes-gcm" => Ok(Self::AesGcm), - "aes-256-gcm" => Ok(Self::Aes256Gcm), - "xor" => Ok(Self::Xor), - #[cfg(feature = "wireguard")] - "chacha20" => Ok(Self::ChaCha20), - #[cfg(feature = "openssl-crypto")] - "openssl-aes-gcm" => Ok(Self::OpensslAesGcm), - #[cfg(feature = "openssl-crypto")] - "openssl-chacha20" => Ok(Self::OpensslChacha20), - #[cfg(feature = "openssl-crypto")] - "openssl-aes-256-gcm" => Ok(Self::OpensslAes256Gcm), - _ => Err(anyhow::anyhow!("invalid encryption algorithm")), - } - } -} - -pub fn get_avaliable_encrypt_methods() -> Vec<&'static str> { - let mut r = vec!["aes-gcm", "aes-256-gcm", "xor"]; - if cfg!(feature = "wireguard") { - r.push("chacha20"); - } - if cfg!(feature = "openssl-crypto") { - r.extend(vec![ - "openssl-aes-gcm", - "openssl-chacha20", - "openssl-aes-256-gcm", - ]); - } - r -} - #[auto_impl::auto_impl(Box, &)] pub trait ConfigLoader: Send + Sync { fn get_id(&self) -> uuid::Uuid; diff --git a/easytier/src/core.rs b/easytier/src/core.rs index f1b88a08..92f2f70b 100644 --- a/easytier/src/core.rs +++ b/easytier/src/core.rs @@ -10,10 +10,9 @@ use std::{ use crate::{ common::{ config::{ - get_avaliable_encrypt_methods, load_config_from_file, process_secure_mode_cfg, - ConfigFileControl, ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, - LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader, - VpnPortalConfig, + load_config_from_file, process_secure_mode_cfg, ConfigFileControl, ConfigLoader, + ConsoleLoggerConfig, EncryptionAlgorithm, FileLoggerConfig, LoggingConfigLoader, + NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader, VpnPortalConfig, }, constants::EASYTIER_VERSION, log, @@ -277,9 +276,9 @@ struct NetworkOptions { long, env = "ET_ENCRYPTION_ALGORITHM", help = t!("core_clap.encryption_algorithm").to_string(), - value_parser = get_avaliable_encrypt_methods() + value_enum, )] - encryption_algorithm: Option, + encryption_algorithm: Option, #[arg( long, @@ -1007,7 +1006,7 @@ impl NetworkOptions { f.enable_encryption = !v; } if let Some(algorithm) = &self.encryption_algorithm { - f.encryption_algorithm = algorithm.clone(); + f.encryption_algorithm = algorithm.to_string(); } if let Some(v) = self.disable_ipv6 { f.enable_ipv6 = !v; diff --git a/easytier/src/peers/encrypt/aes_gcm.rs b/easytier/src/peers/encrypt/aes_gcm.rs index 2201456b..a48bd38e 100644 --- a/easytier/src/peers/encrypt/aes_gcm.rs +++ b/easytier/src/peers/encrypt/aes_gcm.rs @@ -1,10 +1,8 @@ -use aes_gcm::aead::consts::{U12, U16}; -use aes_gcm::aead::generic_array::GenericArray; -use aes_gcm::{AeadCore, AeadInPlace, Aes128Gcm, Aes256Gcm, Key, KeyInit, Nonce, Tag}; +use aes_gcm::{AeadCore, AeadInPlace, Aes128Gcm, Aes256Gcm, Key, KeyInit}; use rand::rngs::OsRng; use zerocopy::{AsBytes, FromBytes}; -use crate::tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED}; +use crate::tunnel::packet_def::{StandardAeadTail, ZCPacket}; use super::{Encryptor, Error}; @@ -42,27 +40,28 @@ impl Encryptor for AesGcmCipher { } let payload_len = zc_packet.payload().len(); - if payload_len < AES_GCM_ENCRYPTION_RESERVED { + if payload_len < StandardAeadTail::SIZE { return Err(Error::PacketTooShort(zc_packet.payload().len())); } - let text_len = payload_len - AES_GCM_ENCRYPTION_RESERVED; + let text_len = payload_len - StandardAeadTail::SIZE; - let aes_tail = AesGcmTail::ref_from_suffix(zc_packet.payload()) + let aes_tail = StandardAeadTail::ref_from_suffix(zc_packet.payload()) .unwrap() .clone(); - let nonce: &GenericArray = Nonce::from_slice(&aes_tail.nonce); - let tag: GenericArray = Tag::clone_from_slice(aes_tail.tag.as_slice()); + let nonce = aes_tail.nonce.into(); + let tag = aes_tail.tag.into(); + let rs = match &self.cipher { AesGcmEnum::AES128GCM(aes_gcm) => aes_gcm.decrypt_in_place_detached( - nonce, + &nonce, &[], &mut zc_packet.mut_payload()[..text_len], &tag, ), AesGcmEnum::AES256GCM(aes_gcm) => aes_gcm.decrypt_in_place_detached( - nonce, + &nonce, &[], &mut zc_packet.mut_payload()[..text_len], &tag, @@ -79,7 +78,7 @@ impl Encryptor for AesGcmCipher { let old_len = zc_packet.buf_len(); zc_packet .mut_inner() - .truncate(old_len - AES_GCM_ENCRYPTION_RESERVED); + .truncate(old_len - StandardAeadTail::SIZE); Ok(()) } @@ -98,43 +97,40 @@ impl Encryptor for AesGcmCipher { return Ok(()); } - let mut tail = AesGcmTail::default(); - if let Some(nonce) = nonce { - if nonce.len() != tail.nonce.len() { - return Err(Error::EncryptionFailed); - } - tail.nonce.copy_from_slice(nonce); - } - let rs = match &self.cipher { + let nonce = nonce + .map(|n| { + <[u8; StandardAeadTail::NONCE_SIZE]>::try_from(n) + .map(Into::into) + .map_err(|_| Error::EncryptionFailed) + }) + .transpose()?; + + let (tag, nonce) = match &self.cipher { AesGcmEnum::AES128GCM(aes_gcm) => { - if nonce.is_none() { - let nonce = Aes128Gcm::generate_nonce(&mut OsRng); - tail.nonce.copy_from_slice(nonce.as_slice()); - } - let nonce = Nonce::from_slice(&tail.nonce); - aes_gcm.encrypt_in_place_detached(nonce, &[], zc_packet.mut_payload()) + let nonce = nonce.unwrap_or_else(|| Aes128Gcm::generate_nonce(&mut OsRng)); + ( + aes_gcm.encrypt_in_place_detached(&nonce, &[], zc_packet.mut_payload()), + nonce, + ) } AesGcmEnum::AES256GCM(aes_gcm) => { - if nonce.is_none() { - let nonce = Aes256Gcm::generate_nonce(&mut OsRng); - tail.nonce.copy_from_slice(nonce.as_slice()); - } - let nonce = Nonce::from_slice(&tail.nonce); - aes_gcm.encrypt_in_place_detached(nonce, &[], zc_packet.mut_payload()) + let nonce = nonce.unwrap_or_else(|| Aes256Gcm::generate_nonce(&mut OsRng)); + ( + aes_gcm.encrypt_in_place_detached(&nonce, &[], zc_packet.mut_payload()), + nonce, + ) } }; - match rs { - Ok(tag) => { - tail.tag.copy_from_slice(tag.as_slice()); + let tail = StandardAeadTail { + tag: tag.map_err(|_| Error::EncryptionFailed)?.into(), + nonce: nonce.into(), + }; - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(true); - zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); - Ok(()) - } - Err(_) => Err(Error::EncryptionFailed), - } + let pm_header = zc_packet.mut_peer_manager_header().unwrap(); + pm_header.set_encrypted(true); + zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); + Ok(()) } } @@ -142,7 +138,7 @@ impl Encryptor for AesGcmCipher { mod tests { use crate::{ peers::encrypt::{aes_gcm::AesGcmCipher, Encryptor}, - tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED}, + tunnel::packet_def::{StandardAeadTail, ZCPacket}, }; use zerocopy::FromBytes; @@ -154,10 +150,7 @@ mod tests { let mut packet = ZCPacket::new_with_payload(text); packet.fill_peer_manager_hdr(0, 0, 0); cipher.encrypt(&mut packet).unwrap(); - assert_eq!( - packet.payload().len(), - text.len() + AES_GCM_ENCRYPTION_RESERVED - ); + assert_eq!(packet.payload().len(), text.len() + StandardAeadTail::SIZE); assert!(packet.peer_manager_header().unwrap().is_encrypted()); cipher.decrypt(&mut packet).unwrap(); @@ -186,7 +179,7 @@ mod tests { assert_eq!(packet1.payload(), packet2.payload()); - let tail = AesGcmTail::ref_from_suffix(packet1.payload()).unwrap(); + let tail = StandardAeadTail::ref_from_suffix(packet1.payload()).unwrap(); assert_eq!(tail.nonce, nonce); cipher.decrypt(&mut packet1).unwrap(); diff --git a/easytier/src/peers/encrypt/mod.rs b/easytier/src/peers/encrypt/mod.rs index 0e42beb5..5810c32a 100644 --- a/easytier/src/peers/encrypt/mod.rs +++ b/easytier/src/peers/encrypt/mod.rs @@ -1,23 +1,20 @@ -use std::sync::Arc; - use crate::{ common::{config::EncryptionAlgorithm, log}, tunnel::packet_def::ZCPacket, }; +use cfg_if::cfg_if; +use std::sync::Arc; #[cfg(feature = "wireguard")] -pub mod ring_aes_gcm; - -#[cfg(feature = "wireguard")] -pub mod ring_chacha20; +pub mod ring; #[cfg(feature = "aes-gcm")] pub mod aes_gcm; #[cfg(feature = "openssl-crypto")] -pub mod openssl_cipher; +pub mod openssl; -pub mod xor_cipher; +pub mod xor; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -32,6 +29,7 @@ pub enum Error { } pub trait Encryptor: Send + Sync + 'static { + fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>; fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>; fn encrypt_with_nonce( &self, @@ -40,16 +38,11 @@ pub trait Encryptor: Send + Sync + 'static { ) -> Result<(), Error> { self.encrypt(zc_packet) } - fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>; } pub struct NullCipher; impl Encryptor for NullCipher { - fn encrypt(&self, _zc_packet: &mut ZCPacket) -> Result<(), Error> { - Ok(()) - } - fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { let pm_header = zc_packet.peer_manager_header().unwrap(); if pm_header.is_encrypted() { @@ -58,6 +51,10 @@ impl Encryptor for NullCipher { Ok(()) } } + + fn encrypt(&self, _zc_packet: &mut ZCPacket) -> Result<(), Error> { + Ok(()) + } } /// Create an encryptor based on the algorithm name @@ -69,60 +66,59 @@ pub fn create_encryptor( let algorithm = match EncryptionAlgorithm::try_from(algorithm) { Ok(algorithm) => algorithm, Err(_) => { + let default = EncryptionAlgorithm::default(); log::warn!( - "Unknown encryption algorithm: {}, falling back to default AES-GCM", - algorithm + "Unknown encryption algorithm: {}, falling back to default {}", + algorithm, + default ); - EncryptionAlgorithm::AesGcm + default } }; match algorithm { + EncryptionAlgorithm::Xor => Arc::new(xor::XorCipher::new(&key_128)), + + #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] EncryptionAlgorithm::AesGcm => { - #[cfg(feature = "wireguard")] - { - Arc::new(ring_aes_gcm::AesGcmCipher::new_128(key_128)) - } - #[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))] - { - Arc::new(aes_gcm::AesGcmCipher::new_128(key_128)) - } - #[cfg(all(not(feature = "wireguard"), not(feature = "aes-gcm")))] - { - compile_error!( - "wireguard or aes-gcm feature must be enabled for default encryption" - ); + cfg_if! { + if #[cfg(feature = "openssl-crypto")] { + Arc::new(openssl::OpenSslCipher::new_aes128_gcm(key_128)) + } else if #[cfg(feature = "wireguard")] { + Arc::new(ring::RingCipher::new_aes128_gcm(key_128)) + } else if #[cfg(feature = "aes-gcm")] { + Arc::new(aes_gcm::AesGcmCipher::new_128(key_128)) + } else { + compile_error!("unreachable!"); + } } } + #[cfg(any(feature = "aes-gcm", feature = "wireguard", feature = "openssl-crypto"))] EncryptionAlgorithm::Aes256Gcm => { - #[cfg(feature = "wireguard")] - { - Arc::new(ring_aes_gcm::AesGcmCipher::new_256(key_256)) - } - #[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))] - { - Arc::new(aes_gcm::AesGcmCipher::new_256(key_256)) + cfg_if! { + if #[cfg(feature = "openssl-crypto")] { + Arc::new(openssl::OpenSslCipher::new_aes256_gcm(key_256)) + } else if #[cfg(feature = "wireguard")] { + Arc::new(ring::RingCipher::new_aes256_gcm(key_256)) + } else if #[cfg(feature = "aes-gcm")] { + Arc::new(aes_gcm::AesGcmCipher::new_256(key_256)) + } else { + compile_error!("unreachable!"); + } } } - EncryptionAlgorithm::Xor => Arc::new(xor_cipher::XorCipher::new(&key_128)), - - #[cfg(feature = "wireguard")] - EncryptionAlgorithm::ChaCha20 => Arc::new(ring_chacha20::RingChaCha20Cipher::new(key_256)), - - #[cfg(feature = "openssl-crypto")] - EncryptionAlgorithm::OpensslAesGcm => { - Arc::new(openssl_cipher::OpenSslCipher::new_aes128_gcm(key_128)) - } - - #[cfg(feature = "openssl-crypto")] - EncryptionAlgorithm::OpensslAes256Gcm => { - Arc::new(openssl_cipher::OpenSslCipher::new_aes256_gcm(key_256)) - } - - #[cfg(feature = "openssl-crypto")] - EncryptionAlgorithm::OpensslChacha20 => { - Arc::new(openssl_cipher::OpenSslCipher::new_chacha20(key_256)) + #[cfg(any(feature = "wireguard", feature = "openssl-crypto"))] + EncryptionAlgorithm::ChaCha20 => { + cfg_if! { + if #[cfg(feature = "openssl-crypto")] { + Arc::new(openssl::OpenSslCipher::new_chacha20(key_256)) + } else if #[cfg(feature = "wireguard")] { + Arc::new(ring::RingCipher::new_chacha20(key_256)) + } else { + compile_error!("unreachable!"); + } + } } } } diff --git a/easytier/src/peers/encrypt/openssl.rs b/easytier/src/peers/encrypt/openssl.rs new file mode 100644 index 00000000..f8bb8ec3 --- /dev/null +++ b/easytier/src/peers/encrypt/openssl.rs @@ -0,0 +1,201 @@ +use crate::tunnel::packet_def::{StandardAeadTail, ZCPacket}; +use openssl::symm::{Cipher, Crypter, Mode}; +use rand::RngCore; +use zerocopy::{AsBytes, FromBytes, FromZeroes}; + +use crate::peers::encrypt::{Encryptor, Error}; + +#[derive(Clone)] +pub struct OpenSslCipher { + pub(crate) cipher: OpenSslEnum, +} + +#[derive(Clone, Copy)] +pub enum OpenSslEnum { + Aes128Gcm([u8; 16]), + Aes256Gcm([u8; 32]), + ChaCha20([u8; 32]), +} + +impl OpenSslCipher { + pub fn new_aes128_gcm(key: [u8; 16]) -> Self { + Self { + cipher: OpenSslEnum::Aes128Gcm(key), + } + } + + pub fn new_aes256_gcm(key: [u8; 32]) -> Self { + Self { + cipher: OpenSslEnum::Aes256Gcm(key), + } + } + + pub fn new_chacha20(key: [u8; 32]) -> Self { + Self { + cipher: OpenSslEnum::ChaCha20(key), + } + } + + fn get_cipher_and_key(&self) -> (Cipher, &[u8]) { + match &self.cipher { + OpenSslEnum::Aes128Gcm(key) => (Cipher::aes_128_gcm(), key.as_slice()), + OpenSslEnum::Aes256Gcm(key) => (Cipher::aes_256_gcm(), key.as_slice()), + OpenSslEnum::ChaCha20(key) => (Cipher::chacha20_poly1305(), key.as_slice()), + } + } +} + +impl Encryptor for OpenSslCipher { + fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { + let pm_header = zc_packet.peer_manager_header().unwrap(); + if !pm_header.is_encrypted() { + return Ok(()); + } + + let payload = zc_packet.payload(); + let len = payload.len(); + if len < StandardAeadTail::SIZE { + return Err(Error::PacketTooShort(len)); + } + + let (cipher, key) = self.get_cipher_and_key(); + + // 提取 nonce/IV 和 tag + let tail = StandardAeadTail::ref_from_suffix(payload).unwrap(); + + let mut decrypter = Crypter::new(cipher, Mode::Decrypt, key, Some(&tail.nonce)) + .map_err(|_| Error::DecryptionFailed)?; + + decrypter + .set_tag(&tail.tag) + .map_err(|_| Error::DecryptionFailed)?; + + let text_len = len - StandardAeadTail::SIZE; + let mut output = vec![0u8; text_len + cipher.block_size()]; + let mut count = decrypter + .update(&payload[..text_len], &mut output) + .map_err(|_| Error::DecryptionFailed)?; + + count += decrypter + .finalize(&mut output[count..]) + .map_err(|_| Error::DecryptionFailed)?; + + // 更新数据包 + zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]); + let pm_header = zc_packet.mut_peer_manager_header().unwrap(); + pm_header.set_encrypted(false); + + let len = zc_packet.buf_len() - (len - count); + zc_packet.mut_inner().truncate(len); + + Ok(()) + } + + fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { + self.encrypt_with_nonce(zc_packet, None) + } + + fn encrypt_with_nonce( + &self, + zc_packet: &mut ZCPacket, + nonce: Option<&[u8]>, + ) -> Result<(), Error> { + let pm_header = zc_packet.peer_manager_header().unwrap(); + if pm_header.is_encrypted() { + tracing::warn!(?zc_packet, "packet is already encrypted"); + return Ok(()); + } + + let (cipher, key) = self.get_cipher_and_key(); + + let mut tail = StandardAeadTail::new_zeroed(); + if let Some(nonce) = nonce { + if nonce.len() != StandardAeadTail::NONCE_SIZE { + return Err(Error::EncryptionFailed); + } + tail.nonce.copy_from_slice(nonce); + } else { + rand::thread_rng().fill_bytes(&mut tail.nonce); + } + + let mut encrypter = Crypter::new(cipher, Mode::Encrypt, key, Some(&tail.nonce)) + .map_err(|_| Error::EncryptionFailed)?; + + let payload_len = zc_packet.payload().len(); + let mut output = vec![0u8; payload_len + cipher.block_size()]; + + let mut count = encrypter + .update(zc_packet.payload(), &mut output) + .map_err(|_| Error::EncryptionFailed)?; + + count += encrypter + .finalize(&mut output[count..]) + .map_err(|_| Error::EncryptionFailed)?; + + // 更新数据包内容 + zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]); + + encrypter + .get_tag(&mut tail.tag) + .map_err(|_| Error::EncryptionFailed)?; + + // 添加 nonce/IV & tag 的结构 + zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); + + let pm_header = zc_packet.mut_peer_manager_header().unwrap(); + pm_header.set_encrypted(true); + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn run_cipher_test_with_nonce(cipher: OpenSslCipher) { + let text = b"Hello, World! This is a standardized test message."; + let nonce: [u8; 12] = [101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112]; + + let mut packet = ZCPacket::new_with_payload(text); + packet.fill_peer_manager_hdr(0, 0, 0); + + cipher + .encrypt_with_nonce(&mut packet, Some(&nonce)) + .unwrap(); + + let payload = packet.payload(); + let len = payload.len(); + + assert!(len > text.len() + StandardAeadTail::SIZE - 1); + assert!(packet.peer_manager_header().unwrap().is_encrypted()); + + let tail = StandardAeadTail::ref_from_suffix(payload).unwrap().clone(); + assert_eq!(tail.nonce, nonce); + + cipher.decrypt(&mut packet).unwrap(); + assert_eq!(packet.payload(), text); + assert!(!packet.peer_manager_header().unwrap().is_encrypted()); + } + + #[test] + fn test_openssl_aes128_gcm() { + let key = [1u8; 16]; + let cipher = OpenSslCipher::new_aes128_gcm(key); + run_cipher_test_with_nonce(cipher); + } + + #[test] + fn test_openssl_aes256_gcm() { + let key = [2u8; 32]; + let cipher = OpenSslCipher::new_aes256_gcm(key); + run_cipher_test_with_nonce(cipher); + } + + #[test] + fn test_openssl_chacha20() { + let key = [3u8; 32]; + let cipher = OpenSslCipher::new_chacha20(key); + run_cipher_test_with_nonce(cipher); + } +} diff --git a/easytier/src/peers/encrypt/openssl_cipher.rs b/easytier/src/peers/encrypt/openssl_cipher.rs deleted file mode 100644 index fc7f70d9..00000000 --- a/easytier/src/peers/encrypt/openssl_cipher.rs +++ /dev/null @@ -1,288 +0,0 @@ -use openssl::symm::{Cipher, Crypter, Mode}; -use rand::RngCore; -use zerocopy::{AsBytes, FromBytes, FromZeroes}; - -use crate::tunnel::packet_def::ZCPacket; - -use crate::peers::encrypt::{Encryptor, Error}; - -// OpenSSL 加密尾部结构 -#[repr(C, packed)] -#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)] -pub struct OpenSslTail { - pub nonce: [u8; 16], // 使用 16 字节的 nonce/IV -} - -pub const OPENSSL_ENCRYPTION_RESERVED: usize = std::mem::size_of::(); - -#[derive(Clone)] -pub struct OpenSslCipher { - pub(crate) cipher: OpenSslEnum, -} - -pub enum OpenSslEnum { - Aes128Gcm([u8; 16]), - Aes256Gcm([u8; 32]), - Chacha20([u8; 32]), -} - -impl Clone for OpenSslEnum { - fn clone(&self) -> Self { - match &self { - OpenSslEnum::Aes128Gcm(key) => OpenSslEnum::Aes128Gcm(*key), - OpenSslEnum::Aes256Gcm(key) => OpenSslEnum::Aes256Gcm(*key), - OpenSslEnum::Chacha20(key) => OpenSslEnum::Chacha20(*key), - } - } -} - -impl OpenSslCipher { - pub fn new_aes128_gcm(key: [u8; 16]) -> Self { - Self { - cipher: OpenSslEnum::Aes128Gcm(key), - } - } - - pub fn new_aes256_gcm(key: [u8; 32]) -> Self { - Self { - cipher: OpenSslEnum::Aes256Gcm(key), - } - } - - pub fn new_chacha20(key: [u8; 32]) -> Self { - Self { - cipher: OpenSslEnum::Chacha20(key), - } - } - - fn get_cipher_and_key(&self) -> (Cipher, &[u8]) { - match &self.cipher { - OpenSslEnum::Aes128Gcm(key) => (Cipher::aes_128_gcm(), key.as_slice()), - OpenSslEnum::Aes256Gcm(key) => (Cipher::aes_256_gcm(), key.as_slice()), - OpenSslEnum::Chacha20(key) => (Cipher::chacha20_poly1305(), key.as_slice()), - } - } - - fn is_aead_cipher(&self) -> bool { - matches!( - self.cipher, - OpenSslEnum::Aes128Gcm(_) | OpenSslEnum::Aes256Gcm(_) | OpenSslEnum::Chacha20(_) - ) - } - - fn get_nonce_size(&self) -> usize { - match &self.cipher { - OpenSslEnum::Aes128Gcm(_) | OpenSslEnum::Aes256Gcm(_) | OpenSslEnum::Chacha20(_) => 12, // GCM and ChaCha20-Poly1305 use 12-byte nonce - } - } -} - -impl Encryptor for OpenSslCipher { - fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if !pm_header.is_encrypted() { - return Ok(()); - } - - let payload_len = zc_packet.payload().len(); - if payload_len < OPENSSL_ENCRYPTION_RESERVED { - return Err(Error::PacketTooShort(zc_packet.payload().len())); - } - - let (cipher, key) = self.get_cipher_and_key(); - let is_aead = self.is_aead_cipher(); - let nonce_size = self.get_nonce_size(); - - // 提取 nonce/IV - let openssl_tail = OpenSslTail::ref_from_suffix(zc_packet.payload()) - .unwrap() - .clone(); - - let text_len = if is_aead { - payload_len - OPENSSL_ENCRYPTION_RESERVED - 16 // AEAD 需要减去 tag 长度 - } else { - payload_len - OPENSSL_ENCRYPTION_RESERVED - }; - - let mut decrypter = Crypter::new( - cipher, - Mode::Decrypt, - key, - Some(&openssl_tail.nonce[..nonce_size]), - ) - .map_err(|_| Error::DecryptionFailed)?; - - if is_aead { - // 对于 AEAD 模式,需要设置 tag - let tag_start = text_len; - let tag = &zc_packet.payload()[tag_start..tag_start + 16]; - decrypter - .set_tag(tag) - .map_err(|_| Error::DecryptionFailed)?; - } - - let mut output = vec![0u8; text_len + cipher.block_size()]; - let mut count = decrypter - .update(&zc_packet.payload()[..text_len], &mut output) - .map_err(|_| Error::DecryptionFailed)?; - - count += decrypter - .finalize(&mut output[count..]) - .map_err(|_| Error::DecryptionFailed)?; - - // 更新数据包 - zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]); - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(false); - let old_len = zc_packet.buf_len(); - let new_len = old_len - (payload_len - count); - zc_packet.mut_inner().truncate(new_len); - - Ok(()) - } - - fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - self.encrypt_with_nonce(zc_packet, None) - } - - fn encrypt_with_nonce( - &self, - zc_packet: &mut ZCPacket, - nonce: Option<&[u8]>, - ) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if pm_header.is_encrypted() { - tracing::warn!(?zc_packet, "packet is already encrypted"); - return Ok(()); - } - - let (cipher, key) = self.get_cipher_and_key(); - let is_aead = self.is_aead_cipher(); - let nonce_size = self.get_nonce_size(); - - let mut tail = OpenSslTail::default(); - if let Some(nonce) = nonce { - if nonce.len() != nonce_size { - return Err(Error::EncryptionFailed); - } - tail.nonce[..nonce_size].copy_from_slice(nonce); - } else { - rand::thread_rng().fill_bytes(&mut tail.nonce[..nonce_size]); - } - - let mut encrypter = - Crypter::new(cipher, Mode::Encrypt, key, Some(&tail.nonce[..nonce_size])) - .map_err(|_| Error::EncryptionFailed)?; - - let payload_len = zc_packet.payload().len(); - let mut output = vec![0u8; payload_len + cipher.block_size()]; - - let mut count = encrypter - .update(zc_packet.payload(), &mut output) - .map_err(|_| Error::EncryptionFailed)?; - - count += encrypter - .finalize(&mut output[count..]) - .map_err(|_| Error::EncryptionFailed)?; - - // 更新数据包内容 - zc_packet.mut_payload()[..count].copy_from_slice(&output[..count]); - - // 对于 AEAD 模式,添加 tag - if is_aead { - let mut tag = vec![0u8; 16]; // GCM 标签通常是 16 字节 - encrypter - .get_tag(&mut tag) - .map_err(|_| Error::EncryptionFailed)?; - zc_packet.mut_inner().extend_from_slice(&tag); - } - - // 添加 nonce/IV - zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); - - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(true); - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - peers::encrypt::{openssl_cipher::OpenSslCipher, Encryptor}, - tunnel::packet_def::ZCPacket, - }; - use zerocopy::FromBytes; - - use super::OPENSSL_ENCRYPTION_RESERVED; - - #[test] - fn test_openssl_aes128_gcm() { - let key = [0u8; 16]; - let cipher = OpenSslCipher::new_aes128_gcm(key); - let text = b"Hello, World! This is a test message for OpenSSL AES-128-GCM."; - let mut packet = ZCPacket::new_with_payload(text); - packet.fill_peer_manager_hdr(0, 0, 0); - - // 加密 - cipher.encrypt(&mut packet).unwrap(); - assert!(packet.payload().len() > text.len() + OPENSSL_ENCRYPTION_RESERVED); - assert!(packet.peer_manager_header().unwrap().is_encrypted()); - - // 解密 - cipher.decrypt(&mut packet).unwrap(); - assert_eq!(packet.payload(), text); - assert!(!packet.peer_manager_header().unwrap().is_encrypted()); - } - - #[test] - fn test_openssl_aes128_gcm_with_nonce() { - let key = [7u8; 16]; - let cipher = OpenSslCipher::new_aes128_gcm(key); - let text = b"Hello"; - let nonce = [3u8; 12]; - - let mut packet1 = ZCPacket::new_with_payload(text); - packet1.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet1, Some(&nonce)) - .unwrap(); - - let mut packet2 = ZCPacket::new_with_payload(text); - packet2.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet2, Some(&nonce)) - .unwrap(); - - assert_eq!(packet1.payload(), packet2.payload()); - assert!(packet1.payload().len() > text.len() + OPENSSL_ENCRYPTION_RESERVED); - - let tail = super::OpenSslTail::ref_from_suffix(packet1.payload()) - .unwrap() - .clone(); - assert_eq!(&tail.nonce[..nonce.len()], nonce); - - cipher.decrypt(&mut packet1).unwrap(); - assert_eq!(packet1.payload(), text); - } - - #[test] - fn test_openssl_chacha20() { - let key = [0u8; 32]; - let cipher = OpenSslCipher::new_chacha20(key); - let text = b"Hello, World! This is a test message for OpenSSL ChaCha20."; - let mut packet = ZCPacket::new_with_payload(text); - packet.fill_peer_manager_hdr(0, 0, 0); - - // 加密 - cipher.encrypt(&mut packet).unwrap(); - assert!(packet.payload().len() > text.len()); - assert!(packet.peer_manager_header().unwrap().is_encrypted()); - - // 解密 - cipher.decrypt(&mut packet).unwrap(); - assert_eq!(packet.payload(), text); - assert!(!packet.peer_manager_header().unwrap().is_encrypted()); - } -} diff --git a/easytier/src/peers/encrypt/ring.rs b/easytier/src/peers/encrypt/ring.rs new file mode 100644 index 00000000..a0876468 --- /dev/null +++ b/easytier/src/peers/encrypt/ring.rs @@ -0,0 +1,252 @@ +use rand::RngCore; +use ring::aead::{self}; +use ring::aead::{LessSafeKey, UnboundKey}; +use zerocopy::{AsBytes, FromBytes, FromZeroes}; + +use crate::tunnel::packet_def::{StandardAeadTail, ZCPacket}; + +use super::{Encryptor, Error}; + +#[derive(Clone)] +pub struct RingCipher { + pub(crate) cipher: RingEnum, +} + +pub enum RingEnum { + Aes128Gcm(LessSafeKey, [u8; 16]), + Aes256Gcm(LessSafeKey, [u8; 32]), + ChaCha20(LessSafeKey, [u8; 32]), +} + +impl RingEnum { + fn get_cipher(&self) -> &LessSafeKey { + match &self { + RingEnum::Aes128Gcm(cipher, _) => cipher, + RingEnum::Aes256Gcm(cipher, _) => cipher, + RingEnum::ChaCha20(cipher, _) => cipher, + } + } +} + +impl Clone for RingEnum { + fn clone(&self) -> Self { + match &self { + RingEnum::Aes128Gcm(_, key) => { + let c = + LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, key.as_slice()).unwrap()); + RingEnum::Aes128Gcm(c, *key) + } + RingEnum::Aes256Gcm(_, key) => { + let c = + LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, key.as_slice()).unwrap()); + RingEnum::Aes256Gcm(c, *key) + } + RingEnum::ChaCha20(_, key) => { + let c = LessSafeKey::new( + UnboundKey::new(&aead::CHACHA20_POLY1305, key.as_slice()).unwrap(), + ); + RingEnum::ChaCha20(c, *key) + } + } + } +} + +impl RingCipher { + pub fn new_aes128_gcm(key: [u8; 16]) -> Self { + let cipher = LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, &key).unwrap()); + Self { + cipher: RingEnum::Aes128Gcm(cipher, key), + } + } + + pub fn new_aes256_gcm(key: [u8; 32]) -> Self { + let cipher = LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, &key).unwrap()); + Self { + cipher: RingEnum::Aes256Gcm(cipher, key), + } + } + + pub fn new_chacha20(key: [u8; 32]) -> Self { + let unbound_key = UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); + let cipher = LessSafeKey::new(unbound_key); + Self { + cipher: RingEnum::ChaCha20(cipher, key), + } + } +} + +impl Encryptor for RingCipher { + fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { + let pm_header = zc_packet.peer_manager_header().unwrap(); + if !pm_header.is_encrypted() { + return Ok(()); + } + + let payload_len = zc_packet.payload().len(); + if payload_len < StandardAeadTail::SIZE { + return Err(Error::PacketTooShort(zc_packet.payload().len())); + } + + let text_and_tag_len = payload_len - StandardAeadTail::SIZE + StandardAeadTail::TAG_SIZE; + + let aes_tail = StandardAeadTail::ref_from_suffix(zc_packet.payload()).unwrap(); + let nonce = aead::Nonce::assume_unique_for_key(aes_tail.nonce); + + self.cipher + .get_cipher() + .open_in_place( + nonce, + aead::Aad::empty(), + &mut zc_packet.mut_payload()[..text_and_tag_len], + ) + .map_err(|_| Error::DecryptionFailed)?; + + let pm_header = zc_packet.mut_peer_manager_header().unwrap(); + pm_header.set_encrypted(false); + let old_len = zc_packet.buf_len(); + zc_packet + .mut_inner() + .truncate(old_len - StandardAeadTail::SIZE); + Ok(()) + } + + fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { + self.encrypt_with_nonce(zc_packet, None) + } + + fn encrypt_with_nonce( + &self, + zc_packet: &mut ZCPacket, + nonce: Option<&[u8]>, + ) -> Result<(), Error> { + let pm_header = zc_packet.peer_manager_header().unwrap(); + if pm_header.is_encrypted() { + tracing::warn!(?zc_packet, "packet is already encrypted"); + return Ok(()); + } + + let mut tail = StandardAeadTail::new_zeroed(); + + match nonce { + Some(n) => tail.nonce = n.try_into().map_err(|_| Error::EncryptionFailed)?, + None => rand::thread_rng().fill_bytes(&mut tail.nonce), + } + let nonce = aead::Nonce::assume_unique_for_key(tail.nonce); + + let tag = self + .cipher + .get_cipher() + .seal_in_place_separate_tag(nonce, aead::Aad::empty(), zc_packet.mut_payload()) + .map_err(|_| Error::EncryptionFailed)?; + + let tag = tag.as_ref(); + if tag.len() != StandardAeadTail::TAG_SIZE { + return Err(Error::InvalidTag(tag.to_vec())); + } + tail.tag.copy_from_slice(tag); + + let pm_header = zc_packet.mut_peer_manager_header().unwrap(); + pm_header.set_encrypted(true); + zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + peers::encrypt::{ring::RingCipher, Encryptor}, + tunnel::packet_def::{StandardAeadTail, ZCPacket}, + }; + use zerocopy::FromBytes; + + #[test] + fn test_aes_gcm_cipher() { + let key = [0u8; 16]; + let cipher = RingCipher::new_aes128_gcm(key); + let text = b"1234567"; + let mut packet = ZCPacket::new_with_payload(text); + packet.fill_peer_manager_hdr(0, 0, 0); + cipher.encrypt(&mut packet).unwrap(); + assert_eq!(packet.payload().len(), text.len() + StandardAeadTail::SIZE); + assert!(packet.peer_manager_header().unwrap().is_encrypted()); + + cipher.decrypt(&mut packet).unwrap(); + assert_eq!(packet.payload(), text); + assert!(!packet.peer_manager_header().unwrap().is_encrypted()); + } + + #[test] + fn test_aes_gcm_cipher_with_nonce() { + let key = [7u8; 16]; + let cipher = RingCipher::new_aes128_gcm(key); + let text = b"Hello"; + let nonce = [3u8; 12]; + + let mut packet1 = ZCPacket::new_with_payload(text); + packet1.fill_peer_manager_hdr(0, 0, 0); + cipher + .encrypt_with_nonce(&mut packet1, Some(&nonce)) + .unwrap(); + + let mut packet2 = ZCPacket::new_with_payload(text); + packet2.fill_peer_manager_hdr(0, 0, 0); + cipher + .encrypt_with_nonce(&mut packet2, Some(&nonce)) + .unwrap(); + + assert_eq!(packet1.payload(), packet2.payload()); + + let tail = StandardAeadTail::ref_from_suffix(packet1.payload()).unwrap(); + assert_eq!(tail.nonce, nonce); + + cipher.decrypt(&mut packet1).unwrap(); + assert_eq!(packet1.payload(), text); + } + + #[test] + fn test_ring_chacha20_cipher() { + let key = [0u8; 32]; + let cipher = RingCipher::new_chacha20(key); + let text = b"Hello, World! This is a test message for Ring ChaCha20-Poly1305."; + let mut packet = ZCPacket::new_with_payload(text); + packet.fill_peer_manager_hdr(0, 0, 0); + + cipher.encrypt(&mut packet).unwrap(); + assert_eq!(packet.payload().len(), text.len() + StandardAeadTail::SIZE); + assert!(packet.peer_manager_header().unwrap().is_encrypted()); + + cipher.decrypt(&mut packet).unwrap(); + assert_eq!(packet.payload(), text); + assert!(!packet.peer_manager_header().unwrap().is_encrypted()); + } + + #[test] + fn test_ring_chacha20_cipher_with_nonce() { + let key = [9u8; 32]; + let cipher = RingCipher::new_chacha20(key); + let text = b"Hello"; + let nonce = [5u8; 12]; + + let mut packet1 = ZCPacket::new_with_payload(text); + packet1.fill_peer_manager_hdr(0, 0, 0); + cipher + .encrypt_with_nonce(&mut packet1, Some(&nonce)) + .unwrap(); + + let mut packet2 = ZCPacket::new_with_payload(text); + packet2.fill_peer_manager_hdr(0, 0, 0); + cipher + .encrypt_with_nonce(&mut packet2, Some(&nonce)) + .unwrap(); + + assert_eq!(packet1.payload(), packet2.payload()); + + let tail = StandardAeadTail::ref_from_suffix(packet1.payload()).unwrap(); + assert_eq!(tail.nonce, nonce); + + cipher.decrypt(&mut packet1).unwrap(); + assert_eq!(packet1.payload(), text); + assert!(!packet1.peer_manager_header().unwrap().is_encrypted()); + } +} diff --git a/easytier/src/peers/encrypt/ring_aes_gcm.rs b/easytier/src/peers/encrypt/ring_aes_gcm.rs deleted file mode 100644 index 74a961bd..00000000 --- a/easytier/src/peers/encrypt/ring_aes_gcm.rs +++ /dev/null @@ -1,205 +0,0 @@ -use rand::RngCore; -use ring::aead::{self}; -use ring::aead::{LessSafeKey, UnboundKey}; -use zerocopy::{AsBytes, FromBytes}; - -use crate::tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED}; - -use super::{Encryptor, Error}; - -#[derive(Clone)] -pub struct AesGcmCipher { - pub(crate) cipher: AesGcmEnum, -} - -pub enum AesGcmEnum { - AesGCM128(LessSafeKey, [u8; 16]), - AesGCM256(LessSafeKey, [u8; 32]), -} - -impl Clone for AesGcmEnum { - fn clone(&self) -> Self { - match &self { - AesGcmEnum::AesGCM128(_, key) => { - let c = - LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, key.as_slice()).unwrap()); - AesGcmEnum::AesGCM128(c, *key) - } - AesGcmEnum::AesGCM256(_, key) => { - let c = - LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, key.as_slice()).unwrap()); - AesGcmEnum::AesGCM256(c, *key) - } - } - } -} - -impl AesGcmCipher { - pub fn new_128(key: [u8; 16]) -> Self { - let cipher = LessSafeKey::new(UnboundKey::new(&aead::AES_128_GCM, &key).unwrap()); - Self { - cipher: AesGcmEnum::AesGCM128(cipher, key), - } - } - - pub fn new_256(key: [u8; 32]) -> Self { - let cipher = LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, &key).unwrap()); - Self { - cipher: AesGcmEnum::AesGCM256(cipher, key), - } - } -} - -impl Encryptor for AesGcmCipher { - fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if !pm_header.is_encrypted() { - return Ok(()); - } - - let payload_len = zc_packet.payload().len(); - if payload_len < AES_GCM_ENCRYPTION_RESERVED { - return Err(Error::PacketTooShort(zc_packet.payload().len())); - } - - let text_and_tag_len = payload_len - AES_GCM_ENCRYPTION_RESERVED + 16; - - let aes_tail = AesGcmTail::ref_from_suffix(zc_packet.payload()).unwrap(); - let nonce = aead::Nonce::assume_unique_for_key(aes_tail.nonce); - - let rs = match &self.cipher { - AesGcmEnum::AesGCM128(cipher, _) => cipher.open_in_place( - nonce, - aead::Aad::empty(), - &mut zc_packet.mut_payload()[..text_and_tag_len], - ), - AesGcmEnum::AesGCM256(cipher, _) => cipher.open_in_place( - nonce, - aead::Aad::empty(), - &mut zc_packet.mut_payload()[..text_and_tag_len], - ), - }; - if rs.is_err() { - return Err(Error::DecryptionFailed); - } - - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(false); - let old_len = zc_packet.buf_len(); - zc_packet - .mut_inner() - .truncate(old_len - AES_GCM_ENCRYPTION_RESERVED); - Ok(()) - } - - fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - self.encrypt_with_nonce(zc_packet, None) - } - - fn encrypt_with_nonce( - &self, - zc_packet: &mut ZCPacket, - nonce: Option<&[u8]>, - ) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if pm_header.is_encrypted() { - tracing::warn!(?zc_packet, "packet is already encrypted"); - return Ok(()); - } - - let mut tail = AesGcmTail::default(); - if let Some(nonce) = nonce { - if nonce.len() != tail.nonce.len() { - return Err(Error::EncryptionFailed); - } - tail.nonce.copy_from_slice(nonce); - } else { - rand::thread_rng().fill_bytes(&mut tail.nonce); - } - let nonce = aead::Nonce::assume_unique_for_key(tail.nonce); - - let rs = match &self.cipher { - AesGcmEnum::AesGCM128(cipher, _) => cipher.seal_in_place_separate_tag( - nonce, - aead::Aad::empty(), - zc_packet.mut_payload(), - ), - AesGcmEnum::AesGCM256(cipher, _) => cipher.seal_in_place_separate_tag( - nonce, - aead::Aad::empty(), - zc_packet.mut_payload(), - ), - }; - match rs { - Ok(tag) => { - let tag = tag.as_ref(); - if tag.len() != 16 { - return Err(Error::InvalidTag(tag.to_vec())); - } - tail.tag.copy_from_slice(tag); - - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(true); - zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); - Ok(()) - } - Err(_) => Err(Error::EncryptionFailed), - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - peers::encrypt::{ring_aes_gcm::AesGcmCipher, Encryptor}, - tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED}, - }; - use zerocopy::FromBytes; - - #[test] - fn test_aes_gcm_cipher() { - let key = [0u8; 16]; - let cipher = AesGcmCipher::new_128(key); - let text = b"1234567"; - let mut packet = ZCPacket::new_with_payload(text); - packet.fill_peer_manager_hdr(0, 0, 0); - cipher.encrypt(&mut packet).unwrap(); - assert_eq!( - packet.payload().len(), - text.len() + AES_GCM_ENCRYPTION_RESERVED - ); - assert!(packet.peer_manager_header().unwrap().is_encrypted()); - - cipher.decrypt(&mut packet).unwrap(); - assert_eq!(packet.payload(), text); - assert!(!packet.peer_manager_header().unwrap().is_encrypted()); - } - - #[test] - fn test_aes_gcm_cipher_with_nonce() { - let key = [7u8; 16]; - let cipher = AesGcmCipher::new_128(key); - let text = b"Hello"; - let nonce = [3u8; 12]; - - let mut packet1 = ZCPacket::new_with_payload(text); - packet1.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet1, Some(&nonce)) - .unwrap(); - - let mut packet2 = ZCPacket::new_with_payload(text); - packet2.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet2, Some(&nonce)) - .unwrap(); - - assert_eq!(packet1.payload(), packet2.payload()); - - let tail = AesGcmTail::ref_from_suffix(packet1.payload()).unwrap(); - assert_eq!(tail.nonce, nonce); - - cipher.decrypt(&mut packet1).unwrap(); - assert_eq!(packet1.payload(), text); - } -} diff --git a/easytier/src/peers/encrypt/ring_chacha20.rs b/easytier/src/peers/encrypt/ring_chacha20.rs deleted file mode 100644 index 8acc7fd9..00000000 --- a/easytier/src/peers/encrypt/ring_chacha20.rs +++ /dev/null @@ -1,170 +0,0 @@ -use rand::RngCore; -use ring::aead::{self, Aad, LessSafeKey, Nonce, UnboundKey}; -use zerocopy::{AsBytes, FromBytes, FromZeroes}; - -use super::{Encryptor, Error}; -use crate::tunnel::packet_def::ZCPacket; - -#[repr(C, packed)] -#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)] -pub struct ChaCha20Poly1305Tail { - pub tag: [u8; 16], - pub nonce: [u8; 12], -} - -pub const CHACHA20_POLY1305_ENCRYPTION_RESERVED: usize = - std::mem::size_of::(); - -#[derive(Clone)] -pub struct RingChaCha20Cipher { - cipher: LessSafeKey, - key: [u8; 32], -} - -impl RingChaCha20Cipher { - pub fn new(key: [u8; 32]) -> Self { - let unbound_key = UnboundKey::new(&aead::CHACHA20_POLY1305, &key).unwrap(); - let cipher = LessSafeKey::new(unbound_key); - Self { cipher, key } - } -} - -impl Encryptor for RingChaCha20Cipher { - fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if !pm_header.is_encrypted() { - return Ok(()); - } - - let payload_len = zc_packet.payload().len(); - if payload_len < CHACHA20_POLY1305_ENCRYPTION_RESERVED { - return Err(Error::PacketTooShort(zc_packet.payload().len())); - } - - let text_and_tag_len = payload_len - CHACHA20_POLY1305_ENCRYPTION_RESERVED + 16; - - let chacha20_tail = ChaCha20Poly1305Tail::ref_from_suffix(zc_packet.payload()).unwrap(); - let nonce = Nonce::assume_unique_for_key(chacha20_tail.nonce); - - let rs = self.cipher.open_in_place( - nonce, - Aad::empty(), - &mut zc_packet.mut_payload()[..text_and_tag_len], - ); - - if rs.is_err() { - return Err(Error::DecryptionFailed); - } - - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(false); - let old_len = zc_packet.buf_len(); - zc_packet - .mut_inner() - .truncate(old_len - CHACHA20_POLY1305_ENCRYPTION_RESERVED); - - Ok(()) - } - - fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> { - self.encrypt_with_nonce(zc_packet, None) - } - - fn encrypt_with_nonce( - &self, - zc_packet: &mut ZCPacket, - nonce: Option<&[u8]>, - ) -> Result<(), Error> { - let pm_header = zc_packet.peer_manager_header().unwrap(); - if pm_header.is_encrypted() { - tracing::warn!(?zc_packet, "packet is already encrypted"); - return Ok(()); - } - - let mut tail = ChaCha20Poly1305Tail::default(); - if let Some(nonce) = nonce { - if nonce.len() != tail.nonce.len() { - return Err(Error::EncryptionFailed); - } - tail.nonce.copy_from_slice(nonce); - } else { - rand::thread_rng().fill_bytes(&mut tail.nonce); - } - let nonce = Nonce::assume_unique_for_key(tail.nonce); - - let rs = - self.cipher - .seal_in_place_separate_tag(nonce, Aad::empty(), zc_packet.mut_payload()); - - match rs { - Ok(tag) => { - tail.tag.copy_from_slice(tag.as_ref()); - let pm_header = zc_packet.mut_peer_manager_header().unwrap(); - pm_header.set_encrypted(true); - zc_packet.mut_inner().extend_from_slice(tail.as_bytes()); - Ok(()) - } - Err(_) => Err(Error::EncryptionFailed), - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ - peers::encrypt::{ring_chacha20::RingChaCha20Cipher, Encryptor}, - tunnel::packet_def::ZCPacket, - }; - use zerocopy::FromBytes; - - use super::CHACHA20_POLY1305_ENCRYPTION_RESERVED; - - #[test] - fn test_ring_chacha20_cipher() { - let key = [0u8; 32]; - let cipher = RingChaCha20Cipher::new(key); - let text = b"Hello, World! This is a test message for Ring ChaCha20-Poly1305."; - let mut packet = ZCPacket::new_with_payload(text); - packet.fill_peer_manager_hdr(0, 0, 0); - - cipher.encrypt(&mut packet).unwrap(); - assert_eq!( - packet.payload().len(), - text.len() + CHACHA20_POLY1305_ENCRYPTION_RESERVED - ); - assert!(packet.peer_manager_header().unwrap().is_encrypted()); - - cipher.decrypt(&mut packet).unwrap(); - assert_eq!(packet.payload(), text); - assert!(!packet.peer_manager_header().unwrap().is_encrypted()); - } - - #[test] - fn test_ring_chacha20_cipher_with_nonce() { - let key = [9u8; 32]; - let cipher = RingChaCha20Cipher::new(key); - let text = b"Hello"; - let nonce = [5u8; 12]; - - let mut packet1 = ZCPacket::new_with_payload(text); - packet1.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet1, Some(&nonce)) - .unwrap(); - - let mut packet2 = ZCPacket::new_with_payload(text); - packet2.fill_peer_manager_hdr(0, 0, 0); - cipher - .encrypt_with_nonce(&mut packet2, Some(&nonce)) - .unwrap(); - - assert_eq!(packet1.payload(), packet2.payload()); - - let tail = super::ChaCha20Poly1305Tail::ref_from_suffix(packet1.payload()).unwrap(); - assert_eq!(tail.nonce, nonce); - - cipher.decrypt(&mut packet1).unwrap(); - assert_eq!(packet1.payload(), text); - assert!(!packet1.peer_manager_header().unwrap().is_encrypted()); - } -} diff --git a/easytier/src/peers/encrypt/xor_cipher.rs b/easytier/src/peers/encrypt/xor.rs similarity index 97% rename from easytier/src/peers/encrypt/xor_cipher.rs rename to easytier/src/peers/encrypt/xor.rs index 0f249883..3e2ddb00 100644 --- a/easytier/src/peers/encrypt/xor_cipher.rs +++ b/easytier/src/peers/encrypt/xor.rs @@ -61,7 +61,7 @@ impl Encryptor for XorCipher { #[cfg(test)] mod tests { use crate::{ - peers::encrypt::{xor_cipher::XorCipher, Encryptor}, + peers::encrypt::{xor::XorCipher, Encryptor}, tunnel::packet_def::ZCPacket, }; diff --git a/easytier/src/peers/peer_session.rs b/easytier/src/peers/peer_session.rs index fceb0822..b69e7609 100644 --- a/easytier/src/peers/peer_session.rs +++ b/easytier/src/peers/peer_session.rs @@ -8,17 +8,17 @@ use std::{ use atomic_shim::AtomicU64; +use crate::{ + common::PeerId, + peers::encrypt::{create_encryptor, Encryptor}, + tunnel::packet_def::{StandardAeadTail, ZCPacket}, +}; use anyhow::anyhow; use dashmap::DashMap; use hmac::{Hmac, Mac as _}; use rand::RngCore as _; use sha2::Sha256; - -use crate::{ - common::PeerId, - peers::encrypt::{create_encryptor, Encryptor}, - tunnel::packet_def::{AesGcmTail, ZCPacket}, -}; +use zerocopy::FromBytes; type HmacSha256 = Hmac; pub struct UpsertResponderSessionReturn { @@ -733,14 +733,8 @@ impl PeerSession { } fn parse_tail(payload: &[u8]) -> Option<[u8; 12]> { - if payload.len() < std::mem::size_of::() { - return None; - } - let tail_off = payload.len() - std::mem::size_of::(); - let tail = &payload[tail_off..]; - let mut nonce = [0u8; 12]; - nonce.copy_from_slice(&tail[16..]); - Some(nonce) + let tail = StandardAeadTail::ref_from_suffix(payload)?; + Some(tail.nonce) } fn evict_old_rx_slots(rx: &mut [[EpochRxSlot; 2]; 2], now_ms: u64) { diff --git a/easytier/src/tunnel/packet_def.rs b/easytier/src/tunnel/packet_def.rs index c79b6543..949b3258 100644 --- a/easytier/src/tunnel/packet_def.rs +++ b/easytier/src/tunnel/packet_def.rs @@ -277,14 +277,22 @@ impl ForeignNetworkPacketHeader { } } -// reserve the space for aes tag and nonce +// reserve space for AEAD authentication tag and nonce #[repr(C, packed)] -#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug, Default)] -pub struct AesGcmTail { - pub tag: [u8; 16], - pub nonce: [u8; 12], +#[derive(AsBytes, FromBytes, FromZeroes, Clone, Debug)] +pub struct AeadTail { + pub tag: [u8; TAG_SIZE], + pub nonce: [u8; NONCE_SIZE], } -pub const AES_GCM_ENCRYPTION_RESERVED: usize = std::mem::size_of::(); + +impl AeadTail { + pub const TAG_SIZE: usize = TAG_SIZE; + pub const NONCE_SIZE: usize = NONCE_SIZE; + + pub const SIZE: usize = std::mem::size_of::(); +} + +pub type StandardAeadTail = AeadTail<16, 12>; #[derive(AsBytes, FromZeroes, Clone, Debug, Copy, PartialEq, Hash, Eq)] #[repr(u8)] @@ -315,7 +323,7 @@ impl CompressorTail { } } -pub const TAIL_RESERVED_SIZE: usize = max(AES_GCM_ENCRYPTION_RESERVED, COMPRESSOR_TAIL_SIZE); +pub const TAIL_RESERVED_SIZE: usize = max(StandardAeadTail::SIZE, COMPRESSOR_TAIL_SIZE); #[derive(Default, Debug)] pub struct ZCPacketOffsets {