feat(encrypt): Add XOR and ChaCha20 encryption with low-end device optimization and openssl support. (#1186)

Add ChaCha20 XOR algorithm, extend AES-GCM-256 capabilities, and integrate OpenSSL support.

---------

Co-authored-by: Sijie.Sun <sunsijie@buaa.edu.cn>
This commit is contained in:
CyiceK
2025-08-09 18:53:55 +08:00
committed by GitHub
parent 7de4b33dd1
commit 0087ac3ffc
13 changed files with 720 additions and 31 deletions

11
Cargo.lock generated
View File

@@ -2033,6 +2033,7 @@ dependencies = [
"network-interface",
"nix 0.29.0",
"once_cell",
"openssl",
"parking_lot",
"percent-encoding",
"petgraph 0.8.1",
@@ -5234,6 +5235,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.5.2+3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4"
dependencies = [
"cc",
]
[[package]]
name = "openssl-sys"
version = "0.9.103"
@@ -5242,6 +5252,7 @@ checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6"
dependencies = [
"cc",
"libc",
"openssl-src",
"pkg-config",
"vcpkg",
]

View File

@@ -150,6 +150,7 @@ boringtun = { package = "boringtun-easytier", version = "0.6.1", optional = true
ring = { version = "0.17", optional = true }
bitflags = "2.5"
aes-gcm = { version = "0.10.3", optional = true }
openssl = { version = "0.10", optional = true, features = ["vendored"] }
# for cli
tabled = "0.16"
@@ -298,6 +299,7 @@ full = [
"websocket",
"wireguard",
"aes-gcm",
"openssl-crypto", # need openssl-dev libs
"smoltcp",
"tun",
"socks5",
@@ -306,6 +308,7 @@ wireguard = ["dep:boringtun", "dep:ring"]
quic = ["dep:quinn", "dep:rustls", "dep:rcgen"]
mimalloc = ["dep:mimalloc"]
aes-gcm = ["dep:aes-gcm"]
openssl-crypto = ["dep:openssl"]
tun = ["dep:tun"]
websocket = [
"dep:tokio-websockets",

View File

@@ -95,6 +95,9 @@ core_clap:
disable_encryption:
en: "disable encryption for peers communication, default is false, must be same with peers"
zh-CN: "禁用对等节点通信的加密默认为false必须与对等节点相同"
encryption_algorithm:
en: "encryption algorithm to use, supported: '', 'xor', 'chacha20', 'aes-gcm', 'aes-gcm-256', 'openssl-aes128-gcm', 'openssl-aes256-gcm', 'openssl-chacha20'. Empty string means default (aes-gcm)"
zh-CN: "要使用的加密算法,支持:''默认aes-gcm、'xor'、'chacha20'、'aes-gcm'、'aes-gcm-256'、'openssl-aes128-gcm'、'openssl-aes256-gcm'、'openssl-chacha20'"
multi_thread:
en: "use multi-thread runtime, default is single-thread"
zh-CN: "使用多线程运行时,默认为单线程"

View File

@@ -48,9 +48,79 @@ pub fn gen_default_flags() -> Flags {
disable_quic_input: false,
foreign_relay_bps_limit: u64::MAX,
multi_thread_count: 2,
encryption_algorithm: "".to_string(), // 空字符串表示使用默认的 AES-GCM
}
}
pub enum EncryptionAlgorithm {
AesGcm,
Aes256Gcm,
Xor,
#[cfg(feature = "wireguard")]
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 TryFrom<&str> for EncryptionAlgorithm {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
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;

View File

@@ -296,6 +296,29 @@ impl GlobalCtx {
key
}
pub fn get_256_key(&self) -> [u8; 32] {
let mut key = [0u8; 32];
let secret = self
.config
.get_network_identity()
.network_secret
.unwrap_or_default();
// fill key according to network secret
let mut hasher = DefaultHasher::new();
hasher.write(secret.as_bytes());
hasher.write(b"easytier-256bit-key"); // 添加固定盐值以区分128位和256位密钥
// 生成32字节密钥
for i in 0..4 {
let chunk_start = i * 8;
let chunk_end = chunk_start + 8;
hasher.write(&key[0..chunk_start]);
hasher.write(&[i as u8]); // 添加索引以确保每个8字节块都不同
key[chunk_start..chunk_end].copy_from_slice(&hasher.finish().to_be_bytes());
}
key
}
pub fn enable_exit_node(&self) -> bool {
self.enable_exit_node
}

View File

@@ -18,8 +18,9 @@ use clap_complete::Shell;
use easytier::{
common::{
config::{
ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, LoggingConfigLoader,
NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader, VpnPortalConfig,
get_avaliable_encrypt_methods, ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig,
LoggingConfigLoader, NetworkIdentity, PeerConfig, PortForwardConfig, TomlConfigLoader,
VpnPortalConfig,
},
constants::EASYTIER_VERSION,
global_ctx::GlobalCtx,
@@ -283,6 +284,15 @@ struct NetworkOptions {
)]
disable_encryption: Option<bool>,
#[arg(
long,
env = "ET_ENCRYPTION_ALGORITHM",
help = t!("core_clap.encryption_algorithm").to_string(),
default_value = "aes-gcm",
value_parser = get_avaliable_encrypt_methods()
)]
encryption_algorithm: Option<String>,
#[arg(
long,
env = "ET_MULTI_THREAD",
@@ -846,6 +856,9 @@ impl NetworkOptions {
if let Some(v) = self.disable_encryption {
f.enable_encryption = !v;
}
if let Some(algorithm) = &self.encryption_algorithm {
f.encryption_algorithm = algorithm.clone();
}
if let Some(v) = self.disable_ipv6 {
f.enable_ipv6 = !v;
}
@@ -894,7 +907,9 @@ impl NetworkOptions {
.unwrap_or(f.foreign_relay_bps_limit);
f.multi_thread_count = self.multi_thread_count.unwrap_or(f.multi_thread_count);
f.disable_relay_kcp = self.disable_relay_kcp.unwrap_or(f.disable_relay_kcp);
f.enable_relay_foreign_network_kcp = self.enable_relay_foreign_network_kcp.unwrap_or(f.enable_relay_foreign_network_kcp);
f.enable_relay_foreign_network_kcp = self
.enable_relay_foreign_network_kcp
.unwrap_or(f.enable_relay_foreign_network_kcp);
cfg.set_flags(f);
if !self.exit_nodes.is_empty() {

View File

@@ -1,11 +1,21 @@
use crate::tunnel::packet_def::ZCPacket;
use std::sync::Arc;
use crate::{common::config::EncryptionAlgorithm, tunnel::packet_def::ZCPacket};
#[cfg(feature = "wireguard")]
pub mod ring_aes_gcm;
#[cfg(feature = "wireguard")]
pub mod ring_chacha20;
#[cfg(feature = "aes-gcm")]
pub mod aes_gcm;
#[cfg(feature = "openssl-crypto")]
pub mod openssl_cipher;
pub mod xor_cipher;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("packet is too short. len: {0}")]
@@ -39,3 +49,70 @@ impl Encryptor for NullCipher {
}
}
}
/// Create an encryptor based on the algorithm name
pub fn create_encryptor(
algorithm: &str,
key_128: [u8; 16],
key_256: [u8; 32],
) -> Arc<dyn Encryptor> {
let algorithm = match EncryptionAlgorithm::try_from(algorithm) {
Ok(algorithm) => algorithm,
Err(_) => {
eprintln!(
"Unknown encryption algorithm: {}, falling back to default AES-GCM",
algorithm
);
EncryptionAlgorithm::AesGcm
}
};
match algorithm {
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"
);
}
}
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))
}
}
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))
}
}
}

