mirror of
https://mirror.suhoan.cn/https://github.com/EasyTier/EasyTier.git
synced 2025-12-12 12:47:25 +08:00
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:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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: "使用多线程运行时,默认为单线程"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
241
easytier/src/peers/encrypt/openssl_cipher.rs
Normal file
241
easytier/src/peers/encrypt/openssl_cipher.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
125
easytier/src/peers/encrypt/ring_chacha20.rs
Normal file
125
easytier/src/peers/encrypt/ring_chacha20.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
86
easytier/src/peers/encrypt/xor_cipher.rs
Normal file
86
easytier/src/peers/encrypt/xor_cipher.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user