diff --git a/Cargo.lock b/Cargo.lock index ab9ce07..3313f38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "aes" version = "0.8.4" @@ -28,6 +38,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -181,6 +205,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -188,9 +221,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -242,6 +285,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -419,6 +472,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "os_str_bytes" version = "6.6.1" @@ -454,6 +513,18 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -621,6 +692,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -771,6 +853,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "uuid" version = "1.7.0" @@ -791,16 +883,19 @@ name = "vmessy" version = "0.1.0" dependencies = [ "aes", + "aes-gcm", "anyhow", "cfb-mode", "clap", "const-fnv1a-hash", + "crc32fast", "hmac", "log", "md-5", "pretty_env_logger", "rand", "serde", + "sha2", "tokio", "toml", "uuid", diff --git a/Cargo.toml b/Cargo.toml index 85c59a7..39d2bdf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,6 @@ hmac = "0.12" md-5 = "0.10" aes = "0.8" rand = "0.8" +crc32fast = "1.4" +sha2 = "0.10" +aes-gcm = "0.10" diff --git a/config/config.toml b/config/config.toml index 8e70c03..90fa572 100644 --- a/config/config.toml +++ b/config/config.toml @@ -4,3 +4,4 @@ address = "0.0.0.0:1090" [outbound] address = "127.0.0.1:1094" uuid = "96850032-1b92-46e9-a4f2-b99631456894" +aead = true diff --git a/src/aead.rs b/src/aead.rs new file mode 100644 index 0000000..502dc21 --- /dev/null +++ b/src/aead.rs @@ -0,0 +1,30 @@ +use crate::vmess::VmessWriter; +use aes::cipher::{generic_array::GenericArray, BlockEncrypt, KeyInit}; +use aes::Aes128; +use md5::{Digest, Md5}; +use rand::Rng; +use tokio::io::AsyncWrite; + +impl VmessWriter { + pub(crate) fn create_auth_id(&self, time: &[u8; 8]) -> [u8; 16] { + let mut buf = [0u8; 16]; + + buf[..8].copy_from_slice(time); + + let mut salt = [0u8; 4]; + rand::thread_rng().fill(&mut salt); + buf[8..12].copy_from_slice(&salt); + + let crc = crc32fast::hash(&buf[..12]); + buf[12..].copy_from_slice(&crc.to_be_bytes()); + + let key = md5!(&self.uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21"); + let key = crate::hash::kdf(&key, &[b"AES Auth ID Encryption"]); + let cipher = Aes128::new((&key[..16]).into()); + + let mut b = GenericArray::from([0u8; 16]); + cipher.encrypt_block_b2b(&buf.into(), &mut b); + + b.into() + } +} diff --git a/src/config.rs b/src/config.rs index 3588fe5..5bbd8dc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,6 +17,7 @@ pub struct Inbound { pub struct Outbound { pub address: String, pub uuid: Uuid, + pub aead: bool, } impl Config { diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..ec258f7 --- /dev/null +++ b/src/hash.rs @@ -0,0 +1,125 @@ +use sha2::{Digest, Sha256}; + +trait Hasher { + fn clone(&self) -> Box; + fn update(&mut self, data: &[u8]); + fn finalize(&mut self) -> [u8; 32]; +} + +struct Sha256Hash(Sha256); + +impl Sha256Hash { + fn new() -> Self { + Self(Sha256::new()) + } +} + +impl Hasher for Sha256Hash { + fn clone(&self) -> Box { + Box::new(Self(self.0.clone())) + } + + fn update(&mut self, data: &[u8]) { + self.0.update(data); + } + + fn finalize(&mut self) -> [u8; 32] { + self.0.clone().finalize().into() + } +} + +struct RecursiveHash { + inner: Box, + outer: Box, + ipad: [u8; 64], + opad: [u8; 64], +} + +impl RecursiveHash { + fn new(key: &[u8], hash: Box) -> Self { + let mut ipad = [0u8; 64]; + let mut opad = [0u8; 64]; + + ipad[..key.len()].copy_from_slice(&key); + opad[..key.len()].copy_from_slice(&key); + + for b in ipad.iter_mut() { + *b ^= 0x36; + } + + for b in opad.iter_mut() { + *b ^= 0x5c; + } + + let mut inner = hash.clone(); + let outer = hash; + + inner.update(&ipad); + Self { + inner, + outer, + ipad, + opad, + } + } +} + +impl Hasher for RecursiveHash { + fn clone(&self) -> Box { + let inner = self.inner.clone(); + let outer = self.outer.clone(); + let ipad = self.ipad.clone(); + let opad = self.opad.clone(); + + Box::new(Self { + inner, + outer, + ipad, + opad, + }) + } + + fn update(&mut self, data: &[u8]) { + self.inner.update(data); + } + + fn finalize(&mut self) -> [u8; 32] { + let result: [u8; 32] = self.inner.finalize().into(); + self.outer.update(&self.opad); + self.outer.update(&result); + self.outer.finalize().into() + } +} + +pub fn kdf(key: &[u8], path: &[&[u8]]) -> [u8; 32] { + let mut current = Box::new(RecursiveHash::new( + b"VMess AEAD KDF", + Box::new(Sha256Hash::new()), + )); + + for p in path.into_iter() { + current = Box::new(RecursiveHash::new(p, current)); + } + + current.update(key); + current.finalize() +} + +#[cfg(test)] +mod tests { + use super::*; + use md5::Md5; + + #[test] + fn test_kdf() { + let uuid = uuid::uuid!("96850032-1b92-46e9-a4f2-b99631456894").as_bytes(); + let key = md5!(&uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21"); + + let res = kdf(&key, &[b"AES Auth ID Encryption"]); + + assert_eq!( + res[..16], + [117, 82, 144, 159, 147, 65, 74, 253, 91, 74, 70, 84, 114, 118, 203, 30] + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index e1e3620..a7d9813 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #[macro_use] pub mod utils; +pub mod aead; pub mod config; +pub mod hash; pub mod proxy; pub mod vmess; diff --git a/src/proxy.rs b/src/proxy.rs index 3eba48d..131e169 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -13,7 +13,11 @@ pub async fn run(config: &Config) -> io::Result<()> { let (conn, _) = listener.accept().await?; let upstream = TcpStream::connect(&config.outbound.address).await?; - let vmess = Vmess::new(upstream, *config.outbound.uuid.as_bytes()); + let vmess = Vmess::new( + upstream, + *config.outbound.uuid.as_bytes(), + config.outbound.aead, + ); let (mut reader, mut writer) = conn.into_split(); let (mut ureader, mut uwriter) = vmess.into_split(); diff --git a/src/utils.rs b/src/utils.rs index 8325366..7bbb0c7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,8 @@ use tokio::io::{Error, ErrorKind, Result}; +pub type Aes128CfbEnc = cfb_mode::Encryptor; +pub type Aes128CfbDec = cfb_mode::BufDecryptor; + #[macro_export] macro_rules! copy { ($r:ident, $w:ident) => { @@ -23,14 +26,25 @@ macro_rules! copy { }; } +macro_rules! sha256 { + ( $($v:expr),+ ) => { + { + let mut hash = Sha256::new(); + $( + hash.update($v); + )* + hash.finalize() + } + } +} + macro_rules! md5 { - ( $($v:expr ),+) => { + ( $($v:expr),+ ) => { { let mut hash = Md5::new(); $( hash.update($v); )* - hash.finalize() } } diff --git a/src/vmess.rs b/src/vmess.rs index db3481e..dd6c470 100644 --- a/src/vmess.rs +++ b/src/vmess.rs @@ -1,13 +1,20 @@ -use crate::utils::extract_host; +use crate::utils::{extract_host, Aes128CfbDec, Aes128CfbEnc}; use std::marker::Unpin; use std::time::{SystemTime, UNIX_EPOCH}; -use aes::cipher::{AsyncStreamCipher, KeyIvInit}; +use aes::cipher::{AsyncStreamCipher, KeyInit, KeyIvInit}; +use aes_gcm::{ + aead::{Aead, Payload}, + Aes128Gcm, +}; + use const_fnv1a_hash::fnv1a_hash_32; use hmac::{Hmac, Mac}; use md5::{Digest, Md5}; + use rand::Rng; +use sha2::Sha256; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, Result}, net::{ @@ -16,9 +23,6 @@ use tokio::{ }, }; -type Aes128CfbEnc = cfb_mode::Encryptor; -type Aes128CfbDec = cfb_mode::BufDecryptor; - #[derive(Clone, Copy)] pub struct Encoder { pub iv: [u8; 16], @@ -40,12 +44,14 @@ impl Encoder { pub struct Vmess { stream: Option, uuid: [u8; 16], + aead: bool, } impl Vmess { - pub fn new(stream: TcpStream, uuid: [u8; 16]) -> Self { + pub fn new(stream: TcpStream, uuid: [u8; 16], aead: bool) -> Self { Self { uuid, + aead, stream: Some(stream), } } @@ -55,12 +61,17 @@ impl Vmess { let (reader, writer) = stream.into_split(); let encoder = Encoder::new(); - let r = VmessReader { reader, encoder }; + let r = VmessReader { + reader, + encoder, + aead: self.aead, + }; let w = VmessWriter { encoder, writer, uuid: self.uuid, handshaked: false, + aead: self.aead, }; (r, w) @@ -70,68 +81,97 @@ impl Vmess { pub struct VmessReader { reader: R, encoder: Encoder, + aead: bool, } impl VmessReader { pub async fn read(&mut self, buf: &mut [u8]) -> Result { - // The header data is encrypted using AES-128-CFB encryption - // The IV is MD5 of the data encryption IV, and the Key is MD5 of the data encryption Key - // - // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ - // | 1 Byte | 1 Byte | 1 Byte | 1 Byte | M Bytes | Remaining Part | - // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ - // | Response Authentication V | Option Opt | Command Cmd | Command Length M | Command Content | Actual Response Data | - // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ - - let key = md5!(&self.encoder.key); - let iv = md5!(&self.encoder.iv); - let mut decoder = Aes128CfbDec::new(&key.into(), &iv.into()); - - let mut header = [0u8; 4]; - self.reader.read_exact(&mut header).await?; - decoder.decrypt(&mut header); // ignore the header for now - // just decrypt it because our decoder is stateful - - // https://xtls.github.io/en/development/protocols/vmess.html#data-section - // - // +----------+-------------+ - // | 2 Bytes | L Bytes | - // +----------+-------------+ - // | Length L | Data Packet | - // +----------+-------------+ - // - // - Length L: A big-endian integer with a maximum value of 2^14 - // - Packet: A data packet encrypted by the specified encryption method - - // AES-128-CFB: - // The entire data section is encrypted using AES-128-CFB - // - 4 bytes: FNV1a hash of actual data - // - L - 4 bytes: actual data - let mut length = [0u8; 2]; - self.reader.read_exact(&mut length).await?; - decoder.decrypt(&mut length); - - // When Opt(M) is enabled, the value of L is equal to the true value xor Mask - // Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte() - let length = (length[0] as usize) << 8 | (length[1] as usize) - 4; // 4bytes checksum - - let mut checksum = [0u8; 4]; - self.reader.read(&mut checksum).await?; - decoder.decrypt(&mut checksum); // ignore the checksum for now - // just decrypt it because our decoder is stateful - - self.reader.read(&mut buf[..length]).await?; - decoder.decrypt(&mut buf[..length]); - - Ok(length) + if !self.aead { + // The header data is encrypted using AES-128-CFB encryption + // The IV is MD5 of the data encryption IV, and the Key is MD5 of the data encryption Key + // + // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ + // | 1 Byte | 1 Byte | 1 Byte | 1 Byte | M Bytes | Remaining Part | + // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ + // | Response Authentication V | Option Opt | Command Cmd | Command Length M | Command Content | Actual Response Data | + // +---------------------------+------------+-------------+------------------+-----------------+----------------------+ + + let key = md5!(&self.encoder.key); + let iv = md5!(&self.encoder.iv); + let mut decoder = Aes128CfbDec::new(&key.into(), &iv.into()); + + let mut header = [0u8; 4]; + self.reader.read_exact(&mut header).await?; + decoder.decrypt(&mut header); // ignore the header for now + // just decrypt it because our decoder is stateful + + // https://xtls.github.io/en/development/protocols/vmess.html#data-section + // + // +----------+-------------+ + // | 2 Bytes | L Bytes | + // +----------+-------------+ + // | Length L | Data Packet | + // +----------+-------------+ + // + // - Length L: A big-endian integer with a maximum value of 2^14 + // - Packet: A data packet encrypted by the specified encryption method + + // AES-128-CFB: + // The entire data section is encrypted using AES-128-CFB + // - 4 bytes: FNV1a hash of actual data + // - L - 4 bytes: actual data + let mut length = [0u8; 2]; + self.reader.read_exact(&mut length).await?; + decoder.decrypt(&mut length); + + // When Opt(M) is enabled, the value of L is equal to the true value xor Mask + // Mask = (RequestMask.NextByte() << 8) + RequestMask.NextByte() + let length = (length[0] as usize) << 8 | (length[1] as usize) - 4; // 4bytes checksum + + let mut checksum = [0u8; 4]; + self.reader.read(&mut checksum).await?; + decoder.decrypt(&mut checksum); // ignore the checksum for now + // just decrypt it because our decoder is stateful + + self.reader.read(&mut buf[..length]).await?; + decoder.decrypt(&mut buf[..length]); + + Ok(length) + } else { + let key = &sha256!(&self.encoder.key)[..16]; + let iv = &sha256!(&self.encoder.iv)[..16]; + + let length_key = &crate::hash::kdf(&key, &[b"AEAD Resp Header Len Key"])[..16]; + let length_iv = &crate::hash::kdf(&iv, &[b"AEAD Resp Header Len IV"])[..12]; + + let mut header_length = [0u8; 18]; + self.reader.read_exact(&mut header_length).await?; + + // header length + let length = Aes128Gcm::new(length_key.into()) + .decrypt(length_iv.into(), &header_length[..]) + .unwrap(); + let length = ((length[0] as u16) << 8) | (length[1] as u16) + 16; // TODO: document + + let mut header = vec![0u8; length as usize]; + self.reader.read_exact(&mut header).await?; // ignore the header for now + + // read next 2bytes to retrive the payload length + let mut length = [0u8; 2]; + self.reader.read(&mut length).await?; + let length = ((length[0] as usize) << 8) | (length[1] as usize); + + self.reader.read(&mut buf[..length]).await + } } } pub struct VmessWriter { - writer: W, - encoder: Encoder, - uuid: [u8; 16], - handshaked: bool, + pub(crate) writer: W, + pub(crate) encoder: Encoder, + pub(crate) uuid: [u8; 16], + pub(crate) handshaked: bool, + pub(crate) aead: bool, } impl VmessWriter { @@ -155,11 +195,12 @@ impl VmessWriter { .as_secs() .to_be_bytes(); - let mut hash = Hmac::::new_from_slice(&self.uuid).unwrap(); // safe to unwrap: always valid length - hash.update(&time); - - let auth = hash.finalize().into_bytes(); - self.writer.write_all(&auth).await?; + if !self.aead { + let mut hash = as KeyInit>::new_from_slice(&self.uuid).unwrap(); // safe to unwrap: always valid length. + hash.update(&time); + let auth = hash.finalize().into_bytes(); + self.writer.write_all(&auth).await?; + } // https://xtls.github.io/en/development/protocols/vmess.html#command-section // @@ -174,12 +215,17 @@ impl VmessWriter { cmd.extend_from_slice(&self.encoder.iv); // Data Encryption IV cmd.extend_from_slice(&self.encoder.key); // Data Encryption Key + let enc_method = if !self.aead { + 0x00 // AES-128-CFB + } else { + 0x05 // None + }; cmd.extend_from_slice(&[ - 0x00, // Response Authentication Value - 0x01, // Option S(0x01): Standard format data stream (recommended) - 0x00, // 4bits Reserved + Encryption Method (0x00 AES-128-CFB) - 0x00, // 1byte Reserved - 0x01, // Command: 0x01 TCP + 0x00, // Response Authentication Value + 0x01, // Option S(0x01): Standard format data stream (recommended) + enc_method, // 4bits Reserved + Encryption Method + 0x00, // 1byte Reserved + 0x01, // Command: 0x01 TCP ]); // TODO: extract port from request. for now we use 80 for all requests @@ -197,14 +243,57 @@ impl VmessWriter { let checksum = fnv1a_hash_32(&cmd, None); cmd.extend_from_slice(&checksum.to_be_bytes()); // 4bytes checksum - // encrypted using AES-128-CFB - // Key: MD5(user ID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21')) - // IV: MD5(X + X + X + X), X = []byte(time generated by authentication information) (8 bytes, Big Endian) let iv = md5!(&time, &time, &time, &time); let key = md5!(&self.uuid, b"c48619fe-8f02-49e0-b9e9-edf763e17e21"); - Aes128CfbEnc::new(&key.into(), &iv.into()).encrypt(&mut cmd); - self.writer.write_all(&cmd).await + if !self.aead { + // Non-AEAD + // encrypted using AES-128-CFB + // Key: MD5(user ID + []byte('c48619fe-8f02-49e0-b9e9-edf763e17e21')) + // IV: MD5(X + X + X + X), X = []byte(time generated by authentication information) (8 bytes, Big Endian) + Aes128CfbEnc::new(&key.into(), &iv.into()).encrypt(&mut cmd); + self.writer.write_all(&cmd).await + } else { + // AEAD + let auth_id = self.create_auth_id(&time); + + let mut nonce = [0u8; 8]; + rand::thread_rng().fill(&mut nonce); + + // header length + let payload = Payload { + msg: &(cmd.len() as u16).to_be_bytes(), + aad: &auth_id, + }; + let header_length_key = + &crate::hash::kdf(&key, &[b"VMess Header AEAD Key_Length", &auth_id, &nonce])[..16]; + let header_length_nonce = + &crate::hash::kdf(&key, &[b"VMess Header AEAD Nonce_Length", &auth_id, &nonce]) + [..12]; + + let header_length = Aes128Gcm::new(header_length_key.into()) + .encrypt(header_length_nonce.into(), payload) + .unwrap(); // TODO: unwrap + + // header payload + let payload = Payload { + msg: &cmd, + aad: &auth_id, + }; + let header_payload_key = + &crate::hash::kdf(&key, &[b"VMess Header AEAD Key", &auth_id, &nonce])[..16]; + let header_payload_nonce = + &crate::hash::kdf(&key, &[b"VMess Header AEAD Nonce", &auth_id, &nonce])[..12]; + + let header_payload = Aes128Gcm::new(header_payload_key.into()) + .encrypt(header_payload_nonce.into(), payload) + .unwrap(); + + self.writer.write_all(&auth_id).await?; + self.writer.write_all(&header_length).await?; + self.writer.write_all(&nonce).await?; + self.writer.write_all(&header_payload).await + } } pub async fn write(&mut self, buf: &[u8]) -> Result<()> { @@ -232,17 +321,21 @@ impl VmessWriter { // - 4 bytes: FNV1a hash of actual data // - L - 4 bytes: actual data let mut vmess_buf = Vec::new(); - { - let length = buf.len() as u16 + 4; + + let length = buf.len() as u16; + if !self.aead { let checksum = fnv1a_hash_32(&buf, None); - vmess_buf.extend_from_slice(&length.to_be_bytes()); + vmess_buf.extend_from_slice(&(length + 4).to_be_bytes()); // 4bytes fnv1a vmess_buf.extend_from_slice(&checksum.to_be_bytes()); vmess_buf.extend_from_slice(buf); - } - Aes128CfbEnc::new(&self.encoder.key.into(), &self.encoder.iv.into()) - .encrypt(&mut vmess_buf); + Aes128CfbEnc::new(&self.encoder.key.into(), &self.encoder.iv.into()) + .encrypt(&mut vmess_buf); + } else { + vmess_buf.extend_from_slice(&length.to_be_bytes()); + vmess_buf.extend_from_slice(buf); + } self.writer.write_all(&vmess_buf).await } @@ -287,6 +380,7 @@ mod tests { let mut vwriter = VmessWriter { writer: w, handshaked: false, + aead: false, uuid, encoder, };