View File

@@ -0,0 +1,241 @@
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::<OpenSslTail>();
#[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> {
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();
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 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_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
#[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_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}

View File

@@ -0,0 +1,125 @@
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::<ChaCha20Poly1305Tail>();
#[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.clone());
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> {
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();
rand::thread_rng().fill_bytes(&mut tail.nonce);
let nonce = Nonce::assume_unique_for_key(tail.nonce.clone());
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 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_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}

View File

@@ -0,0 +1,86 @@
use crate::tunnel::packet_def::ZCPacket;
use super::{Encryptor, Error};
// XOR 加密不需要额外的尾部数据,因为它是对称的
pub const XOR_ENCRYPTION_RESERVED: usize = 0;
#[derive(Clone)]
pub struct XorCipher {
pub(crate) key: Vec<u8>,
}
impl XorCipher {
pub fn new(key: &[u8]) -> Self {
if key.is_empty() {
panic!("XOR key cannot be empty");
}
Self { key: key.to_vec() }
}
fn xor_data(&self, data: &mut [u8]) {
for (i, byte) in data.iter_mut().enumerate() {
*byte ^= self.key[i % self.key.len()];
}
}
}
impl Encryptor for XorCipher {
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(());
}
// XOR 解密XOR是对称的加密和解密操作相同
self.xor_data(zc_packet.mut_payload());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(false);
Ok(())
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> 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(());
}
// XOR 加密
self.xor_data(zc_packet.mut_payload());
let pm_header = zc_packet.mut_peer_manager_header().unwrap();
pm_header.set_encrypted(true);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
peers::encrypt::{xor_cipher::XorCipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
#[test]
fn test_xor_cipher() {
let key = b"test_key_123456";
let cipher = XorCipher::new(key);
let text = b"Hello, World! This is a test message.";
let mut packet = ZCPacket::new_with_payload(text);
packet.fill_peer_manager_hdr(0, 0, 0);
// 加密
cipher.encrypt(&mut packet).unwrap();
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), true);
assert_ne!(packet.payload(), text); // 加密后数据应该不同
// 解密
cipher.decrypt(&mut packet).unwrap();
assert_eq!(packet.payload(), text);
assert_eq!(packet.peer_manager_header().unwrap().is_encrypted(), false);
}
}

