From 8e1f2327f72fdfb4440b0f275dfce166645c1e1e Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 22 Apr 2022 17:03:41 +0200 Subject: [PATCH] Add public key validation to encryption layer options Signed-off-by: trivernis --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/error.rs | 6 ++++ src/lib.rs | 1 + src/protocol/encrypted/crypt_handling.rs | 28 +++++++++++----- src/protocol/encrypted/mod.rs | 21 +++++++++--- src/protocol/encrypted/protocol_impl.rs | 7 ++-- src/utils.rs | 11 +++++++ tests/test_encryption.rs | 41 +++++++++++++++++++++++- 9 files changed, 99 insertions(+), 20 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 2bd55d12..489edbf1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ dependencies = [ [[package]] name = "bromine" -version = "0.20.1" +version = "0.21.0" dependencies = [ "async-trait", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 2182ea62..6afbd580 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bromine" -version = "0.20.1" +version = "0.21.0" authors = ["trivernis "] edition = "2018" readme = "README.md" diff --git a/src/error.rs b/src/error.rs index 2985edfd..6f0dca16 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,8 @@ use crate::error_event::ErrorEventData; use thiserror::Error; use tokio::sync::oneshot; +#[cfg(feature = "encryption_layer")] +use x25519_dalek::PublicKey; pub type Result = std::result::Result; @@ -39,6 +41,10 @@ pub enum Error { #[error("Invalid state")] InvalidState, + + #[cfg(feature = "encryption_layer")] + #[error("Connection of unknown peer with key {0:?} refused")] + UnknownPeer(PublicKey), } impl Error { diff --git a/src/lib.rs b/src/lib.rs index 6eba0549..bdcc1e95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,7 @@ pub mod ipc; mod macros; mod namespaces; pub mod protocol; +pub mod utils; /// Reexported for usage in payload implementations pub use bytes; diff --git a/src/protocol/encrypted/crypt_handling.rs b/src/protocol/encrypted/crypt_handling.rs index 27ee21aa..4e8de598 100644 --- a/src/protocol/encrypted/crypt_handling.rs +++ b/src/protocol/encrypted/crypt_handling.rs @@ -1,5 +1,5 @@ -use crate::prelude::encrypted::EncryptedStream; -use crate::prelude::IPCResult; +use crate::prelude::encrypted::{EncryptedStream, Keys}; +use crate::prelude::{IPCError, IPCResult}; use crate::protocol::AsyncProtocolStream; use bytes::Bytes; use chacha20poly1305::aead::{Aead, NewAead}; @@ -105,15 +105,21 @@ impl EncryptedStream { /// 4. The server generates a new secret /// 5. The server sends the secret to the client /// 6. The connection is upgraded with the new shared key - pub async fn from_server_key_exchange(mut inner: T, secret: StaticSecret) -> IPCResult { + pub async fn from_server_key_exchange(mut inner: T, keys: &Keys) -> IPCResult { let other_pub = receive_public_key(&mut inner).await?; - send_public_key(&mut inner, &secret).await?; - let shared_secret = secret.diffie_hellman(&other_pub); + tracing::debug!("received peer public key {:?}", other_pub); + + if !keys.allow_unknown && !keys.known_peers.contains(&other_pub) { + return Err(IPCError::UnknownPeer(other_pub)); + } + send_public_key(&mut inner, &keys.secret).await?; + let shared_secret = keys.secret.diffie_hellman(&other_pub); let mut stream = Self::new(inner, shared_secret); let permanent_secret = generate_secret(); stream.write_all(&permanent_secret).await?; stream.flush().await?; stream.update_key(permanent_secret.into()); + tracing::debug!("Connection established"); Ok(stream) } @@ -124,14 +130,20 @@ impl EncryptedStream { /// 3. The client creates an intermediary encrypted connection /// 4. The client receives the new key from the server /// 5. The connection is upgraded with the new shared key - pub async fn from_client_key_exchange(mut inner: T, secret: StaticSecret) -> IPCResult { - send_public_key(&mut inner, &secret).await?; + pub async fn from_client_key_exchange(mut inner: T, keys: &Keys) -> IPCResult { + send_public_key(&mut inner, &keys.secret).await?; let other_pub = receive_public_key(&mut inner).await?; - let shared_secret = secret.diffie_hellman(&other_pub); + tracing::debug!("received peer public key {:?}", other_pub); + + if !keys.allow_unknown && !keys.known_peers.contains(&other_pub) { + return Err(IPCError::UnknownPeer(other_pub)); + } + let shared_secret = keys.secret.diffie_hellman(&other_pub); let mut stream = Self::new(inner, shared_secret); let mut key_buf = vec![0u8; 32]; stream.read_exact(&mut key_buf).await?; stream.update_key(key_buf.into()); + tracing::debug!("Connection established"); Ok(stream) } diff --git a/src/protocol/encrypted/mod.rs b/src/protocol/encrypted/mod.rs index b7eb4675..62133f0c 100644 --- a/src/protocol/encrypted/mod.rs +++ b/src/protocol/encrypted/mod.rs @@ -10,7 +10,7 @@ use std::future::Future; use std::io; use std::pin::Pin; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite}; -use x25519_dalek::{SharedSecret, StaticSecret}; +use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; use crate::prelude::encrypted::crypt_handling::CipherBox; use crate::prelude::{AsyncProtocolStream, AsyncStreamProtocolListener}; @@ -20,7 +20,14 @@ pub type OptionalFuture = Option + Send + Sync #[derive(Clone)] pub struct EncryptionOptions { pub inner_options: T, + pub keys: Keys, +} + +#[derive(Clone)] +pub struct Keys { pub secret: StaticSecret, + pub known_peers: Vec, + pub allow_unknown: bool, } impl Default for EncryptionOptions { @@ -30,7 +37,11 @@ impl Default for EncryptionOptions { rng.fill_bytes(&mut secret); Self { - secret: StaticSecret::from(secret), + keys: Keys { + known_peers: Vec::new(), + allow_unknown: false, + secret: StaticSecret::from(secret), + }, inner_options: T::default(), } } @@ -38,12 +49,12 @@ impl Default for EncryptionOptions { pub struct EncryptedListener { inner: T, - secret: StaticSecret, + keys: Keys, } impl EncryptedListener { - pub fn new(inner: T, secret: StaticSecret) -> Self { - Self { inner, secret } + pub fn new(inner: T, keys: Keys) -> Self { + Self { inner, keys } } } diff --git a/src/protocol/encrypted/protocol_impl.rs b/src/protocol/encrypted/protocol_impl.rs index 53d664d2..4c90c984 100644 --- a/src/protocol/encrypted/protocol_impl.rs +++ b/src/protocol/encrypted/protocol_impl.rs @@ -18,13 +18,12 @@ impl AsyncStreamProtocolListener for EncryptedLi ) -> IPCResult { let inner = T::protocol_bind(address, options.inner_options).await?; - Ok(EncryptedListener::new(inner, options.secret)) + Ok(EncryptedListener::new(inner, options.keys)) } async fn protocol_accept(&self) -> IPCResult<(Self::Stream, Self::RemoteAddressType)> { let (inner_stream, remote_addr) = self.inner.protocol_accept().await?; - let stream = - Self::Stream::from_server_key_exchange(inner_stream, self.secret.clone()).await?; + let stream = Self::Stream::from_server_key_exchange(inner_stream, &self.keys).await?; Ok((stream, remote_addr)) } @@ -40,7 +39,7 @@ impl AsyncProtocolStream for EncryptedStream { options: Self::StreamOptions, ) -> Result { let inner = T::protocol_connect(address, options.inner_options).await?; - EncryptedStream::from_client_key_exchange(inner, options.secret).await + EncryptedStream::from_client_key_exchange(inner, &options.keys).await } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..a5b247cf --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "encryption_layer")] +/// Generates a secret that can be passed to the options of the encryption layer and for creating +/// a public key +pub fn generate_secret() -> x25519_dalek::StaticSecret { + let mut rng = rand::thread_rng(); + use rand_core::RngCore; + let mut secret = [0u8; 32]; + rng.fill_bytes(&mut secret); + + x25519_dalek::StaticSecret::from(secret) +} diff --git a/tests/test_encryption.rs b/tests/test_encryption.rs index 1bfa5ea7..a53547c1 100644 --- a/tests/test_encryption.rs +++ b/tests/test_encryption.rs @@ -1,19 +1,38 @@ #![cfg(feature = "encryption_layer")] + use crate::utils::call_counter::increment_counter_for_event; use crate::utils::protocol::TestProtocolListener; use crate::utils::{get_free_port, start_server_and_client}; -use bromine::prelude::encrypted::EncryptedListener; +use bromine::prelude::encrypted::{EncryptedListener, EncryptionOptions, Keys}; use bromine::prelude::*; +use bromine::utils::generate_secret; use bromine::IPCBuilder; use byteorder::{BigEndian, ReadBytesExt}; use bytes::{BufMut, Bytes, BytesMut}; +use dashmap::DashMap; use futures::StreamExt; +use lazy_static::lazy_static; use rand_core::RngCore; use std::io::Read; use std::time::Duration; +use x25519_dalek::{PublicKey, StaticSecret}; mod utils; +pub fn get_secret>(name: S) -> StaticSecret { + lazy_static! { + static ref KEYS: DashMap = DashMap::new(); + } + if KEYS.contains_key(name.as_ref()) { + KEYS.get(name.as_ref()).as_ref().unwrap().value().clone() + } else { + let secret = generate_secret(); + KEYS.insert(name.as_ref().to_string(), secret.clone()); + + secret + } +} + #[tokio::test] async fn it_sends_and_receives_smaller_packages() { send_and_receive_bytes(140).await.unwrap(); @@ -66,7 +85,27 @@ async fn get_client_with_server() -> Context { } fn get_builder(port: u8) -> IPCBuilder> { + let server_secret = get_secret(format!("server-{}", port)); + let client_secret = get_secret(format!("client-{}", port)); + let client_keys = Keys { + secret: client_secret.clone(), + known_peers: vec![PublicKey::from(&server_secret)], + allow_unknown: false, + }; + let server_keys = Keys { + secret: server_secret.clone(), + known_peers: vec![PublicKey::from(&client_secret)], + allow_unknown: false, + }; IPCBuilder::new() + .client_options(EncryptionOptions { + keys: client_keys, + inner_options: (), + }) + .server_options(EncryptionOptions { + keys: server_keys, + inner_options: (), + }) .address(port) .on("bytes", callback!(handle_bytes)) .on("string", callback!(handle_string))