use base64::prelude::*;
use bincode::{Decode, Encode};
use chacha20::cipher::{KeyIvInit, StreamCipher};
use chacha20::{ChaCha20, Key, Nonce};
use hmac::{Hmac, Mac};
use infer;
use sha2::Sha256;
use zeroize::ZeroizeOnDrop;
use super::error::SessionError;
type HmacSha256 = Hmac<Sha256>;
#[derive(Encode, Decode, ZeroizeOnDrop)]
pub struct Message {
pub sequence: u64,
pub timestamp: u32,
pub content_type: u8,
pub payload: Vec<u8>,
pub hmac: [u8; 32],
}
pub enum ContentType {
Text = 0,
Image = 1,
}
impl Message {
pub fn encrypt(
sequence: u64,
timestamp: u32,
content_type: ContentType,
plaintext: &[u8],
encryption_key: &[u8; 32],
signing_key: &[u8; 32],
) -> Self {
let content_type_u8 = content_type as u8;
let processed_payload = if content_type_u8 == ContentType::Image as u8 {
let encoded = BASE64_STANDARD.encode(plaintext);
let data_url = match infer::get(plaintext) {
Some(kind) => {
let mime_type = kind.mime_type();
format!("data:{mime_type};base64,{encoded}")
}
None => {
format!("data:image/jpeg;base64,{encoded}")
}
};
data_url.into_bytes()
} else {
plaintext.to_vec()
};
let nonce_bytes = Self::build_nonce(sequence, timestamp);
let nonce = Nonce::from_slice(&nonce_bytes);
let key = Key::from_slice(encryption_key);
let mut cipher = ChaCha20::new(key, nonce);
let mut payload = processed_payload;
cipher.apply_keystream(&mut payload);
let mut message = Message {
sequence,
timestamp,
content_type: content_type_u8,
payload,
hmac: [0u8; 32], };
let hmac = Self::compute_hmac(&message, signing_key);
message.hmac = hmac;
message
}
pub fn decrypt(
&self,
encryption_key: &[u8; 32],
signing_key: &[u8; 32],
) -> Result<Vec<u8>, SessionError> {
if !self.verify_hmac(signing_key) {
return Err(SessionError::HmacVerificationFailed);
}
let nonce_bytes = Self::build_nonce(self.sequence, self.timestamp);
let nonce = Nonce::from_slice(&nonce_bytes);
let key = Key::from_slice(encryption_key);
let mut cipher = ChaCha20::new(key, nonce);
let mut plaintext = self.payload.clone();
cipher.apply_keystream(&mut plaintext);
Ok(plaintext)
}
pub fn verify_hmac(&self, signing_key: &[u8; 32]) -> bool {
let expected_hmac = Self::compute_hmac(self, signing_key);
expected_hmac == self.hmac
}
fn compute_hmac(message: &Message, signing_key: &[u8; 32]) -> [u8; 32] {
let mut mac =
HmacSha256::new_from_slice(signing_key).expect("HMAC can take key of any size");
mac.update(&message.sequence.to_le_bytes());
mac.update(&message.timestamp.to_le_bytes());
mac.update(&[message.content_type]);
mac.update(&message.payload);
mac.finalize().into_bytes().into()
}
fn build_nonce(sequence: u64, timestamp: u32) -> [u8; 12] {
let mut nonce = [0u8; 12];
nonce[0..8].copy_from_slice(&sequence.to_le_bytes());
nonce[8..12].copy_from_slice(×tamp.to_le_bytes());
nonce
}
}