500 lines of untested protocol code, fun

This commit is contained in:
c0repwn3r 2023-05-03 12:31:57 -04:00
parent d2e1a85554
commit 3b6a8a00d7
Signed by: core
GPG Key ID: FDBF740DADDCEECF
7 changed files with 837 additions and 23 deletions

186
Cargo.lock generated
View File

@ -2,6 +2,16 @@
# It is not intended for manual editing.
version = 3
[[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 = "android_system_properties"
version = "0.1.5"
@ -11,6 +21,39 @@ dependencies = [
"libc",
]
[[package]]
name = "async-stream"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "async-trait"
version = "0.1.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -50,6 +93,12 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.79"
@ -62,6 +111,30 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
"zeroize",
]
[[package]]
name = "chrono"
version = "0.4.24"
@ -77,6 +150,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -115,6 +199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"rand_core",
"typenum",
]
@ -227,6 +312,12 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77"
[[package]]
name = "futures-core"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "generic-array"
version = "0.14.7"
@ -278,6 +369,15 @@ dependencies = [
"cxx-build",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "js-sys"
version = "0.3.61"
@ -297,6 +397,8 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
name = "libepf"
version = "0.1.0"
dependencies = [
"async-trait",
"chacha20poly1305",
"chrono",
"ed25519-dalek",
"hex",
@ -306,6 +408,8 @@ dependencies = [
"serde",
"serde_arrays",
"sha2",
"tokio",
"tokio-test",
"x25519-dalek",
]
@ -358,6 +462,12 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "packed_simd_2"
version = "0.3.8"
@ -384,6 +494,12 @@ dependencies = [
"serde",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pkcs8"
version = "0.10.2"
@ -400,6 +516,17 @@ version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630"
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -540,9 +667,9 @@ dependencies = [
[[package]]
name = "subtle"
version = "2.5.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
@ -586,6 +713,42 @@ dependencies = [
"winapi",
]
[[package]]
name = "tokio"
version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f"
dependencies = [
"autocfg",
"bytes",
"pin-project-lite",
"windows-sys",
]
[[package]]
name = "tokio-stream"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "typenum"
version = "1.16.0"
@ -604,6 +767,16 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "universal-hash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "version_check"
version = "0.9.4"
@ -716,6 +889,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"

View File

@ -15,4 +15,10 @@ sha2 = "0.10"
ed25519-dalek = { version = "2.0.0-rc.2", features = ["rand_core"] }
rand = "0.8"
x25519-dalek = "2.0.0-rc.2"
chrono = "0.4"
chrono = "0.4"
tokio = { version = "1.28", features = ["io-util"] }
async-trait = "0.1"
chacha20poly1305 = "0.10"
[dev-dependencies]
tokio-test = "0.4"

View File

@ -1,5 +1,9 @@
use crate::pki::{EPFCertificate, EpfPublicKey};
use crate::pki::{EPFCertificate, EpfPkiSerializable, EpfPublicKey};
use std::collections::HashMap;
use std::error::Error;
use std::ffi::{OsStr};
use std::fmt::{Display, Formatter};
use std::fs;
pub struct EpfCaPool {
pub ca_lookup_table: HashMap<EpfPublicKey, EPFCertificate>,
@ -27,3 +31,36 @@ impl EpfCaPoolOps for EpfCaPool {
.insert(cert.details.public_key, cert.clone());
}
}
#[derive(Debug)]
pub enum EpfCaPoolLoaderError {
CertDirDoesNotExist(String)
}
impl Display for EpfCaPoolLoaderError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpfCaPoolLoaderError::CertDirDoesNotExist(d) => write!(f, "Certificate dir does not exist: {}", d)
}
}
}
impl Error for EpfCaPoolLoaderError {}
#[cfg(unix)]
pub fn load_ca_pool() -> Result<EpfCaPool, Box<dyn Error>> {
let mut cert_strings = vec![];
for entry in fs::read_dir("/etc/e3pf/certs")? {
let entry = entry?;
if entry.path().extension() == Some(OsStr::new(".pem")) {
cert_strings.push(fs::read_to_string(entry.path())?);
}
}
let mut ca_pool = EpfCaPool::new();
for cert in cert_strings {
ca_pool.insert(&EPFCertificate::from_pem(cert.as_bytes())?);
}
Ok(ca_pool)
}

26
libepf/src/error.rs Normal file
View File

@ -0,0 +1,26 @@
use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::pki::EpfPkiCertificateValidationError;
#[derive(Debug)]
pub enum EpfHandshakeError {
AlreadyTunnelled,
UnsupportedProtocolVersion(usize),
InvalidCertificate(EpfPkiCertificateValidationError),
UntrustedCertificate,
EncryptionError,
MissingKeyProof
}
impl Display for EpfHandshakeError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpfHandshakeError::AlreadyTunnelled => write!(f, "Already tunneled"),
EpfHandshakeError::UnsupportedProtocolVersion(v) => write!(f, "Unsupported protocol version {}", v),
EpfHandshakeError::InvalidCertificate(e) => write!(f, "Invalid certificate: {}", e),
EpfHandshakeError::UntrustedCertificate => write!(f, "Certificate valid but not trusted"),
EpfHandshakeError::EncryptionError => write!(f, "Encryption error"),
EpfHandshakeError::MissingKeyProof => write!(f, "Missing key proof")
}
}
}
impl Error for EpfHandshakeError {}

