use crate::error::{Error, Result};
use aes_gcm::Aes256Gcm;
use chacha20poly1305::ChaCha20Poly1305;
use chacha20poly1305::aead::{Aead, KeyInit, Payload};
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use zeroize::{Zeroize, ZeroizeOnDrop};
const INFO_SLOT: &[u8] = b"omy/v1/slot";
const INFO_PAYLOAD: &[u8] = b"omy/v1/payload";
const INFO_HEADER_MAC: &[u8] = b"omy/v1/header-mac";
const INFO_TLV: &[u8] = b"omy/v1/tlv";
const INFO_FINGERPRINT: &[u8] = b"omy/v1/kek-fingerprint";
const SLOT_INFO_LEN: usize = INFO_SLOT.len() + 2;
const TLV_INFO_LEN: usize = INFO_TLV.len() + 2;
const _: () = assert!(SLOT_INFO_LEN == 13, "omy/v1/slot 长度应为 11");
const _: () = assert!(TLV_INFO_LEN == 12, "omy/v1/tlv 长度应为 10");
pub const KEY_LEN: usize = 32;
pub const TAG_LEN: usize = 16;
pub const NONCE_LEN: usize = 12;
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SecretKey([u8; KEY_LEN]);
impl SecretKey {
#[must_use]
pub const fn from_bytes(bytes: [u8; KEY_LEN]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; KEY_LEN] {
&self.0
}
#[must_use]
pub fn duplicate(&self) -> Self {
Self(self.0)
}
#[must_use]
pub fn random() -> Self {
let mut k = [0u8; KEY_LEN];
crate::util::fill_random(&mut k);
Self(k)
}
}
impl core::fmt::Debug for SecretKey {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("SecretKey(<redacted>)")
}
}
#[derive(Debug)]
pub struct Kek(SecretKey);
impl Kek {
#[must_use]
pub const fn from_key(key: SecretKey) -> Self {
Self(key)
}
#[must_use]
pub const fn as_key(&self) -> &SecretKey {
&self.0
}
#[must_use]
pub fn duplicate(&self) -> Self {
Self(self.0.duplicate())
}
pub fn from_password(password: &[u8], vault_salt: &[u8], params: Argon2Params) -> Result<Self> {
use argon2::{Algorithm, Argon2, Version};
let p = argon2::Params::new(params.m_kib, params.t, params.p, Some(KEY_LEN))
.map_err(|_| Error::KeyDerivation { reason: "invalid Argon2 parameters" })?;
let a2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, p);
let mut out = [0u8; KEY_LEN];
a2.hash_password_into(password, vault_salt, &mut out)
.map_err(|_| Error::KeyDerivation { reason: "Argon2 hashing failed" })?;
Ok(Self(SecretKey::from_bytes(out)))
}
#[must_use]
pub fn derive_dirname_key(&self, vault_salt: &[u8; 16], info: &[u8]) -> SecretKey {
hkdf_expand(self.0.as_bytes(), vault_salt, info)
}
#[must_use]
pub fn derive_slot_key(&self, file_uuid: &[u8; 16], slot_index: u16) -> SecretKey {
let mut info = [0u8; SLOT_INFO_LEN];
let (prefix, suffix) = info.split_at_mut(INFO_SLOT.len());
prefix.copy_from_slice(INFO_SLOT);
suffix.copy_from_slice(&slot_index.to_le_bytes());
hkdf_expand(self.0.as_bytes(), file_uuid, &info)
}
#[must_use]
pub fn fingerprint(&self, vault_salt: &[u8; 16]) -> [u8; KEY_LEN] {
let k = hkdf_expand(self.0.as_bytes(), vault_salt, INFO_FINGERPRINT);
*k.as_bytes()
}
}
#[derive(Debug)]
pub struct Fek(SecretKey);
impl Fek {
#[must_use]
pub fn random() -> Self {
Self(SecretKey::random())
}
#[must_use]
pub const fn from_key(key: SecretKey) -> Self {
Self(key)
}
#[must_use]
pub const fn as_key(&self) -> &SecretKey {
&self.0
}
#[must_use]
pub fn derive_payload_key(&self, file_uuid: &[u8; 16]) -> SecretKey {
hkdf_expand(self.0.as_bytes(), file_uuid, INFO_PAYLOAD)
}
#[must_use]
pub fn derive_header_mac_key(&self, file_uuid: &[u8; 16]) -> SecretKey {
hkdf_expand(self.0.as_bytes(), file_uuid, INFO_HEADER_MAC)
}
#[must_use]
pub fn derive_tlv_key(&self, tlv_type: u16) -> SecretKey {
let mut info = [0u8; TLV_INFO_LEN];
let (prefix, suffix) = info.split_at_mut(INFO_TLV.len());
prefix.copy_from_slice(INFO_TLV);
suffix.copy_from_slice(&tlv_type.to_le_bytes());
hkdf_expand(self.0.as_bytes(), &[], &info)
}
}
#[must_use]
pub fn derive_from_secret(key: &SecretKey, salt: &[u8; 16], info: &[u8]) -> SecretKey {
hkdf_expand(key.as_bytes(), salt, info)
}
fn hkdf_expand(ikm: &[u8], salt: &[u8], info: &[u8]) -> SecretKey {
let hk = Hkdf::<Sha256>::new(Some(salt), ikm);
let mut out = [0u8; KEY_LEN];
hk.expand(info, &mut out)
.unwrap_or_else(|_| unreachable!("HKDF output length 32 is always valid"));
SecretKey::from_bytes(out)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Argon2Params {
pub m_kib: u32,
pub t: u32,
pub p: u32,
}
impl Argon2Params {
pub const MOBILE: Self = Self { m_kib: 32 * 1024, t: 4, p: 1 };
pub const INTERACTIVE: Self = Self { m_kib: 64 * 1024, t: 3, p: 1 };
pub const MODERATE: Self = Self { m_kib: 256 * 1024, t: 4, p: 1 };
pub const SENSITIVE: Self = Self { m_kib: 1024 * 1024, t: 4, p: 1 };
pub const TEST_WEAK: Self = Self { m_kib: 64, t: 1, p: 1 };
}
impl Default for Argon2Params {
fn default() -> Self {
Self::INTERACTIVE
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CipherId {
ChaCha20Poly1305 = 1,
Aes256Gcm = 2,
}
impl CipherId {
pub const fn from_u8(v: u8) -> Result<Self> {
match v {
1 => Ok(Self::ChaCha20Poly1305),
2 => Ok(Self::Aes256Gcm),
_ => Err(Error::MalformedHeader { reason: "unknown cipher_id" }),
}
}
pub fn encrypt(
self,
key: &SecretKey,
nonce: &[u8; NONCE_LEN],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>> {
let payload = Payload { msg: plaintext, aad };
match self {
Self::ChaCha20Poly1305 => {
let c = ChaCha20Poly1305::new(key.as_bytes().into());
c.encrypt(nonce.into(), payload)
}
Self::Aes256Gcm => {
let c = Aes256Gcm::new(key.as_bytes().into());
c.encrypt(nonce.into(), payload)
}
}
.map_err(|_| Error::KeyDerivation { reason: "AEAD encryption failed" })
}
pub fn decrypt(
self,
key: &SecretKey,
nonce: &[u8; NONCE_LEN],
ciphertext: &[u8],
aad: &[u8],
) -> Result<Option<Vec<u8>>> {
let payload = Payload { msg: ciphertext, aad };
let r = match self {
Self::ChaCha20Poly1305 => {
let c = ChaCha20Poly1305::new(key.as_bytes().into());
c.decrypt(nonce.into(), payload)
}
Self::Aes256Gcm => {
let c = Aes256Gcm::new(key.as_bytes().into());
c.decrypt(nonce.into(), payload)
}
};
Ok(r.ok())
}
}
#[must_use]
pub fn chunk_nonce(base_nonce: &[u8; 7], index: u32, is_final: bool) -> [u8; NONCE_LEN] {
let mut n = [0u8; NONCE_LEN];
n[..7].copy_from_slice(base_nonce);
n[7..11].copy_from_slice(&index.to_be_bytes());
n[11] = u8::from(is_final);
n
}
#[must_use]
pub fn chunk_aad(file_uuid: &[u8; 16], index: u32) -> [u8; 20] {
let mut a = [0u8; 20];
a[..16].copy_from_slice(file_uuid);
a[16..].copy_from_slice(&index.to_be_bytes());
a
}
pub const ZERO_NONCE: [u8; NONCE_LEN] = [0u8; NONCE_LEN];
#[must_use]
pub fn header_mac(mac_key: &SecretKey, covered: &[u8]) -> [u8; 32] {
let mut m = <Hmac<Sha256> as Mac>::new_from_slice(mac_key.as_bytes())
.unwrap_or_else(|_| unreachable!("HMAC accepts any key length"));
m.update(covered);
m.finalize().into_bytes().into()
}
#[must_use]
pub fn verify_header_mac(mac_key: &SecretKey, covered: &[u8], expected: &[u8; 32]) -> bool {
use subtle::ConstantTimeEq;
header_mac(mac_key, covered).ct_eq(expected).into()
}
#[must_use]
pub fn content_hash(data: &[u8]) -> [u8; 32] {
use blake2::digest::consts::U32;
use blake2::{Blake2b, Digest};
let mut h = Blake2b::<U32>::new();
h.update(data);
h.finalize().into()
}