You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rsh/src/sock/enc.rs

431 lines
13 KiB

//! Socket encryption wrapper
use super::*;
use cryptohelpers::{
rsa::{
self,
RsaPublicKey,
RsaPrivateKey,
openssl::{
symm::Crypter,
error::ErrorStack,
},
},
sha256,
};
use chacha20stream::{
AsyncSink,
AsyncSource,
Key, IV,
cha,
};
use std::sync::Arc;
use tokio::{
sync::{
RwLock,
RwLockReadGuard,
RwLockWriteGuard,
},
};
use std::{
io,
fmt,
task::{
Context, Poll,
},
pin::Pin,
marker::Unpin,
};
use smallvec::SmallVec;
/// Size of a single RSA ciphertext.
pub const RSA_CIPHERTEXT_SIZE: usize = 512;
/// A single, full block of RSA ciphertext.
type RsaCiphertextBlock = [u8; RSA_CIPHERTEXT_SIZE];
/// Max size to read when exchanging keys
const TRANS_KEY_MAX_SIZE: usize = 4096;
/// Encrypted socket information.
#[derive(Debug)]
struct ESockInfo {
us: RsaPrivateKey,
them: Option<RsaPublicKey>,
}
impl ESockInfo
{
/// Generate a new private key
pub fn new(us: impl Into<RsaPrivateKey>) -> Self
{
Self {
us: us.into(),
them: None,
}
}
/// Generate a new private key for the local endpoint
pub fn generate() -> Result<Self, rsa::Error>
{
Ok(Self::new(RsaPrivateKey::generate()?))
}
}
/// The encryption state of the Tx and Rx instances.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
struct ESockState {
encr: bool,
encw: bool,
}
impl Default for ESockState
{
#[inline]
fn default() -> Self
{
Self {
encr: false,
encw: false,
}
}
}
/// Contains a cc20 Key and IV that can be serialized and then encrypted
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct ESockSessionKey
{
key: Key,
iv: IV,
}
impl fmt::Display for ESockSessionKey
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "Key: {}, IV: {}", self.key.hex(), self.iv.hex())
}
}
impl ESockSessionKey
{
/// Generate a new cc20 key + iv,
pub fn generate() -> Self
{
let (key,iv) = cha::keygen();
Self{key,iv}
}
/// Generate an encryption device
pub fn to_decrypter(&self) -> Result<Crypter, ErrorStack>
{
cha::decrypter(&self.key, &self.iv)
}
/// Generate an encryption device
pub fn to_encrypter(&self) -> Result<Crypter, ErrorStack>
{
cha::encrypter(&self.key, &self.iv)
}
/// Encrypt with RSA
pub fn to_ciphertext<K: ?Sized + rsa::PublicKey>(&self, rsa_key: &K) -> eyre::Result<RsaCiphertextBlock>
{
let mut output = [0u8; RSA_CIPHERTEXT_SIZE];
let mut temp = SmallVec::<[u8; RSA_CIPHERTEXT_SIZE]>::new(); // We know size will fit into here.
serde_cbor::to_writer(&mut temp, self)
.wrap_err(eyre!("Failed to CBOR encode session key to buffer"))
.with_section(|| self.clone().header("Session key was"))?;
debug_assert!(temp.len() < RSA_CIPHERTEXT_SIZE);
let _wr = rsa::encrypt_slice_sync(&temp, rsa_key, &mut &mut output[..])
.wrap_err(eyre!("Failed to encrypt session key with RSA public key"))
.with_section(|| self.clone().header("Session key was"))
.with_section({let temp = temp.len(); move || temp.header("Encoded data size was")})
.with_section(move || base64::encode(temp).header("Encoded data (base64) was"))?;
debug_assert_eq!(_wr, output.len());
Ok(output)
}
/// Decrypt from RSA
pub fn from_ciphertext<K: ?Sized + rsa::PrivateKey>(data: &[u8; RSA_CIPHERTEXT_SIZE], rsa_key: &K) -> eyre::Result<Self>
where <K as rsa::PublicKey>::KeyType: rsa::openssl::pkey::HasPrivate //ugh, why do we have to have this bound??? it should be implied ffs... :/
{
let mut temp = SmallVec::<[u8; RSA_CIPHERTEXT_SIZE]>::new();
rsa::decrypt_slice_sync(data, rsa_key, &mut temp)
.wrap_err(eyre!("Failed to decrypt ciphertext to session key"))
.with_section({let data = data.len(); move || data.header("Ciphertext length was")})
.with_section(|| base64::encode(data).header("Ciphertext was"))?;
Ok(serde_cbor::from_slice(&temp[..])
.wrap_err(eyre!("Failed to decode CBOR data to session key object"))
.with_section({let temp = temp.len(); move || temp.header("Encoded data size was")})
.with_section(move || base64::encode(temp).header("Encoded data (base64) was"))?)
}
}
/// A tx+rx socket.
#[pin_project]
#[derive(Debug)]
pub struct ESock<W, R> {
info: ESockInfo,
state: ESockState,
#[pin]
rx: AsyncSource<R>,
#[pin]
tx: AsyncSink<W>,
}
impl<W: AsyncWrite, R: AsyncRead> ESock<W, R>
{
pub fn inner(&self) -> (&W, &R)
{
(self.tx.inner(), self.rx.inner())
}
fn inner_mut(&mut self) -> (&mut W, &mut R)
{
(self.tx.inner_mut(), self.rx.inner_mut())
}
///Get a mutable ref to unencrypted read+write
fn unencrypted(&mut self) -> (&mut W, &mut R)
{
(self.tx.inner_mut(), self.rx.inner_mut())
}
/// Get a mutable ref to encrypted write+read
fn encrypted(&mut self) -> (&mut AsyncSink<W>, &mut AsyncSource<R>)
{
(&mut self.tx, &mut self.rx)
}
/// Have the RSA keys been exchanged?
pub fn has_exchanged(&self) -> bool
{
self.info.them.is_some()
}
/// Is the Write + Read operation encrypted? Tuple is `(Tx, Rx)`.
pub fn is_encrypted(&self) -> (bool, bool)
{
(self.state.encw, self.state.encr)
}
}
impl<W: AsyncWrite+ Unpin, R: AsyncRead + Unpin> ESock<W, R>
{
/// Get the Tx and Rx of the stream.
///
/// # Returns
/// Returns encrypted stream halfs if the stream is encrypted, unencrypted if not.
pub fn stream(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_))
{
(if self.state.encw {
&mut self.tx
} else {
self.tx.inner_mut()
}, if self.state.encr {
&mut self.rx
} else {
self.rx.inner_mut()
})
}
/// Enable write encryption
pub async fn set_encrypted_write(&mut self, set: bool) -> eyre::Result<()>
{
use tokio::prelude::*;
if set {
let session_key = ESockSessionKey::generate();
let data = {
let them = self.info.them.as_ref().expect("Cannot set encrypted write when keys have not been exchanged");
session_key.to_ciphertext(them)
.wrap_err(eyre!("Failed to encrypt session key with foreign endpoint's key"))
.with_section(|| session_key.to_string().header("Session key was"))
.with_section(|| them.to_string().header("Foreign pubkey was"))?
};
let crypter = session_key.to_encrypter()
.wrap_err(eyre!("Failed to create encryption device from session key for Tx"))
.with_section(|| session_key.to_string().header("Session key was"))?;
// Send rsa `data` over unencrypted endpoint
self.unencrypted().0.write_all(&data[..]).await
.wrap_err(eyre!("Failed to write ciphertext to endpoint"))
.with_section(|| data.to_base64_string().header("Ciphertext of session key was"))?;
// Set crypter of `tx` to `session_key`.
*self.tx.crypter_mut() = crypter;
// Set `encw` to true
self.state.encw = true;
Ok(())
} else {
self.state.encw = false;
Ok(())
}
}
/// Enable read encryption
///
/// The other endpoint must have sent a `set_encrypted_write()`
pub async fn set_encrypted_read(&mut self, set: bool) -> eyre::Result<()>
{
use tokio::prelude::*;
if set {
let mut data = [0u8; RSA_CIPHERTEXT_SIZE];
// Read `data` from unencrypted endpoint
self.unencrypted().1.read_exact(&mut data[..]).await
.wrap_err(eyre!("Failed to read ciphertext from endpoint"))?;
// Decrypt `data`
let session_key = ESockSessionKey::from_ciphertext(&data, &self.info.us)
.wrap_err(eyre!("Failed to decrypt session key from ciphertext"))
.with_section(|| data.to_base64_string().header("Ciphertext was"))
.with_section(|| self.info.us.to_string().header("Our RSA key is"))?;
// Set crypter of `rx` to `session_key`.
*self.rx.crypter_mut() = session_key.to_decrypter()
.wrap_err(eyre!("Failed to create decryption device from session key for Rx"))
.with_section(|| session_key.to_string().header("Decrypted session key was"))?;
// Set `encr` to true
self.state.encr = true;
Ok(())
} else {
self.state.encr = false;
Ok(())
}
}
/// Get dynamic ref to unencrypted write+read
fn unencrypted_dyn(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_))
{
(self.tx.inner_mut(), self.rx.inner_mut())
}
/// Get dynamic ref to encrypted write+read
fn encrypted_dyn(&mut self) -> (&mut (dyn AsyncWrite + Unpin + '_), &mut (dyn AsyncRead + Unpin + '_))
{
(&mut self.tx, &mut self.rx)
}
/// Exchange keys.
pub async fn exchange(&mut self) -> eyre::Result<()>
{
use tokio::prelude::*;
let our_key = self.info.us.get_public_parts();
let (tx, rx) = self.inner_mut();
let read_fut = {
async move {
// Read the public key from `rx`.
//TODO: Find pubkey max size.
let mut sz_buf = [0u8; std::mem::size_of::<u64>()];
rx.read_exact(&mut sz_buf[..]).await
.wrap_err(eyre!("Failed to read size of pubkey form endpoint"))?;
let sz64 = u64::from_be_bytes(sz_buf);
let sz= match usize::try_from(sz64)
.wrap_err(eyre!("Read size could not fit into u64"))
.with_section(|| format!("{:?}", sz_buf).header("Read buffer was"))
.with_section(|| u64::from_be_bytes(sz_buf).header("64=bit size value was"))
.with_warning(|| "This should not happen, it is only possible when you are running a machine with a pointer size lower than 64 bits.")
.with_suggestion(|| "The message is likely malformed. If it is not, then you are communicating with an endpoint of 64 bits whereas your pointer size is far less.")? {
x if x > TRANS_KEY_MAX_SIZE => return Err(eyre!("Recv'd key size exceeded max acceptable key buffer size")),
x => x
};
let mut key_bytes = Vec::with_capacity(sz);
tokio::io::copy(&mut rx.take(sz64), &mut key_bytes).await
.wrap_err("Failed to read key bytes into buffer")
.with_section(move || sz64.header("Pubkey size to read was"))?;
if key_bytes.len() != sz {
return Err(eyre!("Could not read required bytes"));
}
let k = RsaPublicKey::from_bytes(&key_bytes)
.wrap_err("Failed to construct RSA public key from read bytes")
.with_section(|| sz.header("Pubkey size was"))
.with_section(move || key_bytes.to_base64_string().header("Pubkey bytes were"))?;
Result::<RsaPublicKey, eyre::Report>::Ok(k)
}
};
let write_fut = {
let key_bytes = our_key.to_bytes();
assert!(key_bytes.len() <= TRANS_KEY_MAX_SIZE);
let sz64 = u64::try_from(key_bytes.len())
.wrap_err(eyre!("Size of our pubkey could not fit into u64"))
.with_section(|| key_bytes.len().header("Size was"))
.with_warning(|| "This should not happen, it is only possible when you are running a machine with a pointer size larger than 64 bits.")
.with_warning(|| "There was likely internal memory corruption.")?;
let sz_buf = sz64.to_be_bytes();
async move {
tx.write_all(&sz_buf[..]).await
.wrap_err(eyre!("Failed to write key size"))
.with_section(|| sz64.header("Key size bytes were"))
.with_section(|| format!("{:?}", sz_buf).header("Key size bytes (BE) were"))?;
tx.write_all(&key_bytes[..]).await
.wrap_err(eyre!("Failed to write key bytes"))
.with_section(|| sz64.header("Size of key was"))
.with_section(|| key_bytes.to_base64_string().header("Key bytes are"))?;
Result::<(), eyre::Report>::Ok(())
}
};
let (send, recv) = tokio::join! [write_fut, read_fut];
send.wrap_err("Failed to send our pubkey")?;
let recv = recv.wrap_err("Failed to receive foreign pubkey")?;
self.info.them = Some(recv);
Ok(())
}
}
/// Write half for `ESock`.
#[pin_project]
#[derive(Debug)]
pub struct ESockWriteHalf<W>(Arc<(ESockInfo, RwLock<ESockState>)>, #[pin] AsyncSink<W>);
/// Read half for `ESock`.
#[pin_project]
#[derive(Debug)]
pub struct ESockReadHalf<R>(Arc<(ESockInfo, RwLock<ESockState>)>, #[pin] AsyncSource<R>);
#[cfg(test)]
mod tests
{
#[test]
fn rsa_ciphertext_len() -> crate::eyre::Result<()>
{
let data = {
use chacha20stream::cha::{KEY_SIZE, IV_SIZE};
let (key, iv) = chacha20stream::cha::keygen();
let (sz, d) = crate::bin::collect_slices_exact::<&[u8], _, {KEY_SIZE + IV_SIZE}>([key.as_ref(), iv.as_ref()]);
assert_eq!(sz, d.len());
d
};
println!("KEY+IV: {} bytes", data.len());
let key = cryptohelpers::rsa::RsaPublicKey::generate()?;
let rsa = cryptohelpers::rsa::encrypt_slice_to_vec(data, &key)?;
println!("Rsa ciphertext size: {}", rsa.len());
assert_eq!(rsa.len(), super::RSA_CIPHERTEXT_SIZE, "Incorrect RSA ciphertext length constant for cc20 KEY+IV encoding.");
Ok(())
}
#[test]
fn rsa_serial_ciphertext_len() -> crate::eyre::Result<()>
{
let data = serde_cbor::to_vec(&{
let (key, iv) = chacha20stream::cha::keygen();
super::ESockSessionKey {
key, iv,
}
}).expect("Failed to CBOR encode Key+IV");
println!("(cbor) KEY+IV: {} bytes", data.len());
let key = cryptohelpers::rsa::RsaPublicKey::generate()?;
let rsa = cryptohelpers::rsa::encrypt_slice_to_vec(data, &key)?;
println!("Rsa ciphertext size: {}", rsa.len());
assert_eq!(rsa.len(), super::RSA_CIPHERTEXT_SIZE, "Incorrect RSA ciphertext length constant for cc20 KEY+IV CBOR encoding.");
Ok(())
}
}