View File

@ -0,0 +1,551 @@
use std::error::Error;
use std::io;
use async_trait::async_trait;
use chacha20poly1305::{AeadCore, Key, KeyInit, XChaCha20Poly1305, XNonce};
use chacha20poly1305::aead::{Aead, Payload};
use ed25519_dalek::{SigningKey};
use rand::Rng;
use rand::rngs::OsRng;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use x25519_dalek::x25519;
use crate::ca_pool::{load_ca_pool};
use crate::error::EpfHandshakeError;
use crate::pki::{EPFCertificate, EpfPkiCertificateOps, EpfPrivateKey, EpfPublicKey};
use crate::protocol::{encode_packet, EpfApplicationData, EpfClientHello, EpfClientState, EpfFinished, EpfMessage, EpfServerHello, EpfServerState, PACKET_APPLICATION_DATA, PACKET_CLIENT_HELLO, PACKET_FINISHED, PACKET_SERVER_HELLO, PROTOCOL_VERSION, recv_packet};
///// CLIENT /////
#[derive(Clone)]
pub struct EpfClientUpgraded<T: AsyncWriteExt + AsyncReadExt> {
inner: T,
state: EpfClientState,
client_random: [u8; 24],
server_random: [u8; 16],
client_cert: Option<EPFCertificate>,
packet_queue: Vec<EpfMessage>,
server_cert: Option<EPFCertificate>,
cipher: Option<XChaCha20Poly1305>,
private_key: EpfPrivateKey,
public_key: EpfPublicKey
}
pub enum ClientAuthentication {
Cert(Box<EPFCertificate>, EpfPrivateKey),
Ephemeral
}
#[async_trait]
pub trait EpfClientUpgradable {
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
}
#[async_trait]
impl<T> EpfClientUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send {
let private_key;
let public_key: [u8; 32];
let cert;
match auth {
ClientAuthentication::Cert(cert_d, key) => {
cert = Some(cert_d);
private_key = key;
public_key = key[32..].try_into().unwrap();
},
ClientAuthentication::Ephemeral => {
cert = None;
let private_key_l: [u8; 32] = OsRng.gen();
let private_key_real = SigningKey::from(private_key_l);
public_key = *private_key_real.verifying_key().as_bytes();
private_key = private_key_real.to_keypair_bytes();
}
}
EpfClientUpgraded {
inner: self,
state: EpfClientState::NotStarted,
client_random: OsRng.gen(),
server_random: [0u8; 16],
client_cert: cert.map(|u| *u),
server_cert: None,
packet_queue: vec![],
cipher: None,
private_key,
public_key,
}
}
}
#[async_trait]
pub trait EpfClientHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
async fn handshake(&mut self) -> Result<(), Box<dyn Error>>;
async fn upgrade(self) -> EpfClientStream<Self, S> where Self: Sized;
}
#[async_trait]
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin + Clone> EpfClientHandshaker<T> for EpfClientUpgraded<T> {
async fn handshake(&mut self) -> Result<(), Box<dyn Error>> {
match self.state {
EpfClientState::NotStarted => (),
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into())
}
// Step 0: Load Trusted Cert Store
let cert_pool = load_ca_pool()?;
// Step 1: Send Client Hello
self.inner.write_all(&encode_packet(PACKET_CLIENT_HELLO, &EpfClientHello {
protocol_version: PROTOCOL_VERSION,
client_random: self.client_random,
client_certificate: self.client_cert.clone(),
client_public_key: self.public_key,
})?).await?;
self.state = EpfClientState::WaitingForServerHello;
// Step 2: Wait for Server Hello
loop {
let packet = recv_packet(&mut self.inner).await?;
if packet.packet_id != PACKET_SERVER_HELLO {
self.packet_queue.push(packet);
continue;
}
let server_hello: EpfServerHello = rmp_serde::from_slice(&packet.packet_data)?;
self.server_random = server_hello.server_random;
if server_hello.protocol_version != PROTOCOL_VERSION {
return Err(EpfHandshakeError::UnsupportedProtocolVersion(server_hello.protocol_version as usize).into());
}
self.server_cert = Some(server_hello.server_certificate);
break;
}
// Step 3: Validate Server Certificate
let cert_valid = self.server_cert.as_ref().unwrap().verify(&cert_pool);
if let Err(e) = cert_valid {
return Err(EpfHandshakeError::InvalidCertificate(e).into())
}
if let Ok(false) = cert_valid {
return Err(EpfHandshakeError::UntrustedCertificate.into())
}
// Server Cert OK
// Step 4: Build the cipher
let shared_key = x25519(self.private_key[..32].try_into().unwrap(), self.server_cert.as_ref().unwrap().details.public_key);
let cc20p1305_key = Key::from(shared_key);
let cc20p1305 = XChaCha20Poly1305::new(&cc20p1305_key);
self.cipher = Some(cc20p1305);
let payload = Payload {
msg: &[0x42],
aad: &self.server_random,
};
let nonce = XNonce::from_slice(&self.client_random);
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
Ok(d) => d,
Err(_) => {
return Err(EpfHandshakeError::EncryptionError.into())
}
};
self.inner.write_all(&encode_packet(PACKET_FINISHED, &EpfFinished {
protocol_version: PROTOCOL_VERSION,
encrypted_0x42
})?).await?;
self.state = EpfClientState::WaitingForFinished;
loop {
let packet = recv_packet(&mut self.inner).await?;
if packet.packet_id != PACKET_FINISHED {
self.packet_queue.push(packet);
continue;
}
let packet_finished: EpfFinished = rmp_serde::from_slice(&packet.packet_data)?;
let payload = Payload {
msg: &packet_finished.encrypted_0x42,
aad: &self.server_random,
};
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
Ok(d) => d,
Err(_) => {
return Err(EpfHandshakeError::EncryptionError.into());
}
};
if hopefully_0x42 != vec![0x42] {
return Err(EpfHandshakeError::MissingKeyProof.into())
}
break;
}
self.state = EpfClientState::Transport;
Ok(())
}
async fn upgrade(self) -> EpfClientStream<Self, T> where Self: Sized {
EpfClientStream {
inner: self.clone(),
aad: self.server_random,
client_cert: self.client_cert,
packet_queue: self.packet_queue,
server_cert: self.server_cert.unwrap(),
cipher: self.cipher.unwrap(),
private_key: self.private_key,
public_key: self.public_key,
raw_stream: self.inner
}
}
}
pub struct EpfClientStream<T: EpfClientHandshaker<S>, S: AsyncReadExt + AsyncWriteExt + Unpin> {
inner: T,
raw_stream: S,
aad: [u8; 16],
client_cert: Option<EPFCertificate>,
packet_queue: Vec<EpfMessage>,
server_cert: EPFCertificate,
cipher: XChaCha20Poly1305,
private_key: EpfPrivateKey,
public_key: EpfPublicKey
}
#[async_trait]
pub trait EpfStreamOps {
async fn write(&mut self, data: &[u8]) -> Result<(), Box<dyn Error>>;
async fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>>;
}
#[async_trait]
impl<T: EpfClientHandshaker<S> + Send, S: AsyncReadExt + AsyncWriteExt + Unpin + Send> EpfStreamOps for EpfClientStream<T, S> {
async fn write(&mut self, data: &[u8]) -> Result<(), Box<dyn Error>> {
let nonce = XChaCha20Poly1305::generate_nonce(OsRng);
let payload = Payload {
msg: data,
aad: &self.aad,
};
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
Ok(c) => c,
Err(_) => {
return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into())
}
};
let application_data = EpfApplicationData {
protocol_version: PROTOCOL_VERSION,
encrypted_application_data: ciphertext,
nonce: nonce.try_into().unwrap(),
};
let packet = encode_packet(PACKET_APPLICATION_DATA, &application_data)?;
self.raw_stream.write_all(&packet).await?;
Ok(())
}
async fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
loop {
let packet = recv_packet(&mut self.raw_stream).await?;
if packet.packet_id != PACKET_APPLICATION_DATA {
self.packet_queue.push(packet);
continue;
}
let app_data: EpfApplicationData = rmp_serde::from_slice(&packet.packet_data)?;
let nonce = XNonce::from_slice(&app_data.nonce);
let payload = Payload {
msg: &app_data.encrypted_application_data,
aad: &self.aad,
};
let plaintext = match self.cipher.decrypt(nonce, payload) {
Ok(p) => p,
Err(_) => {
return Err(io::Error::new(io::ErrorKind::Other, "Decryption error").into())
}
};
return Ok(plaintext);
}
}
}
///// SERVER /////
#[derive(Clone)]
pub struct EpfServerUpgraded<T: AsyncWriteExt + AsyncReadExt> {
inner: T,
state: EpfServerState,
client_random: [u8; 24],
server_random: [u8; 16],
client_cert: Option<EPFCertificate>,
packet_queue: Vec<EpfMessage>,
cipher: Option<XChaCha20Poly1305>,
cert: EPFCertificate,
private_key: EpfPrivateKey,
public_key: EpfPublicKey
}
#[async_trait]
pub trait EpfServerUpgradable {
async fn upgrade(self, cert: EPFCertificate, private_key: EpfPrivateKey) -> EpfServerUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
}
#[async_trait]
impl<T> EpfServerUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
async fn upgrade(self, cert: EPFCertificate, private_key: EpfPrivateKey) -> EpfServerUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send {
EpfServerUpgraded {
inner: self,
state: EpfServerState::WaitingForClientHello,
server_random: OsRng.gen(),
client_random: [0u8; 24],
cert,
client_cert: None,
packet_queue: vec![],
cipher: None,
private_key,
public_key: SigningKey::from_keypair_bytes(&private_key).unwrap().verifying_key().to_bytes(),
}
}
}
#[async_trait]
pub trait EpfServerHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
async fn handshake(&mut self) -> Result<(), Box<dyn Error>>;
async fn upgrade(self) -> EpfServerStream<Self, S> where Self: Sized;
}
#[async_trait]
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin + Clone> EpfServerHandshaker<T> for EpfServerUpgraded<T> {
async fn handshake(&mut self) -> Result<(), Box<dyn Error>> {
match self.state {
EpfServerState::WaitingForClientHello => (),
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into())
}
// Step 0: Load Trusted Cert Store
let cert_pool = load_ca_pool()?;
let client_public_key;
// Step 1: Wait for Client Hello
loop {
let packet = recv_packet(&mut self.inner).await?;
if packet.packet_id != PACKET_CLIENT_HELLO {
self.packet_queue.push(packet);
continue;
}
let client_hello: EpfClientHello = rmp_serde::from_slice(&packet.packet_data)?;
self.client_random = client_hello.client_random;
if client_hello.protocol_version != PROTOCOL_VERSION {
return Err(EpfHandshakeError::UnsupportedProtocolVersion(client_hello.protocol_version as usize).into());
}
self.client_cert = client_hello.client_certificate;
client_public_key = client_hello.client_public_key;
break;
}
// Step 2: Validate Client Certificate (if present)
if let Some(client_cert) = &self.client_cert {
let cert_valid = client_cert.verify(&cert_pool);
if let Err(e) = cert_valid {
return Err(EpfHandshakeError::InvalidCertificate(e).into())
}
if let Ok(false) = cert_valid {
return Err(EpfHandshakeError::UntrustedCertificate.into())
}
}
// Client Cert OK (if present)
// Step 3: Send Server Hello
self.inner.write_all(&encode_packet(PACKET_SERVER_HELLO, &EpfServerHello {
protocol_version: PROTOCOL_VERSION,
server_certificate: self.cert.clone(),
server_random: self.server_random,
})?).await?;
self.state = EpfServerState::WaitingForFinished;
// Step 4: Build the cipher
let shared_key = x25519(self.private_key[..32].try_into().unwrap(), client_public_key);
let cc20p1305_key = Key::from(shared_key);
let cc20p1305 = XChaCha20Poly1305::new(&cc20p1305_key);
self.cipher = Some(cc20p1305);
let payload = Payload {
msg: &[0x42],
aad: &self.server_random,
};
let nonce = XNonce::from_slice(&self.client_random);
loop {
let packet = recv_packet(&mut self.inner).await?;
if packet.packet_id != PACKET_FINISHED {
self.packet_queue.push(packet);
continue;
}
let packet_finished: EpfFinished = rmp_serde::from_slice(&packet.packet_data)?;
let payload = Payload {
msg: &packet_finished.encrypted_0x42,
aad: &self.server_random,
};
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
Ok(d) => d,
Err(_) => {
return Err(EpfHandshakeError::EncryptionError.into());
}
};
if hopefully_0x42 != vec![0x42] {
return Err(EpfHandshakeError::MissingKeyProof.into())
}
break;
}
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
Ok(d) => d,
Err(_) => {
return Err(EpfHandshakeError::EncryptionError.into())
}
};
self.inner.write_all(&encode_packet(PACKET_FINISHED, &EpfFinished {
protocol_version: PROTOCOL_VERSION,
encrypted_0x42
})?).await?;
self.state = EpfServerState::WaitingForFinished;
self.state = EpfServerState::Transport;
Ok(())
}
async fn upgrade(self) -> EpfServerStream<Self, T> where Self: Sized {
EpfServerStream {
inner: self.clone(),
aad: self.server_random,
server_cert: self.cert,
packet_queue: self.packet_queue,
client_cert: self.client_cert,
cipher: self.cipher.unwrap(),
private_key: self.private_key,
public_key: self.public_key,
raw_stream: self.inner
}
}
}
pub struct EpfServerStream<T: EpfServerHandshaker<S>, S: AsyncReadExt + AsyncWriteExt + Unpin> {
inner: T,
raw_stream: S,
aad: [u8; 16],
client_cert: Option<EPFCertificate>,
packet_queue: Vec<EpfMessage>,
server_cert: EPFCertificate,
cipher: XChaCha20Poly1305,
private_key: EpfPrivateKey,
public_key: EpfPublicKey
}
#[async_trait]
impl<T: EpfServerHandshaker<S> + Send, S: AsyncReadExt + AsyncWriteExt + Unpin + Send> EpfStreamOps for EpfServerStream<T, S> {
async fn write(&mut self, data: &[u8]) -> Result<(), Box<dyn Error>> {
let nonce = XChaCha20Poly1305::generate_nonce(OsRng);
let payload = Payload {
msg: data,
aad: &self.aad,
};
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
Ok(c) => c,
Err(_) => {
return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into())
}
};
let application_data = EpfApplicationData {
protocol_version: PROTOCOL_VERSION,
encrypted_application_data: ciphertext,
nonce: nonce.try_into().unwrap(),
};
let packet = encode_packet(PACKET_APPLICATION_DATA, &application_data)?;
self.raw_stream.write_all(&packet).await?;
Ok(())
}
async fn read(&mut self) -> Result<Vec<u8>, Box<dyn Error>> {
loop {
let packet = recv_packet(&mut self.raw_stream).await?;
if packet.packet_id != PACKET_APPLICATION_DATA {
self.packet_queue.push(packet);
continue;
}
let app_data: EpfApplicationData = rmp_serde::from_slice(&packet.packet_data)?;
let nonce = XNonce::from_slice(&app_data.nonce);
let payload = Payload {
msg: &app_data.encrypted_application_data,
aad: &self.aad,
};
let plaintext = match self.cipher.decrypt(nonce, payload) {
Ok(p) => p,
Err(_) => {
return Err(io::Error::new(io::ErrorKind::Other, "Decryption error").into())
}
};
return Ok(plaintext);
}
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
#[test]
pub fn stream_test() {
}
}

