1use std::sync::{OnceLock, RwLock};
14
15use aes_gcm::aead::{Aead, AeadCore, KeyInit, OsRng};
16use aes_gcm::{Aes256Gcm, Key, Nonce};
17use hkdf::Hkdf;
18use sha2::Sha256;
19use zeroize::Zeroizing;
20
21use crate::blocks::EncryptionAlgorithm;
22use crate::error::{MeshError, Result};
23
24pub const DEFAULT_HKDF_INFO: &str = "combsmesh-emoji-encryption";
26
27pub const NONCE_LEN: usize = 12;
29
30#[derive(Clone)]
33pub struct KeyRing {
34 master: Zeroizing<Vec<u8>>,
35}
36
37impl KeyRing {
38 #[must_use]
41 pub fn new(master: Option<&[u8]>) -> Self {
42 let master = match master {
43 Some(bytes) => bytes.to_vec(),
44 None => Aes256Gcm::generate_key(&mut OsRng).to_vec(),
45 };
46 KeyRing {
47 master: Zeroizing::new(master),
48 }
49 }
50
51 pub fn subkey(&self, info: &str) -> Result<Zeroizing<[u8; 32]>> {
53 let hk = Hkdf::<Sha256>::new(None, &self.master);
54 let mut okm = [0u8; 32];
55 hk.expand(info.as_bytes(), &mut okm)
56 .map_err(|_| MeshError::Crypto("HKDF expand failed".into()))?;
57 Ok(Zeroizing::new(okm))
58 }
59
60 pub fn encrypt(&self, data: &[u8], algorithm: EncryptionAlgorithm) -> Result<Vec<u8>> {
63 let subkey = self.subkey(DEFAULT_HKDF_INFO)?;
64 match algorithm {
65 EncryptionAlgorithm::Aes256Gcm => {
66 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&subkey[..]));
67 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
68 let ct = cipher
69 .encrypt(&nonce, data)
70 .map_err(|_| MeshError::Crypto("AES-256-GCM encrypt failed".into()))?;
71 Ok([&nonce[..], &ct[..]].concat())
72 }
73 EncryptionAlgorithm::ChaCha20Poly1305 => {
74 use chacha20poly1305::{ChaCha20Poly1305, Key as CKey};
75 let cipher = ChaCha20Poly1305::new(CKey::from_slice(&subkey[..]));
76 let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
77 let ct = cipher
78 .encrypt(&nonce, data)
79 .map_err(|_| MeshError::Crypto("ChaCha20-Poly1305 encrypt failed".into()))?;
80 Ok([&nonce[..], &ct[..]].concat())
81 }
82 }
83 }
84
85 pub fn decrypt(&self, data: &[u8], algorithm: EncryptionAlgorithm) -> Result<Vec<u8>> {
89 if data.len() < NONCE_LEN {
90 return Err(MeshError::Crypto(
91 "ciphertext shorter than the nonce".into(),
92 ));
93 }
94 let (nonce, ct) = data.split_at(NONCE_LEN);
95 let subkey = self.subkey(DEFAULT_HKDF_INFO)?;
96 match algorithm {
97 EncryptionAlgorithm::Aes256Gcm => {
98 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&subkey[..]));
99 cipher
100 .decrypt(Nonce::from_slice(nonce), ct)
101 .map_err(|_| MeshError::Crypto("AES-256-GCM decrypt failed".into()))
102 }
103 EncryptionAlgorithm::ChaCha20Poly1305 => {
104 use chacha20poly1305::{ChaCha20Poly1305, Key as CKey, Nonce as CNonce};
105 let cipher = ChaCha20Poly1305::new(CKey::from_slice(&subkey[..]));
106 cipher
107 .decrypt(CNonce::from_slice(nonce), ct)
108 .map_err(|_| MeshError::Crypto("ChaCha20-Poly1305 decrypt failed".into()))
109 }
110 }
111 }
112}
113
114impl std::fmt::Debug for KeyRing {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.write_str("KeyRing(<redacted>)")
117 }
118}
119
120fn slot() -> &'static RwLock<Option<KeyRing>> {
121 static KEYRING: OnceLock<RwLock<Option<KeyRing>>> = OnceLock::new();
122 KEYRING.get_or_init(|| RwLock::new(None))
123}
124
125pub fn init(master: Option<&[u8]>) -> Result<()> {
128 let mut guard = slot()
129 .write()
130 .map_err(|_| MeshError::Crypto("keyring lock poisoned".into()))?;
131 *guard = Some(KeyRing::new(master));
132 Ok(())
133}
134
135pub fn shutdown() {
138 if let Ok(mut guard) = slot().write() {
139 *guard = None;
140 }
141}
142
143pub fn global() -> Result<KeyRing> {
146 let guard = slot()
147 .read()
148 .map_err(|_| MeshError::Crypto("keyring lock poisoned".into()))?;
149 guard.clone().ok_or(MeshError::NotInitialized)
150}