Introduce secure mode (part 1) (#1808)

Use noise protocol on handshake. Check peer's public key if needed. Also support rekey and replay attack prevention.

E2EE and temporary password will be implemented based on this.
This commit is contained in:
KKRainbow
2026-01-25 20:16:51 +08:00
committed by GitHub
parent ffa08d1c43
commit 101f416268
29 changed files with 3320 additions and 91 deletions
+56 -7
View File
@@ -84,6 +84,14 @@ impl Encryptor for AesGcmCipher {
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
self.encrypt_with_nonce(zc_packet, None)
}
fn encrypt_with_nonce(
&self,
zc_packet: &mut ZCPacket,
nonce: Option<&[u8]>,
) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
@@ -91,16 +99,28 @@ impl Encryptor for AesGcmCipher {
}
let mut tail = AesGcmTail::default();
if let Some(nonce) = nonce {
if nonce.len() != tail.nonce.len() {
return Err(Error::EncryptionFailed);
}
tail.nonce.copy_from_slice(nonce);
}
let rs = match &self.cipher {
AesGcmEnum::AES128GCM(aes_gcm) => {
let nonce = Aes128Gcm::generate_nonce(&mut OsRng);
tail.nonce.copy_from_slice(nonce.as_slice());
aes_gcm.encrypt_in_place_detached(&nonce, &[], zc_packet.mut_payload())
if nonce.is_none() {
let nonce = Aes128Gcm::generate_nonce(&mut OsRng);
tail.nonce.copy_from_slice(nonce.as_slice());
}
let nonce = Nonce::from_slice(&tail.nonce);
aes_gcm.encrypt_in_place_detached(nonce, &[], zc_packet.mut_payload())
}
AesGcmEnum::AES256GCM(aes_gcm) => {
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
tail.nonce.copy_from_slice(nonce.as_slice());
aes_gcm.encrypt_in_place_detached(&nonce, &[], zc_packet.mut_payload())
if nonce.is_none() {
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
tail.nonce.copy_from_slice(nonce.as_slice());
}
let nonce = Nonce::from_slice(&tail.nonce);
aes_gcm.encrypt_in_place_detached(nonce, &[], zc_packet.mut_payload())
}
};
@@ -122,8 +142,9 @@ impl Encryptor for AesGcmCipher {
mod tests {
use crate::{
peers::encrypt::{aes_gcm::AesGcmCipher, Encryptor},
tunnel::packet_def::{ZCPacket, AES_GCM_ENCRYPTION_RESERVED},
tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED},
};
use zerocopy::FromBytes;
#[test]
fn test_aes_gcm_cipher() {
@@ -143,4 +164,32 @@ mod tests {
assert_eq!(packet.payload(), text);
assert!(!packet.peer_manager_header().unwrap().is_encrypted());
}
#[test]
fn test_aes_gcm_cipher_with_nonce() {
let key = [7u8; 16];
let cipher = AesGcmCipher::new_128(key);
let text = b"Hello";
let nonce = [3u8; 12];
let mut packet1 = ZCPacket::new_with_payload(text);
packet1.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet1, Some(&nonce))
.unwrap();
let mut packet2 = ZCPacket::new_with_payload(text);
packet2.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet2, Some(&nonce))
.unwrap();
assert_eq!(packet1.payload(), packet2.payload());
let tail = AesGcmTail::ref_from_suffix(packet1.payload()).unwrap();
assert_eq!(tail.nonce, nonce);
cipher.decrypt(&mut packet1).unwrap();
assert_eq!(packet1.payload(), text);
}
}
+7
View File
@@ -30,6 +30,13 @@ pub enum Error {
pub trait Encryptor: Send + Sync + 'static {
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>;
fn encrypt_with_nonce(
&self,
zc_packet: &mut ZCPacket,
_nonce: Option<&[u8]>,
) -> Result<(), Error> {
self.encrypt(zc_packet)
}
fn decrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error>;
}
+48 -1
View File
@@ -142,6 +142,14 @@ impl Encryptor for OpenSslCipher {
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
self.encrypt_with_nonce(zc_packet, None)
}
fn encrypt_with_nonce(
&self,
zc_packet: &mut ZCPacket,
nonce: Option<&[u8]>,
) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
@@ -153,7 +161,14 @@ impl Encryptor for OpenSslCipher {
let nonce_size = self.get_nonce_size();
let mut tail = OpenSslTail::default();
rand::thread_rng().fill_bytes(&mut tail.nonce[..nonce_size]);
if let Some(nonce) = nonce {
if nonce.len() != nonce_size {
return Err(Error::EncryptionFailed);
}
tail.nonce[..nonce_size].copy_from_slice(nonce);
} else {
rand::thread_rng().fill_bytes(&mut tail.nonce[..nonce_size]);
}
let mut encrypter =
Crypter::new(cipher, Mode::Encrypt, key, Some(&tail.nonce[..nonce_size]))
@@ -198,6 +213,7 @@ mod tests {
peers::encrypt::{openssl_cipher::OpenSslCipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
use zerocopy::FromBytes;
use super::OPENSSL_ENCRYPTION_RESERVED;
@@ -220,6 +236,37 @@ mod tests {
assert!(!packet.peer_manager_header().unwrap().is_encrypted());
}
#[test]
fn test_openssl_aes128_gcm_with_nonce() {
let key = [7u8; 16];
let cipher = OpenSslCipher::new_aes128_gcm(key);
let text = b"Hello";
let nonce = [3u8; 12];
let mut packet1 = ZCPacket::new_with_payload(text);
packet1.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet1, Some(&nonce))
.unwrap();
let mut packet2 = ZCPacket::new_with_payload(text);
packet2.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet2, Some(&nonce))
.unwrap();
assert_eq!(packet1.payload(), packet2.payload());
assert!(packet1.payload().len() > text.len() + OPENSSL_ENCRYPTION_RESERVED);
let tail = super::OpenSslTail::ref_from_suffix(packet1.payload())
.unwrap()
.clone();
assert_eq!(&tail.nonce[..nonce.len()], nonce);
cipher.decrypt(&mut packet1).unwrap();
assert_eq!(packet1.payload(), text);
}
#[test]
fn test_openssl_chacha20() {
let key = [0u8; 32];
+46 -2
View File
@@ -93,6 +93,14 @@ impl Encryptor for AesGcmCipher {
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
self.encrypt_with_nonce(zc_packet, None)
}
fn encrypt_with_nonce(
&self,
zc_packet: &mut ZCPacket,
nonce: Option<&[u8]>,
) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
@@ -100,7 +108,14 @@ impl Encryptor for AesGcmCipher {
}
let mut tail = AesGcmTail::default();
rand::thread_rng().fill_bytes(&mut tail.nonce);
if let Some(nonce) = nonce {
if nonce.len() != tail.nonce.len() {
return Err(Error::EncryptionFailed);
}
tail.nonce.copy_from_slice(nonce);
} else {
rand::thread_rng().fill_bytes(&mut tail.nonce);
}
let nonce = aead::Nonce::assume_unique_for_key(tail.nonce);
let rs = match &self.cipher {
@@ -137,8 +152,9 @@ impl Encryptor for AesGcmCipher {
mod tests {
use crate::{
peers::encrypt::{ring_aes_gcm::AesGcmCipher, Encryptor},
tunnel::packet_def::{ZCPacket, AES_GCM_ENCRYPTION_RESERVED},
tunnel::packet_def::{AesGcmTail, ZCPacket, AES_GCM_ENCRYPTION_RESERVED},
};
use zerocopy::FromBytes;
#[test]
fn test_aes_gcm_cipher() {
@@ -158,4 +174,32 @@ mod tests {
assert_eq!(packet.payload(), text);
assert!(!packet.peer_manager_header().unwrap().is_encrypted());
}
#[test]
fn test_aes_gcm_cipher_with_nonce() {
let key = [7u8; 16];
let cipher = AesGcmCipher::new_128(key);
let text = b"Hello";
let nonce = [3u8; 12];
let mut packet1 = ZCPacket::new_with_payload(text);
packet1.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet1, Some(&nonce))
.unwrap();
let mut packet2 = ZCPacket::new_with_payload(text);
packet2.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet2, Some(&nonce))
.unwrap();
assert_eq!(packet1.payload(), packet2.payload());
let tail = AesGcmTail::ref_from_suffix(packet1.payload()).unwrap();
assert_eq!(tail.nonce, nonce);
cipher.decrypt(&mut packet1).unwrap();
assert_eq!(packet1.payload(), text);
}
}
+46 -1
View File
@@ -67,6 +67,14 @@ impl Encryptor for RingChaCha20Cipher {
}
fn encrypt(&self, zc_packet: &mut ZCPacket) -> Result<(), Error> {
self.encrypt_with_nonce(zc_packet, None)
}
fn encrypt_with_nonce(
&self,
zc_packet: &mut ZCPacket,
nonce: Option<&[u8]>,
) -> Result<(), Error> {
let pm_header = zc_packet.peer_manager_header().unwrap();
if pm_header.is_encrypted() {
tracing::warn!(?zc_packet, "packet is already encrypted");
@@ -74,7 +82,14 @@ impl Encryptor for RingChaCha20Cipher {
}
let mut tail = ChaCha20Poly1305Tail::default();
rand::thread_rng().fill_bytes(&mut tail.nonce);
if let Some(nonce) = nonce {
if nonce.len() != tail.nonce.len() {
return Err(Error::EncryptionFailed);
}
tail.nonce.copy_from_slice(nonce);
} else {
rand::thread_rng().fill_bytes(&mut tail.nonce);
}
let nonce = Nonce::assume_unique_for_key(tail.nonce);
let rs =
@@ -100,6 +115,7 @@ mod tests {
peers::encrypt::{ring_chacha20::RingChaCha20Cipher, Encryptor},
tunnel::packet_def::ZCPacket,
};
use zerocopy::FromBytes;
use super::CHACHA20_POLY1305_ENCRYPTION_RESERVED;
@@ -122,4 +138,33 @@ mod tests {
assert_eq!(packet.payload(), text);
assert!(!packet.peer_manager_header().unwrap().is_encrypted());
}
#[test]
fn test_ring_chacha20_cipher_with_nonce() {
let key = [9u8; 32];
let cipher = RingChaCha20Cipher::new(key);
let text = b"Hello";
let nonce = [5u8; 12];
let mut packet1 = ZCPacket::new_with_payload(text);
packet1.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet1, Some(&nonce))
.unwrap();
let mut packet2 = ZCPacket::new_with_payload(text);
packet2.fill_peer_manager_hdr(0, 0, 0);
cipher
.encrypt_with_nonce(&mut packet2, Some(&nonce))
.unwrap();
assert_eq!(packet1.payload(), packet2.payload());
let tail = super::ChaCha20Poly1305Tail::ref_from_suffix(packet1.payload()).unwrap();
assert_eq!(tail.nonce, nonce);
cipher.decrypt(&mut packet1).unwrap();
assert_eq!(packet1.payload(), text);
assert!(!packet1.peer_manager_header().unwrap().is_encrypted());
}
}