View File

@@ -71,7 +71,7 @@ struct RpcTransport {
packet_recv: Mutex<UnboundedReceiver<ZCPacket>>,
peer_rpc_tspt_sender: UnboundedSender<ZCPacket>,
encryptor: Arc<Box<dyn Encryptor>>,
encryptor: Arc<dyn Encryptor>,
}
#[async_trait::async_trait]
@@ -147,7 +147,7 @@ pub struct PeerManager {
foreign_network_manager: Arc<ForeignNetworkManager>,
foreign_network_client: Arc<ForeignNetworkClient>,
encryptor: Arc<Box<dyn Encryptor>>,
encryptor: Arc<dyn Encryptor + 'static>,
data_compress_algo: CompressorAlgo,
exit_nodes: Vec<IpAddr>,
@@ -184,25 +184,18 @@ impl PeerManager {
my_peer_id,
));
let mut encryptor: Arc<Box<dyn Encryptor>> = Arc::new(Box::new(NullCipher));
if global_ctx.get_flags().enable_encryption {
#[cfg(feature = "wireguard")]
{
use super::encrypt::ring_aes_gcm::AesGcmCipher;
encryptor = Arc::new(Box::new(AesGcmCipher::new_128(global_ctx.get_128_key())));
}
#[cfg(all(feature = "aes-gcm", not(feature = "wireguard")))]
{
use super::encrypt::aes_gcm::AesGcmCipher;
encryptor = Arc::new(Box::new(AesGcmCipher::new_128(global_ctx.get_128_key())));
}
#[cfg(all(not(feature = "wireguard"), not(feature = "aes-gcm")))]
{
compile_error!("wireguard or aes-gcm feature must be enabled for encryption");
}
}
let encryptor = if global_ctx.get_flags().enable_encryption {
// 只有在启用加密时才使用工厂函数选择算法
let algorithm = &global_ctx.get_flags().encryption_algorithm;
super::encrypt::create_encryptor(
algorithm,
global_ctx.get_128_key(),
global_ctx.get_256_key(),
)
} else {
// disable_encryption = true 时使用 NullCipher
Arc::new(NullCipher)
};
if global_ctx
.check_network_in_whitelist(&global_ctx.get_network_name())
@@ -1110,7 +1103,7 @@ impl PeerManager {
pub async fn try_compress_and_encrypt(
compress_algo: CompressorAlgo,
encryptor: &Box<dyn Encryptor>,
encryptor: &Arc<dyn Encryptor + 'static>,
msg: &mut ZCPacket,
) -> Result<(), Error> {
let compressor = DefaultCompressor {};
@@ -1375,9 +1368,12 @@ impl PeerManager {
return false;
}
let next_hop_policy = Self::get_next_hop_policy( self.global_ctx.get_flags().latency_first);
let next_hop_policy = Self::get_next_hop_policy(self.global_ctx.get_flags().latency_first);
// check relay node allow relay kcp.
let Some(next_hop_id) = route.get_next_hop_with_policy(dst_peer_id, next_hop_policy).await else {
let Some(next_hop_id) = route
.get_next_hop_with_policy(dst_peer_id, next_hop_policy)
.await
else {
return false;
};
@@ -1386,7 +1382,11 @@ impl PeerManager {
};
// check next hop allow kcp relay
if next_hop_info.feature_flag.map(|x| x.no_relay_kcp).unwrap_or(false) {
if next_hop_info
.feature_flag
.map(|x| x.no_relay_kcp)
.unwrap_or(false)
{
return false;
}

View File

@@ -49,6 +49,9 @@ message FlagsInConfig {
// enable relay foreign network kcp packets
bool enable_relay_foreign_network_kcp = 28;
// encryption algorithm to use, empty string means default (aes-gcm)
string encryption_algorithm = 29;
}
message RpcDescriptor {

View File

@@ -268,8 +268,40 @@ async fn ping6_test(from_netns: &str, target_ip: &str, payload_size: Option<usiz
#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
pub async fn basic_three_node_test(#[values("tcp", "udp", "wg", "ws", "wss")] proto: &str) {
let insts = init_three_node(proto).await;
pub async fn basic_three_node_test(
#[values("tcp", "udp", "wg", "ws", "wss")] proto: &str,
#[values(
["aes-gcm", "aes-gcm"],
["aes-256-gcm", "aes-256-gcm"],
["chacha20", "chacha20"],
["xor", "xor"],
["openssl-chacha20", "openssl-chacha20"],
["openssl-aes-gcm", "openssl-aes-gcm"],
["openssl-aes-256-gcm", "openssl-aes-256-gcm"],
["aes-gcm", "openssl-aes-gcm"],
["openssl-aes-gcm", "aes-gcm"],
["aes-256-gcm", "openssl-aes-256-gcm"],
["openssl-aes-256-gcm", "aes-256-gcm"],
["chacha20", "openssl-chacha20"],
["openssl-chacha20", "chacha20"],
)]
encrypt_algorithm_pair: [&str; 2],
) {
let insts = init_three_node_ex(
proto,
|cfg| {
let mut flags = cfg.get_flags();
if cfg.get_inst_name() == "inst0" {
flags.encryption_algorithm = encrypt_algorithm_pair[0].to_string();
} else {
flags.encryption_algorithm = encrypt_algorithm_pair[1].to_string();
}
cfg.set_flags(flags);
cfg
},
false,
)
.await;
check_route(
"10.144.144.2/24",