View File

@ -1,4 +1,6 @@
pub mod ca_pool;
pub mod pki;
pub mod util;
pub mod protocol;
pub mod protocol;
pub mod handshake_stream;
pub mod error;

View File

@ -1,23 +1,27 @@
use std::error::Error;
use serde::{Deserialize, Serialize};
use crate::pki::EPFCertificate;
use tokio::io::{AsyncReadExt};
use crate::pki::{EPFCertificate, EPFPKI_PUBLIC_KEY_LENGTH};
pub const PROTOCOL_VERSION: u32 = 1;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct EpfMessage {
pub packet_id: u32,
pub packet_data: Vec<u8>
}
pub const CLIENT_HELLO: u32 = 1;
pub const PACKET_CLIENT_HELLO: u32 = 1;
#[derive(Serialize, Deserialize)]
pub struct EpfClientHello {
pub protocol_version: u32,
pub client_random: [u8; 16]
pub client_random: [u8; 24],
pub client_certificate: Option<EPFCertificate>,
pub client_public_key: [u8; EPFPKI_PUBLIC_KEY_LENGTH]
}
pub const SERVER_HELLO: u32 = 2;
pub const PACKET_SERVER_HELLO: u32 = 2;
#[derive(Serialize, Deserialize)]
pub struct EpfServerHello {
@ -26,15 +30,7 @@ pub struct EpfServerHello {
pub server_random: [u8; 16]
}
pub const CLIENT_KEY_EXCHANGE: u32 = 3;
#[derive(Serialize, Deserialize)]
pub struct EpfClientKeyExchange {
pub protocol_version: u32,
pub encrypted_shared_secret: Vec<u8>
}
pub const FINISHED: u32 = 4;
pub const PACKET_FINISHED: u32 = 3;
#[derive(Serialize, Deserialize)]
pub struct EpfFinished {
@ -42,14 +38,16 @@ pub struct EpfFinished {
pub encrypted_0x42: Vec<u8>
}
pub const APPLICATION_DATA: u32 = 5;
pub const PACKET_APPLICATION_DATA: u32 = 4;
#[derive(Serialize, Deserialize)]
pub struct EpfApplicationData {
pub protocol_version: u32,
pub application_data: Vec<u8>
pub encrypted_application_data: Vec<u8>,
pub nonce: [u8; 24]
}
#[derive(Clone)]
pub enum EpfClientState {
NotStarted,
WaitingForServerHello,
@ -58,9 +56,9 @@ pub enum EpfClientState {
Closed
}
#[derive(Clone)]
pub enum EpfServerState {
WaitingForClientHello,
WaitingForClientKeyExchange,
WaitingForFinished,
Transport,
Closed
@ -72,5 +70,17 @@ pub fn encode_packet<T: Serialize>(id: u32, packet: &T) -> Result<Vec<u8>, rmp_s
packet_id: id,
packet_data: message_data,
};
rmp_serde::to_vec(&message_wrapper)
let mut packet_data = rmp_serde::to_vec(&message_wrapper)?;
let mut packet = (packet_data.len() as u64).to_le_bytes().to_vec();
// Packet: 8-byte little-endian length prefix, packet data
packet.append(&mut packet_data);
Ok(packet)
}
pub async fn recv_packet<C: AsyncReadExt + Unpin>(stream: &mut C) -> Result<EpfMessage, Box<dyn Error>> {
let packet_length = stream.read_u64_le().await?;
let mut packet_data_buf = vec![0u8; packet_length as usize];
stream.read_exact(&mut packet_data_buf).await?;
let message: EpfMessage = rmp_serde::from_slice(&packet_data_buf)?;
Ok(message)
}