This commit is contained in:
Alexey
2026-03-21 15:45:29 +03:00
parent 7a8f946029
commit d7bbb376c9
154 changed files with 6194 additions and 3775 deletions

View File

@@ -13,10 +13,13 @@
#![allow(dead_code)]
use aes::Aes256;
use ctr::{Ctr128BE, cipher::{KeyIvInit, StreamCipher}};
use zeroize::Zeroize;
use crate::error::{ProxyError, Result};
use aes::Aes256;
use ctr::{
Ctr128BE,
cipher::{KeyIvInit, StreamCipher},
};
use zeroize::Zeroize;
type Aes256Ctr = Ctr128BE<Aes256>;
@@ -42,33 +45,39 @@ impl AesCtr {
cipher: Aes256Ctr::new(key.into(), (&iv_bytes).into()),
}
}
/// Create from key and IV slices
pub fn from_key_iv(key: &[u8], iv: &[u8]) -> Result<Self> {
if key.len() != 32 {
return Err(ProxyError::InvalidKeyLength { expected: 32, got: key.len() });
return Err(ProxyError::InvalidKeyLength {
expected: 32,
got: key.len(),
});
}
if iv.len() != 16 {
return Err(ProxyError::InvalidKeyLength { expected: 16, got: iv.len() });
return Err(ProxyError::InvalidKeyLength {
expected: 16,
got: iv.len(),
});
}
let key: [u8; 32] = key.try_into().unwrap();
let iv = u128::from_be_bytes(iv.try_into().unwrap());
Ok(Self::new(&key, iv))
}
/// Encrypt/decrypt data in-place (CTR mode is symmetric)
pub fn apply(&mut self, data: &mut [u8]) {
self.cipher.apply_keystream(data);
}
/// Encrypt data, returning new buffer
pub fn encrypt(&mut self, data: &[u8]) -> Vec<u8> {
let mut output = data.to_vec();
self.apply(&mut output);
output
}
/// Decrypt data (for CTR, identical to encrypt)
pub fn decrypt(&mut self, data: &[u8]) -> Vec<u8> {
self.encrypt(data)
@@ -99,27 +108,33 @@ impl Drop for AesCbc {
impl AesCbc {
/// AES block size
const BLOCK_SIZE: usize = 16;
/// Create new AES-CBC cipher with key and IV
pub fn new(key: [u8; 32], iv: [u8; 16]) -> Self {
Self { key, iv }
}
/// Create from slices
pub fn from_slices(key: &[u8], iv: &[u8]) -> Result<Self> {
if key.len() != 32 {
return Err(ProxyError::InvalidKeyLength { expected: 32, got: key.len() });
return Err(ProxyError::InvalidKeyLength {
expected: 32,
got: key.len(),
});
}
if iv.len() != 16 {
return Err(ProxyError::InvalidKeyLength { expected: 16, got: iv.len() });
return Err(ProxyError::InvalidKeyLength {
expected: 16,
got: iv.len(),
});
}
Ok(Self {
key: key.try_into().unwrap(),
iv: iv.try_into().unwrap(),
})
}
/// Encrypt a single block using raw AES (no chaining)
fn encrypt_block(&self, block: &[u8; 16], key_schedule: &aes::Aes256) -> [u8; 16] {
use aes::cipher::BlockEncrypt;
@@ -127,7 +142,7 @@ impl AesCbc {
key_schedule.encrypt_block((&mut output).into());
output
}
/// Decrypt a single block using raw AES (no chaining)
fn decrypt_block(&self, block: &[u8; 16], key_schedule: &aes::Aes256) -> [u8; 16] {
use aes::cipher::BlockDecrypt;
@@ -135,7 +150,7 @@ impl AesCbc {
key_schedule.decrypt_block((&mut output).into());
output
}
/// XOR two 16-byte blocks
fn xor_blocks(a: &[u8; 16], b: &[u8; 16]) -> [u8; 16] {
let mut result = [0u8; 16];
@@ -144,27 +159,28 @@ impl AesCbc {
}
result
}
/// Encrypt data using CBC mode with proper chaining
///
/// CBC Encryption: C[i] = AES_Encrypt(P[i] XOR C[i-1]), where C[-1] = IV
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
if !data.len().is_multiple_of(Self::BLOCK_SIZE) {
return Err(ProxyError::Crypto(
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
));
return Err(ProxyError::Crypto(format!(
"CBC data must be aligned to 16 bytes, got {}",
data.len()
)));
}
if data.is_empty() {
return Ok(Vec::new());
}
use aes::cipher::KeyInit;
let key_schedule = aes::Aes256::new((&self.key).into());
let mut result = Vec::with_capacity(data.len());
let mut prev_ciphertext = self.iv;
for chunk in data.chunks(Self::BLOCK_SIZE) {
let plaintext: [u8; 16] = chunk.try_into().unwrap();
let xored = Self::xor_blocks(&plaintext, &prev_ciphertext);
@@ -172,30 +188,31 @@ impl AesCbc {
prev_ciphertext = ciphertext;
result.extend_from_slice(&ciphertext);
}
Ok(result)
}
/// Decrypt data using CBC mode with proper chaining
///
/// CBC Decryption: P[i] = AES_Decrypt(C[i]) XOR C[i-1], where C[-1] = IV
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
if !data.len().is_multiple_of(Self::BLOCK_SIZE) {
return Err(ProxyError::Crypto(
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
));
return Err(ProxyError::Crypto(format!(
"CBC data must be aligned to 16 bytes, got {}",
data.len()
)));
}
if data.is_empty() {
return Ok(Vec::new());
}
use aes::cipher::KeyInit;
let key_schedule = aes::Aes256::new((&self.key).into());
let mut result = Vec::with_capacity(data.len());
let mut prev_ciphertext = self.iv;
for chunk in data.chunks(Self::BLOCK_SIZE) {
let ciphertext: [u8; 16] = chunk.try_into().unwrap();
let decrypted = self.decrypt_block(&ciphertext, &key_schedule);
@@ -203,75 +220,77 @@ impl AesCbc {
prev_ciphertext = ciphertext;
result.extend_from_slice(&plaintext);
}
Ok(result)
}
/// Encrypt data in-place
pub fn encrypt_in_place(&self, data: &mut [u8]) -> Result<()> {
if !data.len().is_multiple_of(Self::BLOCK_SIZE) {
return Err(ProxyError::Crypto(
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
));
return Err(ProxyError::Crypto(format!(
"CBC data must be aligned to 16 bytes, got {}",
data.len()
)));
}
if data.is_empty() {
return Ok(());
}
use aes::cipher::KeyInit;
let key_schedule = aes::Aes256::new((&self.key).into());
let mut prev_ciphertext = self.iv;
for i in (0..data.len()).step_by(Self::BLOCK_SIZE) {
let block = &mut data[i..i + Self::BLOCK_SIZE];
for j in 0..Self::BLOCK_SIZE {
block[j] ^= prev_ciphertext[j];
}
let block_array: &mut [u8; 16] = block.try_into().unwrap();
*block_array = self.encrypt_block(block_array, &key_schedule);
prev_ciphertext = *block_array;
}
Ok(())
}
/// Decrypt data in-place
pub fn decrypt_in_place(&self, data: &mut [u8]) -> Result<()> {
if !data.len().is_multiple_of(Self::BLOCK_SIZE) {
return Err(ProxyError::Crypto(
format!("CBC data must be aligned to 16 bytes, got {}", data.len())
));
return Err(ProxyError::Crypto(format!(
"CBC data must be aligned to 16 bytes, got {}",
data.len()
)));
}
if data.is_empty() {
return Ok(());
}
use aes::cipher::KeyInit;
let key_schedule = aes::Aes256::new((&self.key).into());
let mut prev_ciphertext = self.iv;
for i in (0..data.len()).step_by(Self::BLOCK_SIZE) {
let block = &mut data[i..i + Self::BLOCK_SIZE];
let current_ciphertext: [u8; 16] = block.try_into().unwrap();
let block_array: &mut [u8; 16] = block.try_into().unwrap();
*block_array = self.decrypt_block(block_array, &key_schedule);
for j in 0..Self::BLOCK_SIZE {
block[j] ^= prev_ciphertext[j];
}
prev_ciphertext = current_ciphertext;
}
Ok(())
}
}
@@ -318,227 +337,227 @@ impl Decryptor for PassthroughEncryptor {
#[cfg(test)]
mod tests {
use super::*;
// ============= AES-CTR Tests =============
#[test]
fn test_aes_ctr_roundtrip() {
let key = [0u8; 32];
let iv = 12345u128;
let original = b"Hello, MTProto!";
let mut enc = AesCtr::new(&key, iv);
let encrypted = enc.encrypt(original);
let mut dec = AesCtr::new(&key, iv);
let decrypted = dec.decrypt(&encrypted);
assert_eq!(original.as_slice(), decrypted.as_slice());
}
#[test]
fn test_aes_ctr_in_place() {
let key = [0x42u8; 32];
let iv = 999u128;
let original = b"Test data for in-place encryption";
let mut data = original.to_vec();
let mut cipher = AesCtr::new(&key, iv);
cipher.apply(&mut data);
assert_ne!(&data[..], original);
let mut cipher = AesCtr::new(&key, iv);
cipher.apply(&mut data);
assert_eq!(&data[..], original);
}
// ============= AES-CBC Tests =============
#[test]
fn test_aes_cbc_roundtrip() {
let key = [0u8; 32];
let iv = [0u8; 16];
let original = [0u8; 32];
let cipher = AesCbc::new(key, iv);
let encrypted = cipher.encrypt(&original).unwrap();
let decrypted = cipher.decrypt(&encrypted).unwrap();
assert_eq!(original.as_slice(), decrypted.as_slice());
}
#[test]
fn test_aes_cbc_chaining_works() {
let key = [0x42u8; 32];
let iv = [0x00u8; 16];
let plaintext = [0xAAu8; 32];
let cipher = AesCbc::new(key, iv);
let ciphertext = cipher.encrypt(&plaintext).unwrap();
let block1 = &ciphertext[0..16];
let block2 = &ciphertext[16..32];
assert_ne!(
block1, block2,
"CBC chaining broken: identical plaintext blocks produced identical ciphertext"
);
}
#[test]
fn test_aes_cbc_known_vector() {
let key = [0u8; 32];
let iv = [0u8; 16];
let plaintext = [0u8; 16];
let cipher = AesCbc::new(key, iv);
let ciphertext = cipher.encrypt(&plaintext).unwrap();
let decrypted = cipher.decrypt(&ciphertext).unwrap();
assert_eq!(plaintext.as_slice(), decrypted.as_slice());
assert_ne!(ciphertext.as_slice(), plaintext.as_slice());
}
#[test]
fn test_aes_cbc_multi_block() {
let key = [0x12u8; 32];
let iv = [0x34u8; 16];
let plaintext: Vec<u8> = (0..80).collect();
let cipher = AesCbc::new(key, iv);
let ciphertext = cipher.encrypt(&plaintext).unwrap();
let decrypted = cipher.decrypt(&ciphertext).unwrap();
assert_eq!(plaintext, decrypted);
}
#[test]
fn test_aes_cbc_in_place() {
let key = [0x12u8; 32];
let iv = [0x34u8; 16];
let original = [0x56u8; 48];
let mut buffer = original;
let cipher = AesCbc::new(key, iv);
cipher.encrypt_in_place(&mut buffer).unwrap();
assert_ne!(&buffer[..], &original[..]);
cipher.decrypt_in_place(&mut buffer).unwrap();
assert_eq!(&buffer[..], &original[..]);
}
#[test]
fn test_aes_cbc_empty_data() {
let cipher = AesCbc::new([0u8; 32], [0u8; 16]);
let encrypted = cipher.encrypt(&[]).unwrap();
assert!(encrypted.is_empty());
let decrypted = cipher.decrypt(&[]).unwrap();
assert!(decrypted.is_empty());
}
#[test]
fn test_aes_cbc_unaligned_error() {
let cipher = AesCbc::new([0u8; 32], [0u8; 16]);
let result = cipher.encrypt(&[0u8; 15]);
assert!(result.is_err());
let result = cipher.encrypt(&[0u8; 17]);
assert!(result.is_err());
}
#[test]
fn test_aes_cbc_avalanche_effect() {
let key = [0xAB; 32];
let iv = [0xCD; 16];
let plaintext1 = [0u8; 32];
let mut plaintext2 = [0u8; 32];
plaintext2[0] = 0x01;
let cipher = AesCbc::new(key, iv);
let ciphertext1 = cipher.encrypt(&plaintext1).unwrap();
let ciphertext2 = cipher.encrypt(&plaintext2).unwrap();
assert_ne!(&ciphertext1[0..16], &ciphertext2[0..16]);
assert_ne!(&ciphertext1[16..32], &ciphertext2[16..32]);
}
#[test]
fn test_aes_cbc_iv_matters() {
let key = [0x55; 32];
let plaintext = [0x77u8; 16];
let cipher1 = AesCbc::new(key, [0u8; 16]);
let cipher2 = AesCbc::new(key, [1u8; 16]);
let ciphertext1 = cipher1.encrypt(&plaintext).unwrap();
let ciphertext2 = cipher2.encrypt(&plaintext).unwrap();
assert_ne!(ciphertext1, ciphertext2);
}
#[test]
fn test_aes_cbc_deterministic() {
let key = [0x99; 32];
let iv = [0x88; 16];
let plaintext = [0x77u8; 32];
let cipher = AesCbc::new(key, iv);
let ciphertext1 = cipher.encrypt(&plaintext).unwrap();
let ciphertext2 = cipher.encrypt(&plaintext).unwrap();
assert_eq!(ciphertext1, ciphertext2);
}
// ============= Zeroize Tests =============
#[test]
fn test_aes_cbc_zeroize_on_drop() {
let key = [0xAA; 32];
let iv = [0xBB; 16];
let cipher = AesCbc::new(key, iv);
// Verify key/iv are set
assert_eq!(cipher.key, [0xAA; 32]);
assert_eq!(cipher.iv, [0xBB; 16]);
drop(cipher);
// After drop, key/iv are zeroized (can't observe directly,
// but the Drop impl runs without panic)
}
// ============= Error Handling Tests =============
#[test]
fn test_invalid_key_length() {
let result = AesCtr::from_key_iv(&[0u8; 16], &[0u8; 16]);
assert!(result.is_err());
let result = AesCbc::from_slices(&[0u8; 16], &[0u8; 16]);
assert!(result.is_err());
}
#[test]
fn test_invalid_iv_length() {
let result = AesCtr::from_key_iv(&[0u8; 32], &[0u8; 8]);
assert!(result.is_err());
let result = AesCbc::from_slices(&[0u8; 32], &[0u8; 8]);
assert!(result.is_err());
}
}
}

View File

@@ -12,10 +12,10 @@
//! usages are intentional and protocol-mandated.
use hmac::{Hmac, Mac};
use sha2::Sha256;
use md5::Md5;
use sha1::Sha1;
use sha2::Digest;
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
@@ -28,8 +28,7 @@ pub fn sha256(data: &[u8]) -> [u8; 32] {
/// SHA-256 HMAC
pub fn sha256_hmac(key: &[u8], data: &[u8]) -> [u8; 32] {
let mut mac = HmacSha256::new_from_slice(key)
.expect("HMAC accepts any key length");
let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().into()
}
@@ -124,27 +123,18 @@ pub fn derive_middleproxy_keys(
srv_ipv6: Option<&[u8; 16]>,
) -> ([u8; 32], [u8; 16]) {
let s = build_middleproxy_prekey(
nonce_srv,
nonce_clt,
clt_ts,
srv_ip,
clt_port,
purpose,
clt_ip,
srv_port,
secret,
clt_ipv6,
srv_ipv6,
nonce_srv, nonce_clt, clt_ts, srv_ip, clt_port, purpose, clt_ip, srv_port, secret,
clt_ipv6, srv_ipv6,
);
let md5_1 = md5(&s[1..]);
let sha1_sum = sha1(&s);
let md5_2 = md5(&s[2..]);
let mut key = [0u8; 32];
key[..12].copy_from_slice(&md5_1[..12]);
key[12..].copy_from_slice(&sha1_sum);
(key, md5_2)
}
@@ -164,17 +154,8 @@ mod tests {
let secret = vec![0x55u8; 128];
let prekey = build_middleproxy_prekey(
&nonce_srv,
&nonce_clt,
&clt_ts,
srv_ip,
&clt_port,
b"CLIENT",
clt_ip,
&srv_port,
&secret,
None,
None,
&nonce_srv, &nonce_clt, &clt_ts, srv_ip, &clt_port, b"CLIENT", clt_ip, &srv_port,
&secret, None, None,
);
let digest = sha256(&prekey);
assert_eq!(

View File

@@ -4,7 +4,7 @@ pub mod aes;
pub mod hash;
pub mod random;
pub use aes::{AesCtr, AesCbc};
pub use aes::{AesCbc, AesCtr};
pub use hash::{
build_middleproxy_prekey, crc32, crc32c, derive_middleproxy_keys, sha256, sha256_hmac,
};

View File

@@ -3,11 +3,11 @@
#![allow(deprecated)]
#![allow(dead_code)]
use rand::{Rng, RngExt, SeedableRng};
use rand::rngs::StdRng;
use parking_lot::Mutex;
use zeroize::Zeroize;
use crate::crypto::AesCtr;
use parking_lot::Mutex;
use rand::rngs::StdRng;
use rand::{Rng, RngExt, SeedableRng};
use zeroize::Zeroize;
/// Cryptographically secure PRNG with AES-CTR
pub struct SecureRandom {
@@ -34,16 +34,16 @@ impl SecureRandom {
pub fn new() -> Self {
let mut seed_source = rand::rng();
let mut rng = StdRng::from_rng(&mut seed_source);
let mut key = [0u8; 32];
rng.fill_bytes(&mut key);
let iv: u128 = rng.random();
let cipher = AesCtr::new(&key, iv);
// Zeroize local key copy — cipher already consumed it
key.zeroize();
Self {
inner: Mutex::new(SecureRandomInner {
rng,
@@ -53,7 +53,7 @@ impl SecureRandom {
}),
}
}
/// Fill a caller-provided buffer with random bytes.
pub fn fill(&self, out: &mut [u8]) {
let mut inner = self.inner.lock();
@@ -94,7 +94,7 @@ impl SecureRandom {
self.fill(&mut out);
out
}
/// Generate random number in range [0, max)
pub fn range(&self, max: usize) -> usize {
if max == 0 {
@@ -103,16 +103,16 @@ impl SecureRandom {
let mut inner = self.inner.lock();
inner.rng.random_range(0..max)
}
/// Generate random bits
pub fn bits(&self, k: usize) -> u64 {
if k == 0 {
return 0;
}
let bytes_needed = k.div_ceil(8);
let bytes = self.bytes(bytes_needed.min(8));
let mut result = 0u64;
for (i, &b) in bytes.iter().enumerate() {
if i >= 8 {
@@ -120,14 +120,14 @@ impl SecureRandom {
}
result |= (b as u64) << (i * 8);
}
if k < 64 {
result &= (1u64 << k) - 1;
}
result
}
/// Choose random element from slice
pub fn choose<'a, T>(&self, slice: &'a [T]) -> Option<&'a T> {
if slice.is_empty() {
@@ -136,7 +136,7 @@ impl SecureRandom {
Some(&slice[self.range(slice.len())])
}
}
/// Shuffle slice in place
pub fn shuffle<T>(&self, slice: &mut [T]) {
let mut inner = self.inner.lock();
@@ -145,13 +145,13 @@ impl SecureRandom {
slice.swap(i, j);
}
}
/// Generate random u32
pub fn u32(&self) -> u32 {
let mut inner = self.inner.lock();
inner.rng.random()
}
/// Generate random u64
pub fn u64(&self) -> u64 {
let mut inner = self.inner.lock();
@@ -169,7 +169,7 @@ impl Default for SecureRandom {
mod tests {
use super::*;
use std::collections::HashSet;
#[test]
fn test_bytes_uniqueness() {
let rng = SecureRandom::new();
@@ -177,7 +177,7 @@ mod tests {
let b = rng.bytes(32);
assert_ne!(a, b);
}
#[test]
fn test_bytes_length() {
let rng = SecureRandom::new();
@@ -186,63 +186,63 @@ mod tests {
assert_eq!(rng.bytes(100).len(), 100);
assert_eq!(rng.bytes(1000).len(), 1000);
}
#[test]
fn test_range() {
let rng = SecureRandom::new();
for _ in 0..1000 {
let n = rng.range(10);
assert!(n < 10);
}
assert_eq!(rng.range(1), 0);
assert_eq!(rng.range(0), 0);
}
#[test]
fn test_bits() {
let rng = SecureRandom::new();
for _ in 0..100 {
assert!(rng.bits(1) <= 1);
}
for _ in 0..100 {
assert!(rng.bits(8) <= 255);
}
}
#[test]
fn test_choose() {
let rng = SecureRandom::new();
let items = vec![1, 2, 3, 4, 5];
let mut seen = HashSet::new();
for _ in 0..1000 {
if let Some(&item) = rng.choose(&items) {
seen.insert(item);
}
}
assert_eq!(seen.len(), 5);
let empty: Vec<i32> = vec![];
assert!(rng.choose(&empty).is_none());
}
#[test]
fn test_shuffle() {
let rng = SecureRandom::new();
let original = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut shuffled = original.clone();
rng.shuffle(&mut shuffled);
let mut sorted = shuffled.clone();
sorted.sort();
assert_eq!(sorted, original);
assert_ne!(shuffled, original);
}
}