1use crate::error::{Error, Result};
26use aes_gcm::Aes256Gcm;
27use chacha20poly1305::ChaCha20Poly1305;
28use chacha20poly1305::aead::{Aead, KeyInit, Payload};
29use hkdf::Hkdf;
30use hmac::{Hmac, Mac};
31use sha2::Sha256;
32use zeroize::{Zeroize, ZeroizeOnDrop};
33
34const INFO_SLOT: &[u8] = b"omy/v1/slot";
36const INFO_PAYLOAD: &[u8] = b"omy/v1/payload";
38const INFO_HEADER_MAC: &[u8] = b"omy/v1/header-mac";
40const INFO_TLV: &[u8] = b"omy/v1/tlv";
42const INFO_FINGERPRINT: &[u8] = b"omy/v1/kek-fingerprint";
48
49const SLOT_INFO_LEN: usize = INFO_SLOT.len() + 2;
51const TLV_INFO_LEN: usize = INFO_TLV.len() + 2;
53
54const _: () = assert!(SLOT_INFO_LEN == 13, "omy/v1/slot 长度应为 11");
57const _: () = assert!(TLV_INFO_LEN == 12, "omy/v1/tlv 长度应为 10");
58
59pub const KEY_LEN: usize = 32;
61pub const TAG_LEN: usize = 16;
63pub const NONCE_LEN: usize = 12;
65
66#[derive(Zeroize, ZeroizeOnDrop)]
71pub struct SecretKey([u8; KEY_LEN]);
72
73impl SecretKey {
74 #[must_use]
76 pub const fn from_bytes(bytes: [u8; KEY_LEN]) -> Self {
77 Self(bytes)
78 }
79
80 #[must_use]
82 pub const fn as_bytes(&self) -> &[u8; KEY_LEN] {
83 &self.0
84 }
85
86 #[must_use]
88 pub fn duplicate(&self) -> Self {
89 Self(self.0)
90 }
91
92 #[must_use]
94 pub fn random() -> Self {
95 let mut k = [0u8; KEY_LEN];
96 crate::util::fill_random(&mut k);
97 Self(k)
98 }
99}
100
101impl core::fmt::Debug for SecretKey {
102 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
104 f.write_str("SecretKey(<redacted>)")
105 }
106}
107
108#[derive(Debug)]
113pub struct Kek(SecretKey);
114
115impl Kek {
116 #[must_use]
118 pub const fn from_key(key: SecretKey) -> Self {
119 Self(key)
120 }
121
122 #[must_use]
124 pub const fn as_key(&self) -> &SecretKey {
125 &self.0
126 }
127
128 #[must_use]
133 pub fn duplicate(&self) -> Self {
134 Self(self.0.duplicate())
135 }
136
137 pub fn from_password(password: &[u8], vault_salt: &[u8], params: Argon2Params) -> Result<Self> {
148 use argon2::{Algorithm, Argon2, Version};
149
150 let p = argon2::Params::new(params.m_kib, params.t, params.p, Some(KEY_LEN))
151 .map_err(|_| Error::KeyDerivation { reason: "invalid Argon2 parameters" })?;
152 let a2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
153
154 let mut out = [0u8; KEY_LEN];
155 a2.hash_password_into(password, vault_salt, &mut out)
156 .map_err(|_| Error::KeyDerivation { reason: "Argon2 hashing failed" })?;
157 Ok(Self(SecretKey::from_bytes(out)))
158 }
159
160 #[must_use]
172 pub fn derive_dirname_key(&self, vault_salt: &[u8; 16], info: &[u8]) -> SecretKey {
173 hkdf_expand(self.0.as_bytes(), vault_salt, info)
174 }
175
176 #[must_use]
180 pub fn derive_slot_key(&self, file_uuid: &[u8; 16], slot_index: u16) -> SecretKey {
181 let mut info = [0u8; SLOT_INFO_LEN];
182 let (prefix, suffix) = info.split_at_mut(INFO_SLOT.len());
183 prefix.copy_from_slice(INFO_SLOT);
184 suffix.copy_from_slice(&slot_index.to_le_bytes());
185 hkdf_expand(self.0.as_bytes(), file_uuid, &info)
186 }
187
188 #[must_use]
212 pub fn fingerprint(&self, vault_salt: &[u8; 16]) -> [u8; KEY_LEN] {
213 let k = hkdf_expand(self.0.as_bytes(), vault_salt, INFO_FINGERPRINT);
214 *k.as_bytes()
215 }
216}
217
218#[derive(Debug)]
222pub struct Fek(SecretKey);
223
224impl Fek {
225 #[must_use]
227 pub fn random() -> Self {
228 Self(SecretKey::random())
229 }
230
231 #[must_use]
233 pub const fn from_key(key: SecretKey) -> Self {
234 Self(key)
235 }
236
237 #[must_use]
239 pub const fn as_key(&self) -> &SecretKey {
240 &self.0
241 }
242
243 #[must_use]
245 pub fn derive_payload_key(&self, file_uuid: &[u8; 16]) -> SecretKey {
246 hkdf_expand(self.0.as_bytes(), file_uuid, INFO_PAYLOAD)
247 }
248
249 #[must_use]
251 pub fn derive_header_mac_key(&self, file_uuid: &[u8; 16]) -> SecretKey {
252 hkdf_expand(self.0.as_bytes(), file_uuid, INFO_HEADER_MAC)
253 }
254
255 #[must_use]
259 pub fn derive_tlv_key(&self, tlv_type: u16) -> SecretKey {
260 let mut info = [0u8; TLV_INFO_LEN];
261 let (prefix, suffix) = info.split_at_mut(INFO_TLV.len());
262 prefix.copy_from_slice(INFO_TLV);
263 suffix.copy_from_slice(&tlv_type.to_le_bytes());
264 hkdf_expand(self.0.as_bytes(), &[], &info)
265 }
266}
267
268#[must_use]
275pub fn derive_from_secret(key: &SecretKey, salt: &[u8; 16], info: &[u8]) -> SecretKey {
276 hkdf_expand(key.as_bytes(), salt, info)
277}
278
279fn hkdf_expand(ikm: &[u8], salt: &[u8], info: &[u8]) -> SecretKey {
281 let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
282 let mut out = [0u8; KEY_LEN];
283 hk.expand(info, &mut out)
285 .unwrap_or_else(|_| unreachable!("HKDF output length 32 is always valid"));
286 SecretKey::from_bytes(out)
287}
288
289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub struct Argon2Params {
292 pub m_kib: u32,
294 pub t: u32,
296 pub p: u32,
298}
299
300impl Argon2Params {
301 pub const MOBILE: Self = Self { m_kib: 32 * 1024, t: 4, p: 1 };
303 pub const INTERACTIVE: Self = Self { m_kib: 64 * 1024, t: 3, p: 1 };
305 pub const MODERATE: Self = Self { m_kib: 256 * 1024, t: 4, p: 1 };
307 pub const SENSITIVE: Self = Self { m_kib: 1024 * 1024, t: 4, p: 1 };
309
310 pub const TEST_WEAK: Self = Self { m_kib: 64, t: 1, p: 1 };
316}
317
318impl Default for Argon2Params {
319 fn default() -> Self {
320 Self::INTERACTIVE
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326#[repr(u8)]
327pub enum CipherId {
328 ChaCha20Poly1305 = 1,
333 Aes256Gcm = 2,
335}
336
337impl CipherId {
338 pub const fn from_u8(v: u8) -> Result<Self> {
344 match v {
345 1 => Ok(Self::ChaCha20Poly1305),
346 2 => Ok(Self::Aes256Gcm),
347 _ => Err(Error::MalformedHeader { reason: "unknown cipher_id" }),
348 }
349 }
350
351 pub fn encrypt(
357 self,
358 key: &SecretKey,
359 nonce: &[u8; NONCE_LEN],
360 plaintext: &[u8],
361 aad: &[u8],
362 ) -> Result<Vec<u8>> {
363 let payload = Payload { msg: plaintext, aad };
364 match self {
365 Self::ChaCha20Poly1305 => {
366 let c = ChaCha20Poly1305::new(key.as_bytes().into());
367 c.encrypt(nonce.into(), payload)
368 }
369 Self::Aes256Gcm => {
370 let c = Aes256Gcm::new(key.as_bytes().into());
371 c.encrypt(nonce.into(), payload)
372 }
373 }
374 .map_err(|_| Error::KeyDerivation { reason: "AEAD encryption failed" })
375 }
376
377 pub fn decrypt(
386 self,
387 key: &SecretKey,
388 nonce: &[u8; NONCE_LEN],
389 ciphertext: &[u8],
390 aad: &[u8],
391 ) -> Result<Option<Vec<u8>>> {
392 let payload = Payload { msg: ciphertext, aad };
393 let r = match self {
394 Self::ChaCha20Poly1305 => {
395 let c = ChaCha20Poly1305::new(key.as_bytes().into());
396 c.decrypt(nonce.into(), payload)
397 }
398 Self::Aes256Gcm => {
399 let c = Aes256Gcm::new(key.as_bytes().into());
400 c.decrypt(nonce.into(), payload)
401 }
402 };
403 Ok(r.ok())
404 }
405}
406
407#[must_use]
416pub fn chunk_nonce(base_nonce: &[u8; 7], index: u32, is_final: bool) -> [u8; NONCE_LEN] {
417 let mut n = [0u8; NONCE_LEN];
418 n[..7].copy_from_slice(base_nonce);
419 n[7..11].copy_from_slice(&index.to_be_bytes());
420 n[11] = u8::from(is_final);
421 n
422}
423
424#[must_use]
432pub fn chunk_aad(file_uuid: &[u8; 16], index: u32) -> [u8; 20] {
433 let mut a = [0u8; 20];
434 a[..16].copy_from_slice(file_uuid);
435 a[16..].copy_from_slice(&index.to_be_bytes());
436 a
437}
438
439pub const ZERO_NONCE: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
445
446#[must_use]
450pub fn header_mac(mac_key: &SecretKey, covered: &[u8]) -> [u8; 32] {
451 let mut m = <Hmac<Sha256> as Mac>::new_from_slice(mac_key.as_bytes())
452 .unwrap_or_else(|_| unreachable!("HMAC accepts any key length"));
453 m.update(covered);
454 m.finalize().into_bytes().into()
455}
456
457#[must_use]
461pub fn verify_header_mac(mac_key: &SecretKey, covered: &[u8], expected: &[u8; 32]) -> bool {
462 use subtle::ConstantTimeEq;
463 header_mac(mac_key, covered).ct_eq(expected).into()
464}
465
466#[must_use]
468pub fn content_hash(data: &[u8]) -> [u8; 32] {
469 use blake2::digest::consts::U32;
470 use blake2::{Blake2b, Digest};
471 let mut h = Blake2b::<U32>::new();
472 h.update(data);
473 h.finalize().into()
474}