1use aes_gcm::aead::Aead;
7use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
8use hmac::{Hmac, Mac};
9use rand::Rng;
10use sha2::Sha256;
11
12const KEY_LABEL: &[u8] = b"codoseo channel key v1";
15const NONCE_LEN: usize = 12;
16
17#[derive(Debug, thiserror::Error)]
18pub enum CryptoError {
19 #[error("the stored value is too short to be valid")]
20 TooShort,
21 #[error("the stored value could not be decrypted (wrong key or damaged data)")]
22 Decrypt,
23}
24
25#[derive(Clone)]
27pub struct ChannelKey {
28 cipher: Aes256Gcm,
29}
30
31impl std::fmt::Debug for ChannelKey {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.write_str("ChannelKey(..)")
34 }
35}
36
37impl ChannelKey {
38 pub fn derive(secret_key: &str) -> ChannelKey {
40 let mut mac = Hmac::<Sha256>::new_from_slice(secret_key.as_bytes())
41 .expect("HMAC accepts keys of any length");
42 mac.update(KEY_LABEL);
43 let key = mac.finalize().into_bytes();
44 ChannelKey {
45 cipher: Aes256Gcm::new_from_slice(&key).expect("HMAC-SHA256 output is 32 bytes"),
46 }
47 }
48
49 pub fn encrypt(&self, plaintext: &[u8]) -> Vec<u8> {
51 let mut nonce_bytes = [0u8; NONCE_LEN];
52 rand::rng().fill_bytes(&mut nonce_bytes);
53 let nonce = Nonce::try_from(&nonce_bytes[..]).expect("nonce is 12 bytes");
54 let sealed = self
55 .cipher
56 .encrypt(&nonce, plaintext)
57 .expect("AES-GCM encryption of an in-memory buffer cannot fail");
58 let mut out = Vec::with_capacity(NONCE_LEN + sealed.len());
59 out.extend_from_slice(&nonce_bytes);
60 out.extend_from_slice(&sealed);
61 out
62 }
63
64 pub fn decrypt(&self, bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
66 if bytes.len() <= NONCE_LEN {
67 return Err(CryptoError::TooShort);
68 }
69 let (nonce_bytes, sealed) = bytes.split_at(NONCE_LEN);
70 let nonce = Nonce::try_from(nonce_bytes).map_err(|_| CryptoError::TooShort)?;
71 self.cipher
72 .decrypt(&nonce, sealed)
73 .map_err(|_| CryptoError::Decrypt)
74 }
75}