code cleanup - pt1
This commit is contained in:
parent
15ac941df5
commit
6f2a0ca828
|
@ -1,11 +1,11 @@
|
||||||
use crate::pki::{EPFCertificate, EpfPkiSerializable, EpfPublicKey};
|
use crate::pki::{EPFCertificate, EpfPkiSerializable, EpfPublicKey};
|
||||||
|
use crate::util::verifying_key;
|
||||||
|
use log::trace;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::ffi::{OsStr};
|
use std::ffi::OsStr;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use log::trace;
|
|
||||||
use crate::util::verifying_key;
|
|
||||||
|
|
||||||
pub struct EpfCaPool {
|
pub struct EpfCaPool {
|
||||||
pub ca_lookup_table: HashMap<EpfPublicKey, EPFCertificate>,
|
pub ca_lookup_table: HashMap<EpfPublicKey, EPFCertificate>,
|
||||||
|
@ -36,12 +36,14 @@ impl EpfCaPoolOps for EpfCaPool {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EpfCaPoolLoaderError {
|
pub enum EpfCaPoolLoaderError {
|
||||||
CertDirDoesNotExist(String)
|
CertDirDoesNotExist(String),
|
||||||
}
|
}
|
||||||
impl Display for EpfCaPoolLoaderError {
|
impl Display for EpfCaPoolLoaderError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
EpfCaPoolLoaderError::CertDirDoesNotExist(d) => write!(f, "Certificate dir does not exist: {}", d)
|
EpfCaPoolLoaderError::CertDirDoesNotExist(d) => {
|
||||||
|
write!(f, "Certificate dir does not exist: {}", d)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,4 +67,4 @@ pub fn load_ca_pool() -> Result<EpfCaPool, Box<dyn Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ca_pool)
|
Ok(ca_pool)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::pki::EpfPkiCertificateValidationError;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use crate::pki::EpfPkiCertificateValidationError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EpfHandshakeError {
|
pub enum EpfHandshakeError {
|
||||||
|
@ -9,18 +9,22 @@ pub enum EpfHandshakeError {
|
||||||
InvalidCertificate(EpfPkiCertificateValidationError),
|
InvalidCertificate(EpfPkiCertificateValidationError),
|
||||||
UntrustedCertificate,
|
UntrustedCertificate,
|
||||||
EncryptionError,
|
EncryptionError,
|
||||||
MissingKeyProof
|
MissingKeyProof,
|
||||||
}
|
}
|
||||||
impl Display for EpfHandshakeError {
|
impl Display for EpfHandshakeError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
EpfHandshakeError::AlreadyTunnelled => write!(f, "Already tunneled"),
|
EpfHandshakeError::AlreadyTunnelled => write!(f, "Already tunneled"),
|
||||||
EpfHandshakeError::UnsupportedProtocolVersion(v) => write!(f, "Unsupported protocol version {}", v),
|
EpfHandshakeError::UnsupportedProtocolVersion(v) => {
|
||||||
|
write!(f, "Unsupported protocol version {}", v)
|
||||||
|
}
|
||||||
EpfHandshakeError::InvalidCertificate(e) => write!(f, "Invalid certificate: {}", e),
|
EpfHandshakeError::InvalidCertificate(e) => write!(f, "Invalid certificate: {}", e),
|
||||||
EpfHandshakeError::UntrustedCertificate => write!(f, "Certificate valid but not trusted"),
|
EpfHandshakeError::UntrustedCertificate => {
|
||||||
|
write!(f, "Certificate valid but not trusted")
|
||||||
|
}
|
||||||
EpfHandshakeError::EncryptionError => write!(f, "Encryption error"),
|
EpfHandshakeError::EncryptionError => write!(f, "Encryption error"),
|
||||||
EpfHandshakeError::MissingKeyProof => write!(f, "Missing key proof")
|
EpfHandshakeError::MissingKeyProof => write!(f, "Missing key proof"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Error for EpfHandshakeError {}
|
impl Error for EpfHandshakeError {}
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
use std::error::Error;
|
use crate::ca_pool::{load_ca_pool, EpfCaPool};
|
||||||
use std::io;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use chacha20poly1305::{AeadCore, Key, KeyInit, XChaCha20Poly1305, XNonce};
|
|
||||||
use chacha20poly1305::aead::{Aead, Payload};
|
|
||||||
use ed25519_dalek::{SecretKey, SigningKey};
|
|
||||||
use log::{debug, trace};
|
|
||||||
use rand::Rng;
|
|
||||||
use rand::rngs::OsRng;
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use x25519_dalek::{PublicKey, StaticSecret, x25519};
|
|
||||||
use crate::ca_pool::{EpfCaPool, load_ca_pool};
|
|
||||||
use crate::danger_trace;
|
use crate::danger_trace;
|
||||||
use crate::error::EpfHandshakeError;
|
use crate::error::EpfHandshakeError;
|
||||||
use crate::pki::{EPFCertificate, EPFPKI_PUBLIC_KEY_LENGTH, EpfPkiCertificateOps, EpfPrivateKey, EpfPublicKey};
|
use crate::pki::{
|
||||||
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};
|
EPFCertificate, EpfPkiCertificateOps, EpfPrivateKey, EpfPublicKey, EPFPKI_PUBLIC_KEY_LENGTH,
|
||||||
|
};
|
||||||
|
use crate::protocol::{
|
||||||
|
encode_packet, recv_packet, EpfApplicationData, EpfClientHello, EpfClientState, EpfFinished,
|
||||||
|
EpfMessage, EpfServerHello, EpfServerState, PACKET_APPLICATION_DATA, PACKET_CLIENT_HELLO,
|
||||||
|
PACKET_FINISHED, PACKET_SERVER_HELLO, PROTOCOL_VERSION,
|
||||||
|
};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use chacha20poly1305::aead::{Aead, Payload};
|
||||||
|
use chacha20poly1305::{AeadCore, Key, KeyInit, XChaCha20Poly1305, XNonce};
|
||||||
|
use ed25519_dalek::{SecretKey, SigningKey};
|
||||||
|
use log::{debug, trace};
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rand::Rng;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::io;
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use x25519_dalek::{x25519, PublicKey, StaticSecret};
|
||||||
|
|
||||||
///// CLIENT /////
|
///// CLIENT /////
|
||||||
|
|
||||||
|
@ -27,23 +33,31 @@ pub struct EpfClientUpgraded<T: AsyncWriteExt + AsyncReadExt> {
|
||||||
server_cert: Option<EPFCertificate>,
|
server_cert: Option<EPFCertificate>,
|
||||||
cipher: Option<XChaCha20Poly1305>,
|
cipher: Option<XChaCha20Poly1305>,
|
||||||
private_key: EpfPrivateKey,
|
private_key: EpfPrivateKey,
|
||||||
public_key: PublicKey
|
public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientAuthentication {
|
pub enum ClientAuthentication {
|
||||||
Cert(Box<EPFCertificate>, EpfPrivateKey),
|
Cert(Box<EPFCertificate>, EpfPrivateKey),
|
||||||
Ephemeral
|
Ephemeral,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait EpfClientUpgradable {
|
pub trait EpfClientUpgradable {
|
||||||
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
|
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T> EpfClientUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
|
impl<T> EpfClientUpgradable for T
|
||||||
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send {
|
where
|
||||||
|
T: AsyncWriteExt + AsyncReadExt + Send,
|
||||||
|
{
|
||||||
|
async fn upgrade(self, auth: ClientAuthentication) -> EpfClientUpgraded<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + AsyncWriteExt + AsyncReadExt + Send,
|
||||||
|
{
|
||||||
danger_trace!(target: "EpfClientUpgradable", "upgrade(auth: {:?})", auth);
|
danger_trace!(target: "EpfClientUpgradable", "upgrade(auth: {:?})", auth);
|
||||||
|
|
||||||
let private_key;
|
let private_key;
|
||||||
|
@ -56,7 +70,7 @@ impl<T> EpfClientUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
|
||||||
cert = Some(cert_d);
|
cert = Some(cert_d);
|
||||||
private_key = key.clone();
|
private_key = key.clone();
|
||||||
public_key = PublicKey::from(&StaticSecret::from(private_key.to_bytes()));
|
public_key = PublicKey::from(&StaticSecret::from(private_key.to_bytes()));
|
||||||
},
|
}
|
||||||
ClientAuthentication::Ephemeral => {
|
ClientAuthentication::Ephemeral => {
|
||||||
cert = None;
|
cert = None;
|
||||||
let private_key_l: [u8; 32] = OsRng.gen();
|
let private_key_l: [u8; 32] = OsRng.gen();
|
||||||
|
@ -84,24 +98,33 @@ impl<T> EpfClientUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait EpfClientHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
|
pub trait EpfClientHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
|
||||||
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>>;
|
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>>;
|
||||||
async fn upgrade(self) -> EpfClientStream<S> where Self: Sized;
|
async fn upgrade(self) -> EpfClientStream<S>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for EpfClientUpgraded<T> {
|
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T>
|
||||||
|
for EpfClientUpgraded<T>
|
||||||
|
{
|
||||||
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>> {
|
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>> {
|
||||||
match self.state {
|
match self.state {
|
||||||
EpfClientState::NotStarted => (),
|
EpfClientState::NotStarted => (),
|
||||||
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into())
|
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 1: Send Client Hello
|
// Step 1: Send Client Hello
|
||||||
self.inner.write_all(&encode_packet(PACKET_CLIENT_HELLO, &EpfClientHello {
|
self.inner
|
||||||
protocol_version: PROTOCOL_VERSION,
|
.write_all(&encode_packet(
|
||||||
client_random: self.client_random,
|
PACKET_CLIENT_HELLO,
|
||||||
client_certificate: self.client_cert.clone(),
|
&EpfClientHello {
|
||||||
client_x25519_public_key: self.public_key.to_bytes(),
|
protocol_version: PROTOCOL_VERSION,
|
||||||
})?).await?;
|
client_random: self.client_random,
|
||||||
|
client_certificate: self.client_cert.clone(),
|
||||||
|
client_x25519_public_key: self.public_key.to_bytes(),
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
.await?;
|
||||||
self.inner.flush().await?;
|
self.inner.flush().await?;
|
||||||
|
|
||||||
trace!("---- !!!!! SENT CLIENT HELLO");
|
trace!("---- !!!!! SENT CLIENT HELLO");
|
||||||
|
@ -126,7 +149,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
self.server_random = server_hello.server_random;
|
self.server_random = server_hello.server_random;
|
||||||
|
|
||||||
if server_hello.protocol_version != PROTOCOL_VERSION {
|
if server_hello.protocol_version != PROTOCOL_VERSION {
|
||||||
return Err(EpfHandshakeError::UnsupportedProtocolVersion(server_hello.protocol_version as usize).into());
|
return Err(EpfHandshakeError::UnsupportedProtocolVersion(
|
||||||
|
server_hello.protocol_version as usize,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.server_cert = Some(server_hello.server_certificate);
|
self.server_cert = Some(server_hello.server_certificate);
|
||||||
|
@ -139,10 +165,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
// Step 3: Validate Server Certificate
|
// Step 3: Validate Server Certificate
|
||||||
let cert_valid = self.server_cert.as_ref().unwrap().verify(&cert_pool);
|
let cert_valid = self.server_cert.as_ref().unwrap().verify(&cert_pool);
|
||||||
if let Err(e) = cert_valid {
|
if let Err(e) = cert_valid {
|
||||||
return Err(EpfHandshakeError::InvalidCertificate(e).into())
|
return Err(EpfHandshakeError::InvalidCertificate(e).into());
|
||||||
}
|
}
|
||||||
if let Ok(false) = cert_valid {
|
if let Ok(false) = cert_valid {
|
||||||
return Err(EpfHandshakeError::UntrustedCertificate.into())
|
return Err(EpfHandshakeError::UntrustedCertificate.into());
|
||||||
}
|
}
|
||||||
// Server Cert OK
|
// Server Cert OK
|
||||||
|
|
||||||
|
@ -151,13 +177,24 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
let private_key = StaticSecret::from(self.private_key.to_bytes());
|
let private_key = StaticSecret::from(self.private_key.to_bytes());
|
||||||
let their_public_key = PublicKey::from(server_x25519_key);
|
let their_public_key = PublicKey::from(server_x25519_key);
|
||||||
|
|
||||||
assert_ne!(their_public_key.to_bytes(), PublicKey::from(&private_key).to_bytes());
|
assert_ne!(
|
||||||
|
their_public_key.to_bytes(),
|
||||||
|
PublicKey::from(&private_key).to_bytes()
|
||||||
|
);
|
||||||
|
|
||||||
danger_trace!("pr: {}, their pub: {}, my pub: {}", hex::encode(self.private_key.to_bytes()), hex::encode(self.server_cert.as_ref().unwrap().details.public_key), hex::encode(self.private_key.verifying_key().to_bytes()));
|
danger_trace!(
|
||||||
|
"pr: {}, their pub: {}, my pub: {}",
|
||||||
|
hex::encode(self.private_key.to_bytes()),
|
||||||
|
hex::encode(self.server_cert.as_ref().unwrap().details.public_key),
|
||||||
|
hex::encode(self.private_key.verifying_key().to_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
let shared_key = private_key.diffie_hellman(&their_public_key).to_bytes();
|
let shared_key = private_key.diffie_hellman(&their_public_key).to_bytes();
|
||||||
|
|
||||||
trace!("server public key: {:x?}", self.server_cert.as_ref().unwrap().details.public_key);
|
trace!(
|
||||||
|
"server public key: {:x?}",
|
||||||
|
self.server_cert.as_ref().unwrap().details.public_key
|
||||||
|
);
|
||||||
danger_trace!("shared key: {}", hex::encode(shared_key));
|
danger_trace!("shared key: {}", hex::encode(shared_key));
|
||||||
|
|
||||||
let cc20p1305_key = Key::from(shared_key);
|
let cc20p1305_key = Key::from(shared_key);
|
||||||
|
@ -177,15 +214,18 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
|
|
||||||
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
|
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(_) => {
|
Err(_) => return Err(EpfHandshakeError::EncryptionError.into()),
|
||||||
return Err(EpfHandshakeError::EncryptionError.into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.inner.write_all(&encode_packet(PACKET_FINISHED, &EpfFinished {
|
self.inner
|
||||||
protocol_version: PROTOCOL_VERSION,
|
.write_all(&encode_packet(
|
||||||
encrypted_0x42
|
PACKET_FINISHED,
|
||||||
})?).await?;
|
&EpfFinished {
|
||||||
|
protocol_version: PROTOCOL_VERSION,
|
||||||
|
encrypted_0x42,
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
.await?;
|
||||||
self.inner.flush().await?;
|
self.inner.flush().await?;
|
||||||
|
|
||||||
self.state = EpfClientState::WaitingForFinished;
|
self.state = EpfClientState::WaitingForFinished;
|
||||||
|
@ -207,7 +247,12 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
aad: &self.server_random,
|
aad: &self.server_random,
|
||||||
};
|
};
|
||||||
|
|
||||||
danger_trace!("ciphertext: {:?}, aad: {:?}, nonce: {:?}", packet_finished.encrypted_0x42, payload.aad, nonce);
|
danger_trace!(
|
||||||
|
"ciphertext: {:?}, aad: {:?}, nonce: {:?}",
|
||||||
|
packet_finished.encrypted_0x42,
|
||||||
|
payload.aad,
|
||||||
|
nonce
|
||||||
|
);
|
||||||
|
|
||||||
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
|
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
|
@ -217,7 +262,7 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
};
|
};
|
||||||
|
|
||||||
if hopefully_0x42 != vec![0x42] {
|
if hopefully_0x42 != vec![0x42] {
|
||||||
return Err(EpfHandshakeError::MissingKeyProof.into())
|
return Err(EpfHandshakeError::MissingKeyProof.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -228,7 +273,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfClientHandshaker<T> for
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upgrade(self) -> EpfClientStream<T> where Self: Sized {
|
async fn upgrade(self) -> EpfClientStream<T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
let aad = self.server_random.clone();
|
let aad = self.server_random.clone();
|
||||||
let client_cert = self.client_cert.clone();
|
let client_cert = self.client_cert.clone();
|
||||||
let packet_queue = self.packet_queue.clone();
|
let packet_queue = self.packet_queue.clone();
|
||||||
|
@ -258,7 +306,7 @@ pub struct EpfClientStream<S: AsyncReadExt + AsyncWriteExt + Unpin> {
|
||||||
server_cert: EPFCertificate,
|
server_cert: EPFCertificate,
|
||||||
cipher: XChaCha20Poly1305,
|
cipher: XChaCha20Poly1305,
|
||||||
private_key: EpfPrivateKey,
|
private_key: EpfPrivateKey,
|
||||||
public_key: PublicKey
|
public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -279,9 +327,7 @@ impl<S: AsyncReadExt + AsyncWriteExt + Unpin + Send> EpfStreamOps for EpfClientS
|
||||||
|
|
||||||
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
|
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(_) => {
|
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into()),
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let application_data = EpfApplicationData {
|
let application_data = EpfApplicationData {
|
||||||
protocol_version: PROTOCOL_VERSION,
|
protocol_version: PROTOCOL_VERSION,
|
||||||
|
@ -339,17 +385,33 @@ pub struct EpfServerUpgraded<T: AsyncWriteExt + AsyncReadExt> {
|
||||||
cipher: Option<XChaCha20Poly1305>,
|
cipher: Option<XChaCha20Poly1305>,
|
||||||
cert: EPFCertificate,
|
cert: EPFCertificate,
|
||||||
private_key: EpfPrivateKey,
|
private_key: EpfPrivateKey,
|
||||||
public_key: EpfPublicKey
|
public_key: EpfPublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait EpfServerUpgradable {
|
pub trait EpfServerUpgradable {
|
||||||
async fn upgrade(self, cert: EPFCertificate, private_key: EpfPrivateKey) -> EpfServerUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
|
async fn upgrade(
|
||||||
|
self,
|
||||||
|
cert: EPFCertificate,
|
||||||
|
private_key: EpfPrivateKey,
|
||||||
|
) -> EpfServerUpgraded<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + AsyncWriteExt + AsyncReadExt + Send;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: ?Sized> EpfServerUpgradable for T where T: AsyncWriteExt + AsyncReadExt + Send {
|
impl<T: ?Sized> EpfServerUpgradable for T
|
||||||
async fn upgrade(self, cert: EPFCertificate, private_key: EpfPrivateKey) -> EpfServerUpgraded<Self> where Self: Sized + AsyncWriteExt + AsyncReadExt + Send {
|
where
|
||||||
|
T: AsyncWriteExt + AsyncReadExt + Send,
|
||||||
|
{
|
||||||
|
async fn upgrade(
|
||||||
|
self,
|
||||||
|
cert: EPFCertificate,
|
||||||
|
private_key: EpfPrivateKey,
|
||||||
|
) -> EpfServerUpgraded<Self>
|
||||||
|
where
|
||||||
|
Self: Sized + AsyncWriteExt + AsyncReadExt + Send,
|
||||||
|
{
|
||||||
EpfServerUpgraded {
|
EpfServerUpgraded {
|
||||||
inner: self,
|
inner: self,
|
||||||
state: EpfServerState::WaitingForClientHello,
|
state: EpfServerState::WaitingForClientHello,
|
||||||
|
@ -368,15 +430,19 @@ impl<T: ?Sized> EpfServerUpgradable for T where T: AsyncWriteExt + AsyncReadExt
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait EpfServerHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
|
pub trait EpfServerHandshaker<S: AsyncWriteExt + AsyncReadExt + Unpin> {
|
||||||
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>>;
|
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>>;
|
||||||
async fn upgrade(self) -> EpfServerStream<S> where Self: Sized;
|
async fn upgrade(self) -> EpfServerStream<S>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for EpfServerUpgraded<T> {
|
impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T>
|
||||||
|
for EpfServerUpgraded<T>
|
||||||
|
{
|
||||||
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>> {
|
async fn handshake(&mut self, cert_pool: EpfCaPool) -> Result<(), Box<dyn Error>> {
|
||||||
match self.state {
|
match self.state {
|
||||||
EpfServerState::WaitingForClientHello => (),
|
EpfServerState::WaitingForClientHello => (),
|
||||||
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into())
|
_ => return Err(EpfHandshakeError::AlreadyTunnelled.into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_public_key;
|
let client_public_key;
|
||||||
|
@ -397,7 +463,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
self.client_random = client_hello.client_random;
|
self.client_random = client_hello.client_random;
|
||||||
|
|
||||||
if client_hello.protocol_version != PROTOCOL_VERSION {
|
if client_hello.protocol_version != PROTOCOL_VERSION {
|
||||||
return Err(EpfHandshakeError::UnsupportedProtocolVersion(client_hello.protocol_version as usize).into());
|
return Err(EpfHandshakeError::UnsupportedProtocolVersion(
|
||||||
|
client_hello.protocol_version as usize,
|
||||||
|
)
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client_cert = client_hello.client_certificate;
|
self.client_cert = client_hello.client_certificate;
|
||||||
|
@ -413,10 +482,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
if let Some(client_cert) = &self.client_cert {
|
if let Some(client_cert) = &self.client_cert {
|
||||||
let cert_valid = client_cert.verify(&cert_pool);
|
let cert_valid = client_cert.verify(&cert_pool);
|
||||||
if let Err(e) = cert_valid {
|
if let Err(e) = cert_valid {
|
||||||
return Err(EpfHandshakeError::InvalidCertificate(e).into())
|
return Err(EpfHandshakeError::InvalidCertificate(e).into());
|
||||||
}
|
}
|
||||||
if let Ok(false) = cert_valid {
|
if let Ok(false) = cert_valid {
|
||||||
return Err(EpfHandshakeError::UntrustedCertificate.into())
|
return Err(EpfHandshakeError::UntrustedCertificate.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Client Cert OK (if present)
|
// Client Cert OK (if present)
|
||||||
|
@ -424,12 +493,20 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
trace!("client cert okay");
|
trace!("client cert okay");
|
||||||
|
|
||||||
// Step 3: Send Server Hello
|
// Step 3: Send Server Hello
|
||||||
self.inner.write_all(&encode_packet(PACKET_SERVER_HELLO, &EpfServerHello {
|
self.inner
|
||||||
protocol_version: PROTOCOL_VERSION,
|
.write_all(&encode_packet(
|
||||||
server_certificate: self.cert.clone(),
|
PACKET_SERVER_HELLO,
|
||||||
server_random: self.server_random,
|
&EpfServerHello {
|
||||||
server_x25519_public_key: PublicKey::from(&StaticSecret::from(self.private_key.to_bytes())).to_bytes()
|
protocol_version: PROTOCOL_VERSION,
|
||||||
})?).await?;
|
server_certificate: self.cert.clone(),
|
||||||
|
server_random: self.server_random,
|
||||||
|
server_x25519_public_key: PublicKey::from(&StaticSecret::from(
|
||||||
|
self.private_key.to_bytes(),
|
||||||
|
))
|
||||||
|
.to_bytes(),
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
.await?;
|
||||||
self.inner.flush().await?;
|
self.inner.flush().await?;
|
||||||
|
|
||||||
trace!("sent server hello");
|
trace!("sent server hello");
|
||||||
|
@ -440,9 +517,17 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
let private_key = StaticSecret::from(self.private_key.to_bytes());
|
let private_key = StaticSecret::from(self.private_key.to_bytes());
|
||||||
let their_public_key = PublicKey::from(client_public_key);
|
let their_public_key = PublicKey::from(client_public_key);
|
||||||
|
|
||||||
assert_ne!(their_public_key.to_bytes(), PublicKey::from(&private_key).to_bytes());
|
assert_ne!(
|
||||||
|
their_public_key.to_bytes(),
|
||||||
|
PublicKey::from(&private_key).to_bytes()
|
||||||
|
);
|
||||||
|
|
||||||
danger_trace!("pr: {}, their pub: {}, my pub: {}", hex::encode(self.private_key.to_bytes()), hex::encode(client_public_key), hex::encode(self.private_key.verifying_key().to_bytes()));
|
danger_trace!(
|
||||||
|
"pr: {}, their pub: {}, my pub: {}",
|
||||||
|
hex::encode(self.private_key.to_bytes()),
|
||||||
|
hex::encode(client_public_key),
|
||||||
|
hex::encode(self.private_key.verifying_key().to_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
let shared_key = private_key.diffie_hellman(&their_public_key).to_bytes();
|
let shared_key = private_key.diffie_hellman(&their_public_key).to_bytes();
|
||||||
|
|
||||||
|
@ -476,7 +561,12 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
};
|
};
|
||||||
|
|
||||||
trace!("trying to decrypt 0x42");
|
trace!("trying to decrypt 0x42");
|
||||||
danger_trace!("ciphertext: {:?}, nonce: {:?}, aad: {:?}", payload.msg, nonce, payload.aad);
|
danger_trace!(
|
||||||
|
"ciphertext: {:?}, nonce: {:?}, aad: {:?}",
|
||||||
|
payload.msg,
|
||||||
|
nonce,
|
||||||
|
payload.aad
|
||||||
|
);
|
||||||
|
|
||||||
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
|
let hopefully_0x42 = match self.cipher.as_ref().unwrap().decrypt(nonce, payload) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
|
@ -486,7 +576,7 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
};
|
};
|
||||||
|
|
||||||
if hopefully_0x42 != vec![0x42] {
|
if hopefully_0x42 != vec![0x42] {
|
||||||
return Err(EpfHandshakeError::MissingKeyProof.into())
|
return Err(EpfHandshakeError::MissingKeyProof.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -494,15 +584,18 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
|
|
||||||
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
|
let encrypted_0x42 = match self.cipher.as_ref().unwrap().encrypt(nonce, payload) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(_) => {
|
Err(_) => return Err(EpfHandshakeError::EncryptionError.into()),
|
||||||
return Err(EpfHandshakeError::EncryptionError.into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.inner.write_all(&encode_packet(PACKET_FINISHED, &EpfFinished {
|
self.inner
|
||||||
protocol_version: PROTOCOL_VERSION,
|
.write_all(&encode_packet(
|
||||||
encrypted_0x42
|
PACKET_FINISHED,
|
||||||
})?).await?;
|
&EpfFinished {
|
||||||
|
protocol_version: PROTOCOL_VERSION,
|
||||||
|
encrypted_0x42,
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
.await?;
|
||||||
self.inner.flush().await?;
|
self.inner.flush().await?;
|
||||||
|
|
||||||
self.state = EpfServerState::WaitingForFinished;
|
self.state = EpfServerState::WaitingForFinished;
|
||||||
|
@ -512,7 +605,10 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upgrade(self) -> EpfServerStream<T> where Self: Sized {
|
async fn upgrade(self) -> EpfServerStream<T>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
EpfServerStream {
|
EpfServerStream {
|
||||||
aad: self.server_random,
|
aad: self.server_random,
|
||||||
server_cert: self.cert,
|
server_cert: self.cert,
|
||||||
|
@ -521,7 +617,7 @@ impl<T: AsyncWriteExt + AsyncReadExt + Send + Unpin> EpfServerHandshaker<T> for
|
||||||
cipher: self.cipher.unwrap(),
|
cipher: self.cipher.unwrap(),
|
||||||
private_key: self.private_key,
|
private_key: self.private_key,
|
||||||
public_key: self.public_key,
|
public_key: self.public_key,
|
||||||
raw_stream: self.inner
|
raw_stream: self.inner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -534,7 +630,7 @@ pub struct EpfServerStream<S: AsyncReadExt + AsyncWriteExt + Unpin> {
|
||||||
server_cert: EPFCertificate,
|
server_cert: EPFCertificate,
|
||||||
cipher: XChaCha20Poly1305,
|
cipher: XChaCha20Poly1305,
|
||||||
private_key: EpfPrivateKey,
|
private_key: EpfPrivateKey,
|
||||||
public_key: EpfPublicKey
|
public_key: EpfPublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -549,9 +645,7 @@ impl<S: AsyncReadExt + AsyncWriteExt + Unpin + Send> EpfStreamOps for EpfServerS
|
||||||
|
|
||||||
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
|
let ciphertext = match self.cipher.encrypt(&nonce, payload) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(_) => {
|
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into()),
|
||||||
return Err(io::Error::new(io::ErrorKind::Other, "Encryption error").into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let application_data = EpfApplicationData {
|
let application_data = EpfApplicationData {
|
||||||
protocol_version: PROTOCOL_VERSION,
|
protocol_version: PROTOCOL_VERSION,
|
||||||
|
@ -599,20 +693,23 @@ impl<S: AsyncReadExt + AsyncWriteExt + Unpin + Send> EpfStreamOps for EpfServerS
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::net::SocketAddr;
|
use crate::ca_pool::{EpfCaPool, EpfCaPoolOps};
|
||||||
use std::str::FromStr;
|
use crate::handshake_stream::{
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
ClientAuthentication, EpfClientHandshaker, EpfClientUpgradable, EpfClientUpgraded,
|
||||||
|
EpfServerHandshaker, EpfServerUpgradable, EpfServerUpgraded, EpfStreamOps,
|
||||||
|
};
|
||||||
|
use crate::pki::{EPFCertificate, EPFCertificateDetails, EpfPkiCertificateOps};
|
||||||
use ed25519_dalek::{SecretKey, SigningKey};
|
use ed25519_dalek::{SecretKey, SigningKey};
|
||||||
use log::{debug, trace};
|
use log::{debug, trace};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use tcp_test::channel;
|
use tcp_test::channel;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
use tokio::join;
|
use tokio::join;
|
||||||
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
use tokio::net::{TcpListener, TcpSocket, TcpStream};
|
||||||
use x25519_dalek::{PublicKey, StaticSecret};
|
use x25519_dalek::{PublicKey, StaticSecret};
|
||||||
use crate::ca_pool::{EpfCaPool, EpfCaPoolOps};
|
|
||||||
use crate::handshake_stream::{ClientAuthentication, EpfClientHandshaker, EpfClientUpgradable, EpfClientUpgraded, EpfServerHandshaker, EpfServerUpgradable, EpfServerUpgraded, EpfStreamOps};
|
|
||||||
use crate::pki::{EPFCertificate, EPFCertificateDetails, EpfPkiCertificateOps};
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
pub async fn stream_test() {
|
pub async fn stream_test() {
|
||||||
|
@ -620,7 +717,9 @@ mod tests {
|
||||||
|
|
||||||
let tcp_listener = TcpListener::bind("0.0.0.0:36116").await.unwrap();
|
let tcp_listener = TcpListener::bind("0.0.0.0:36116").await.unwrap();
|
||||||
|
|
||||||
let tcp_client_future = TcpSocket::new_v4().unwrap().connect(SocketAddr::from_str("127.0.0.1:36116").unwrap());
|
let tcp_client_future = TcpSocket::new_v4()
|
||||||
|
.unwrap()
|
||||||
|
.connect(SocketAddr::from_str("127.0.0.1:36116").unwrap());
|
||||||
|
|
||||||
let (a, b) = join![tcp_listener.accept(), tcp_client_future];
|
let (a, b) = join![tcp_listener.accept(), tcp_client_future];
|
||||||
|
|
||||||
|
@ -634,7 +733,11 @@ mod tests {
|
||||||
details: EPFCertificateDetails {
|
details: EPFCertificateDetails {
|
||||||
name: "Testing Server Certificate".to_string(),
|
name: "Testing Server Certificate".to_string(),
|
||||||
not_before: 0,
|
not_before: 0,
|
||||||
not_after: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 30,
|
not_after: SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
+ 30,
|
||||||
public_key: server_private_key.verifying_key().to_bytes(),
|
public_key: server_private_key.verifying_key().to_bytes(),
|
||||||
issuer_public_key: [0u8; 32],
|
issuer_public_key: [0u8; 32],
|
||||||
claims: Default::default(),
|
claims: Default::default(),
|
||||||
|
@ -644,13 +747,20 @@ mod tests {
|
||||||
};
|
};
|
||||||
server_cert.sign(&server_private_key).unwrap();
|
server_cert.sign(&server_private_key).unwrap();
|
||||||
|
|
||||||
debug!("{}", hex::encode(server_private_key.verifying_key().to_bytes()));
|
debug!(
|
||||||
|
"{}",
|
||||||
|
hex::encode(server_private_key.verifying_key().to_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
let mut client_cert = EPFCertificate {
|
let mut client_cert = EPFCertificate {
|
||||||
details: EPFCertificateDetails {
|
details: EPFCertificateDetails {
|
||||||
name: "Testing Client Certificate".to_string(),
|
name: "Testing Client Certificate".to_string(),
|
||||||
not_before: 0,
|
not_before: 0,
|
||||||
not_after: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 30,
|
not_after: SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
+ 30,
|
||||||
public_key: client_private_key.verifying_key().to_bytes(),
|
public_key: client_private_key.verifying_key().to_bytes(),
|
||||||
issuer_public_key: [0u8; 32],
|
issuer_public_key: [0u8; 32],
|
||||||
claims: Default::default(),
|
claims: Default::default(),
|
||||||
|
@ -667,8 +777,13 @@ mod tests {
|
||||||
cert_pool_2.insert(&client_cert);
|
cert_pool_2.insert(&client_cert);
|
||||||
cert_pool_2.insert(&server_cert);
|
cert_pool_2.insert(&server_cert);
|
||||||
|
|
||||||
let mut c: EpfClientUpgraded<TcpStream> = EpfClientUpgradable::upgrade(c, ClientAuthentication::Cert(Box::new(client_cert), client_private_key)).await;
|
let mut c: EpfClientUpgraded<TcpStream> = EpfClientUpgradable::upgrade(
|
||||||
let mut s: EpfServerUpgraded<TcpStream> = EpfServerUpgradable::upgrade(s, server_cert, server_private_key).await;
|
c,
|
||||||
|
ClientAuthentication::Cert(Box::new(client_cert), client_private_key),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let mut s: EpfServerUpgraded<TcpStream> =
|
||||||
|
EpfServerUpgradable::upgrade(s, server_cert, server_private_key).await;
|
||||||
|
|
||||||
let server_handshake_accept_task = tokio::spawn(async move {
|
let server_handshake_accept_task = tokio::spawn(async move {
|
||||||
trace!("starting server handshake listener");
|
trace!("starting server handshake listener");
|
||||||
|
@ -702,6 +817,11 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(ss_1.to_bytes(), ss_2.to_bytes());
|
assert_eq!(ss_1.to_bytes(), ss_2.to_bytes());
|
||||||
|
|
||||||
println!("SS: {}, B_p: {}, A_p: {}", hex::encode(ss_1.to_bytes()), hex::encode(bob_pub.to_bytes()), hex::encode(alice_pub.to_bytes()));
|
println!(
|
||||||
|
"SS: {}, B_p: {}, A_p: {}",
|
||||||
|
hex::encode(ss_1.to_bytes()),
|
||||||
|
hex::encode(bob_pub.to_bytes()),
|
||||||
|
hex::encode(alice_pub.to_bytes())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
pub mod ca_pool;
|
pub mod ca_pool;
|
||||||
pub mod pki;
|
|
||||||
pub mod util;
|
|
||||||
pub mod protocol;
|
|
||||||
pub mod handshake_stream;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod handshake_stream;
|
||||||
|
pub mod pki;
|
||||||
|
pub mod protocol;
|
||||||
|
pub mod util;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod log;
|
pub mod log;
|
||||||
|
|
|
@ -14,4 +14,4 @@ macro_rules! danger_trace {
|
||||||
log::trace!($($arg)+)
|
log::trace!($($arg)+)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,7 +202,7 @@ impl EpfPkiCertificateOps for EPFCertificate {
|
||||||
} else {
|
} else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
} else if let Some(cert) = ca_pool.get_ca(&verifying_key(&self.details.issuer_public_key) ){
|
} else if let Some(cert) = ca_pool.get_ca(&verifying_key(&self.details.issuer_public_key)) {
|
||||||
cert
|
cert
|
||||||
} else {
|
} else {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
|
@ -272,9 +272,11 @@ impl EpfPkiSerializable for EPFCertificate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
Ok(pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes_pki()?))
|
Ok(
|
||||||
.as_bytes()
|
pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes_pki()?))
|
||||||
.to_vec())
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
||||||
|
@ -303,9 +305,11 @@ impl EpfPkiSerializable for EpfPublicKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
Ok(pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes().to_vec()))
|
Ok(
|
||||||
.as_bytes()
|
pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes().to_vec()))
|
||||||
.to_vec())
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
||||||
|
@ -316,7 +320,11 @@ impl EpfPkiSerializable for EpfPublicKey {
|
||||||
if pem.tag() != Self::PEM_BANNER {
|
if pem.tag() != Self::PEM_BANNER {
|
||||||
return Err("Not a public key".into());
|
return Err("Not a public key".into());
|
||||||
}
|
}
|
||||||
Ok(Self::from_bytes(pem.contents().try_into().map_err(|_| -> Box<dyn Error> { "Wrong size".into() })?)?)
|
Ok(Self::from_bytes(
|
||||||
|
pem.contents()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| -> Box<dyn Error> { "Wrong size".into() })?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,9 +342,11 @@ impl EpfPkiSerializable for EpfPrivateKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
fn as_pem(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
Ok(pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes_pki()?))
|
Ok(
|
||||||
.as_bytes()
|
pem::encode(&Pem::new(Self::PEM_BANNER, self.as_bytes_pki()?))
|
||||||
.to_vec())
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
fn from_pem(bytes: &[u8]) -> Result<Self, Box<dyn Error>>
|
||||||
|
@ -347,7 +357,11 @@ impl EpfPkiSerializable for EpfPrivateKey {
|
||||||
if pem.tag() != Self::PEM_BANNER {
|
if pem.tag() != Self::PEM_BANNER {
|
||||||
return Err("Incorrect PEM tag".into());
|
return Err("Incorrect PEM tag".into());
|
||||||
}
|
}
|
||||||
Ok(Self::from_keypair_bytes(pem.contents().try_into().map_err(|_| -> Box<dyn Error> { "Wrong size".into() })?)?)
|
Ok(Self::from_keypair_bytes(
|
||||||
|
pem.contents()
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| -> Box<dyn Error> { "Wrong size".into() })?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,12 +373,12 @@ mod tests {
|
||||||
EpfPkiCertificateValidationError, EpfPkiSerializable, EpfPrivateKey, EpfPublicKey,
|
EpfPkiCertificateValidationError, EpfPkiSerializable, EpfPrivateKey, EpfPublicKey,
|
||||||
EPFPKI_PUBLIC_KEY_LENGTH, EPFPKI_SIGNATURE_LENGTH,
|
EPFPKI_PUBLIC_KEY_LENGTH, EPFPKI_SIGNATURE_LENGTH,
|
||||||
};
|
};
|
||||||
|
use crate::util::{signing_key, verifying_key};
|
||||||
use ed25519_dalek::{SignatureError, SigningKey};
|
use ed25519_dalek::{SignatureError, SigningKey};
|
||||||
|
use hex_literal::hex;
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use hex_literal::hex;
|
|
||||||
use crate::util::{signing_key, verifying_key};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn certificate_serialization() {
|
pub fn certificate_serialization() {
|
||||||
|
@ -373,7 +387,10 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn certificate_deserialization() {
|
pub fn certificate_deserialization() {
|
||||||
assert_eq!(EPFCertificate::from_bytes_pki(&cert_bytes()).unwrap(), cert())
|
assert_eq!(
|
||||||
|
EPFCertificate::from_bytes_pki(&cert_bytes()).unwrap(),
|
||||||
|
cert()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -394,17 +411,26 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn pubkey_serialization() {
|
pub fn pubkey_serialization() {
|
||||||
assert_eq!((verifying_key(&[0u8; 32])).as_bytes_pki().unwrap(), [0u8; 32].to_vec())
|
assert_eq!(
|
||||||
|
(verifying_key(&[0u8; 32])).as_bytes_pki().unwrap(),
|
||||||
|
[0u8; 32].to_vec()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn pubkey_deserialization() {
|
pub fn pubkey_deserialization() {
|
||||||
assert_eq!(EpfPublicKey::from_bytes(&[0u8; 32]).unwrap(), verifying_key(&[0u8; 32]))
|
assert_eq!(
|
||||||
|
EpfPublicKey::from_bytes(&[0u8; 32]).unwrap(),
|
||||||
|
verifying_key(&[0u8; 32])
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn pubkey_serialization_pem() {
|
pub fn pubkey_serialization_pem() {
|
||||||
assert_eq!((verifying_key(&[0u8; 32])).as_pem().unwrap(), null_public_key_pem())
|
assert_eq!(
|
||||||
|
(verifying_key(&[0u8; 32])).as_pem().unwrap(),
|
||||||
|
null_public_key_pem()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -424,21 +450,30 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
pub fn privkey_serialization() {
|
pub fn privkey_serialization() {
|
||||||
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
||||||
assert_eq!((signing_key(&priv_key_data)).as_bytes_pki().unwrap(), priv_key_data.to_vec())
|
assert_eq!(
|
||||||
|
(signing_key(&priv_key_data)).as_bytes_pki().unwrap(),
|
||||||
|
priv_key_data.to_vec()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn privkey_deserialization() {
|
pub fn privkey_deserialization() {
|
||||||
let priv_key = EpfPrivateKey::generate(&mut OsRng);
|
let priv_key = EpfPrivateKey::generate(&mut OsRng);
|
||||||
|
|
||||||
assert_eq!(priv_key.to_keypair_bytes(), signing_key(&priv_key.to_keypair_bytes()).to_keypair_bytes())
|
assert_eq!(
|
||||||
|
priv_key.to_keypair_bytes(),
|
||||||
|
signing_key(&priv_key.to_keypair_bytes()).to_keypair_bytes()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn privkey_serialization_pem() {
|
pub fn privkey_serialization_pem() {
|
||||||
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
||||||
|
|
||||||
assert_eq!((signing_key(&priv_key_data)).as_pem().unwrap(), null_private_key_pem())
|
assert_eq!(
|
||||||
|
(signing_key(&priv_key_data)).as_pem().unwrap(),
|
||||||
|
null_private_key_pem()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -446,7 +481,9 @@ mod tests {
|
||||||
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
let priv_key_data = hex!("00000000000000000000000000000000000000000000000000000000000000003B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EpfPrivateKey::from_pem(&null_private_key_pem()).unwrap().to_keypair_bytes(),
|
EpfPrivateKey::from_pem(&null_private_key_pem())
|
||||||
|
.unwrap()
|
||||||
|
.to_keypair_bytes(),
|
||||||
signing_key(&priv_key_data).to_keypair_bytes()
|
signing_key(&priv_key_data).to_keypair_bytes()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -455,7 +492,9 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
pub fn privkey_deserialization_pem_wrong_tag() {
|
pub fn privkey_deserialization_pem_wrong_tag() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
EpfPrivateKey::from_pem(&null_public_key_pem()).unwrap().to_keypair_bytes(),
|
EpfPrivateKey::from_pem(&null_public_key_pem())
|
||||||
|
.unwrap()
|
||||||
|
.to_keypair_bytes(),
|
||||||
signing_key(&[0u8; 64]).to_keypair_bytes()
|
signing_key(&[0u8; 64]).to_keypair_bytes()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -632,9 +671,7 @@ mod tests {
|
||||||
fingerprint: "".to_string(),
|
fingerprint: "".to_string(),
|
||||||
signature: [0u8; EPFPKI_SIGNATURE_LENGTH],
|
signature: [0u8; EPFPKI_SIGNATURE_LENGTH],
|
||||||
};
|
};
|
||||||
not_trusted_cert
|
not_trusted_cert.sign(&private_key).unwrap();
|
||||||
.sign(&private_key)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let ca_pool = EpfCaPool::new();
|
let ca_pool = EpfCaPool::new();
|
||||||
|
|
||||||
|
@ -866,6 +903,15 @@ mod tests {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn null_private_key_pem() -> Vec<u8> {
|
fn null_private_key_pem() -> Vec<u8> {
|
||||||
vec![45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 69, 80, 70, 32, 80, 82, 73, 86, 65, 84, 69, 32, 75, 69, 89, 45, 45, 45, 45, 45, 13, 10, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 55, 97, 105, 101, 56, 122, 114, 97, 107, 76, 87, 75, 106, 113, 78, 65, 113, 98, 119, 49, 122, 13, 10, 90, 84, 73, 86, 100, 120, 51, 105, 81, 54, 89, 54, 119, 69, 105, 104, 105, 49, 110, 97, 75, 81, 61, 61, 13, 10, 45, 45, 45, 45, 45, 69, 78, 68, 32, 69, 80, 70, 32, 80, 82, 73, 86, 65, 84, 69, 32, 75, 69, 89, 45, 45, 45, 45, 45, 13, 10]
|
vec![
|
||||||
|
45, 45, 45, 45, 45, 66, 69, 71, 73, 78, 32, 69, 80, 70, 32, 80, 82, 73, 86, 65, 84, 69,
|
||||||
|
32, 75, 69, 89, 45, 45, 45, 45, 45, 13, 10, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
|
||||||
|
65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65,
|
||||||
|
65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 55, 97, 105, 101, 56, 122, 114, 97, 107, 76,
|
||||||
|
87, 75, 106, 113, 78, 65, 113, 98, 119, 49, 122, 13, 10, 90, 84, 73, 86, 100, 120, 51,
|
||||||
|
105, 81, 54, 89, 54, 119, 69, 105, 104, 105, 49, 110, 97, 75, 81, 61, 61, 13, 10, 45,
|
||||||
|
45, 45, 45, 45, 69, 78, 68, 32, 69, 80, 70, 32, 80, 82, 73, 86, 65, 84, 69, 32, 75, 69,
|
||||||
|
89, 45, 45, 45, 45, 45, 13, 10,
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use std::error::Error;
|
use crate::pki::{EPFCertificate, EPFPKI_PUBLIC_KEY_LENGTH};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::{AsyncReadExt};
|
use std::error::Error;
|
||||||
use crate::pki::{EPFCertificate, EPFPKI_PUBLIC_KEY_LENGTH};
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 1;
|
pub const PROTOCOL_VERSION: u32 = 1;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct EpfMessage {
|
pub struct EpfMessage {
|
||||||
pub packet_id: u32,
|
pub packet_id: u32,
|
||||||
pub packet_data: Vec<u8>
|
pub packet_data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PACKET_CLIENT_HELLO: u32 = 1;
|
pub const PACKET_CLIENT_HELLO: u32 = 1;
|
||||||
|
@ -19,7 +19,7 @@ pub struct EpfClientHello {
|
||||||
pub protocol_version: u32,
|
pub protocol_version: u32,
|
||||||
pub client_random: [u8; 24],
|
pub client_random: [u8; 24],
|
||||||
pub client_certificate: Option<EPFCertificate>,
|
pub client_certificate: Option<EPFCertificate>,
|
||||||
pub client_x25519_public_key: [u8; EPFPKI_PUBLIC_KEY_LENGTH]
|
pub client_x25519_public_key: [u8; EPFPKI_PUBLIC_KEY_LENGTH],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PACKET_SERVER_HELLO: u32 = 2;
|
pub const PACKET_SERVER_HELLO: u32 = 2;
|
||||||
|
@ -29,7 +29,7 @@ pub struct EpfServerHello {
|
||||||
pub protocol_version: u32,
|
pub protocol_version: u32,
|
||||||
pub server_certificate: EPFCertificate,
|
pub server_certificate: EPFCertificate,
|
||||||
pub server_random: [u8; 16],
|
pub server_random: [u8; 16],
|
||||||
pub server_x25519_public_key: [u8; EPFPKI_PUBLIC_KEY_LENGTH]
|
pub server_x25519_public_key: [u8; EPFPKI_PUBLIC_KEY_LENGTH],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PACKET_FINISHED: u32 = 3;
|
pub const PACKET_FINISHED: u32 = 3;
|
||||||
|
@ -37,7 +37,7 @@ pub const PACKET_FINISHED: u32 = 3;
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct EpfFinished {
|
pub struct EpfFinished {
|
||||||
pub protocol_version: u32,
|
pub protocol_version: u32,
|
||||||
pub encrypted_0x42: Vec<u8>
|
pub encrypted_0x42: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PACKET_APPLICATION_DATA: u32 = 4;
|
pub const PACKET_APPLICATION_DATA: u32 = 4;
|
||||||
|
@ -46,7 +46,7 @@ pub const PACKET_APPLICATION_DATA: u32 = 4;
|
||||||
pub struct EpfApplicationData {
|
pub struct EpfApplicationData {
|
||||||
pub protocol_version: u32,
|
pub protocol_version: u32,
|
||||||
pub encrypted_application_data: Vec<u8>,
|
pub encrypted_application_data: Vec<u8>,
|
||||||
pub nonce: [u8; 24]
|
pub nonce: [u8; 24],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -55,7 +55,7 @@ pub enum EpfClientState {
|
||||||
WaitingForServerHello,
|
WaitingForServerHello,
|
||||||
WaitingForFinished,
|
WaitingForFinished,
|
||||||
Transport,
|
Transport,
|
||||||
Closed
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -63,10 +63,13 @@ pub enum EpfServerState {
|
||||||
WaitingForClientHello,
|
WaitingForClientHello,
|
||||||
WaitingForFinished,
|
WaitingForFinished,
|
||||||
Transport,
|
Transport,
|
||||||
Closed
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode_packet<T: Serialize>(id: u32, packet: &T) -> Result<Vec<u8>, rmp_serde::encode::Error> {
|
pub fn encode_packet<T: Serialize>(
|
||||||
|
id: u32,
|
||||||
|
packet: &T,
|
||||||
|
) -> Result<Vec<u8>, rmp_serde::encode::Error> {
|
||||||
let message_data = rmp_serde::to_vec(packet)?;
|
let message_data = rmp_serde::to_vec(packet)?;
|
||||||
let message_wrapper = EpfMessage {
|
let message_wrapper = EpfMessage {
|
||||||
packet_id: id,
|
packet_id: id,
|
||||||
|
@ -80,11 +83,13 @@ pub fn encode_packet<T: Serialize>(id: u32, packet: &T) -> Result<Vec<u8>, rmp_s
|
||||||
Ok(packet)
|
Ok(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recv_packet<C: AsyncReadExt + Unpin>(stream: &mut C) -> Result<EpfMessage, Box<dyn Error>> {
|
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 packet_length = stream.read_u64_le().await?;
|
||||||
|
|
||||||
let mut packet_data_buf = vec![0u8; packet_length as usize];
|
let mut packet_data_buf = vec![0u8; packet_length as usize];
|
||||||
stream.read_exact(&mut packet_data_buf).await?;
|
stream.read_exact(&mut packet_data_buf).await?;
|
||||||
let message: EpfMessage = rmp_serde::from_slice(&packet_data_buf)?;
|
let message: EpfMessage = rmp_serde::from_slice(&packet_data_buf)?;
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use ed25519_dalek::{SigningKey, VerifyingKey};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
use ed25519_dalek::{SigningKey, VerifyingKey};
|
|
||||||
|
|
||||||
pub fn pretty_print_date(date: &SystemTime) -> String {
|
pub fn pretty_print_date(date: &SystemTime) -> String {
|
||||||
let datetime: DateTime<Utc> = (*date).into();
|
let datetime: DateTime<Utc> = (*date).into();
|
||||||
|
@ -17,4 +17,4 @@ pub fn verifying_key(key: &[u8; 32]) -> VerifyingKey {
|
||||||
}
|
}
|
||||||
pub fn signing_key(key: &[u8; 64]) -> SigningKey {
|
pub fn signing_key(key: &[u8; 64]) -> SigningKey {
|
||||||
SigningKey::from_keypair_bytes(key).unwrap()
|
SigningKey::from_keypair_bytes(key).unwrap()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue