use std::sync::{OnceLock, RwLock};
use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use hkdf::Hkdf;
use sha2::Sha256;
use zeroize::Zeroizing;
use crate::blocks::EncryptionAlgorithm;
use crate::error::{MeshError, Result};
pub const DEFAULT_HKDF_INFO: &str = "combsmesh-emoji-encryption";
pub const NONCE_LEN: usize = 12;
#[derive(Clone)]
pub struct KeyRing {
master: Zeroizing<Vec<u8>>,
}
impl KeyRing {
#[must_use]
pub fn new(master: Option<&[u8]>) -> Self {
let master = match master {
Some(bytes) => bytes.to_vec(),
None => Aes256Gcm::generate_key(&mut OsRng).to_vec(),
};
KeyRing {
master: Zeroizing::new(master),
}
}
pub fn subkey(&self, info: &str) -> Result<Zeroizing<[u8; 32]>> {
let hk = Hkdf::<Sha256>::new(None, &self.master);
let mut okm = [0u8; 32];
hk.expand(info.as_bytes(), &mut okm)
.map_err(|_| MeshError::Crypto("HKDF expand failed".into()))?;
Ok(Zeroizing::new(okm))
}
pub fn encrypt(&self, data: &[u8], algorithm: EncryptionAlgorithm) -> Result<Vec<u8>> {
let subkey = self.subkey(DEFAULT_HKDF_INFO)?;
match algorithm {
EncryptionAlgorithm::Aes256Gcm => {
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&subkey[..]));
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let ct = cipher
.encrypt(&nonce, data)
.map_err(|_| MeshError::Crypto("AES-256-GCM encrypt failed".into()))?;
Ok([&nonce[..], &ct[..]].concat())
}
EncryptionAlgorithm::ChaCha20Poly1305 => {
use chacha20poly1305::{ChaCha20Poly1305, Key as CKey};
let cipher = ChaCha20Poly1305::new(CKey::from_slice(&subkey[..]));
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let ct = cipher
.encrypt(&nonce, data)
.map_err(|_| MeshError::Crypto("ChaCha20-Poly1305 encrypt failed".into()))?;
Ok([&nonce[..], &ct[..]].concat())
}
}
}
pub fn decrypt(&self, data: &[u8], algorithm: EncryptionAlgorithm) -> Result<Vec<u8>> {
if data.len() < NONCE_LEN {
return Err(MeshError::Crypto(
"ciphertext shorter than the nonce".into(),
));
}
let (nonce, ct) = data.split_at(NONCE_LEN);
let subkey = self.subkey(DEFAULT_HKDF_INFO)?;
match algorithm {
EncryptionAlgorithm::Aes256Gcm => {
let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&subkey[..]));
cipher
.decrypt(Nonce::from_slice(nonce), ct)
.map_err(|_| MeshError::Crypto("AES-256-GCM decrypt failed".into()))
}
EncryptionAlgorithm::ChaCha20Poly1305 => {
use chacha20poly1305::{ChaCha20Poly1305, Key as CKey, Nonce as CNonce};
let cipher = ChaCha20Poly1305::new(CKey::from_slice(&subkey[..]));
cipher
.decrypt(CNonce::from_slice(nonce), ct)
.map_err(|_| MeshError::Crypto("ChaCha20-Poly1305 decrypt failed".into()))
}
}
}
}
impl std::fmt::Debug for KeyRing {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("KeyRing(<redacted>)")
}
}
fn slot() -> &'static RwLock<Option<KeyRing>> {
static KEYRING: OnceLock<RwLock<Option<KeyRing>>> = OnceLock::new();
KEYRING.get_or_init(|| RwLock::new(None))
}
pub fn init(master: Option<&[u8]>) -> Result<()> {
let mut guard = slot()
.write()
.map_err(|_| MeshError::Crypto("keyring lock poisoned".into()))?;
*guard = Some(KeyRing::new(master));
Ok(())
}
pub fn shutdown() {
if let Ok(mut guard) = slot().write() {
*guard = None;
}
}
pub fn global() -> Result<KeyRing> {
let guard = slot()
.read()
.map_err(|_| MeshError::Crypto("keyring lock poisoned".into()))?;
guard.clone().ok_or(MeshError::NotInitialized)
}