use crate::crypto::{NONCE_LEN, SALT_LEN, TAG_LEN};
use crate::error::{MnemoError, Result};
pub const MAGIC: &[u8; 4] = b"MNEM";
pub const VERSION: u16 = 7;
pub const MIGRATABLE_FROM: u16 = 4;
pub const PAGE_SIZE: usize = 8192;
pub const PAYLOAD: usize = PAGE_SIZE - NONCE_LEN - TAG_LEN; pub const WRAPPED_DEK_LEN: usize = 48;
pub const FLAG_ENCRYPTED: u32 = 1;
pub const HEADER_CRC_OFF: usize = 238;
pub const HEADER_SEAL_NONCE_OFF: usize = 242;
pub const HEADER_SEAL_TAG_OFF: usize = HEADER_SEAL_NONCE_OFF + NONCE_LEN;
pub const SEAL_AAD_PREFIX: &[u8] = b"mnemo-header-seal-v7";
const _: () = assert!(HEADER_SEAL_TAG_OFF + TAG_LEN <= PAGE_SIZE);
#[derive(Clone, Debug)]
pub struct Header {
pub version: u16,
pub page_size: u32,
pub flags: u32,
pub dimensions: u32,
pub created_at: i64,
pub write_counter: u64,
pub next_page: u64,
pub catalog_start: u64,
pub catalog_pages: u64,
pub catalog_len: u64,
pub vector_count: u64,
pub m_cost: u32,
pub t_cost: u32,
pub p_cost: u32,
pub salt: [u8; SALT_LEN],
pub dek_nonce: [u8; NONCE_LEN],
pub wrapped_dek: [u8; WRAPPED_DEK_LEN],
pub index_start: u64,
pub index_pages: u64,
pub index_len: u64,
pub wal_start: u64,
pub wal_pages: u64,
pub wal_seq: u64,
pub manifest_start: u64,
pub manifest_pages: u64,
pub manifest_len: u64,
pub seal_nonce: Option<[u8; NONCE_LEN]>,
pub seal_tag: Option<[u8; TAG_LEN]>,
}
fn header_seal_aad(h: &Header) -> Vec<u8> {
let mut aad = Vec::with_capacity(SEAL_AAD_PREFIX.len() + 2 + 15 * 8);
aad.extend_from_slice(SEAL_AAD_PREFIX);
aad.extend_from_slice(&h.version.to_le_bytes());
aad.extend_from_slice(&h.write_counter.to_le_bytes());
aad.extend_from_slice(&h.next_page.to_le_bytes());
aad.extend_from_slice(&h.catalog_start.to_le_bytes());
aad.extend_from_slice(&h.catalog_pages.to_le_bytes());
aad.extend_from_slice(&h.catalog_len.to_le_bytes());
aad.extend_from_slice(&h.vector_count.to_le_bytes());
aad.extend_from_slice(&h.index_start.to_le_bytes());
aad.extend_from_slice(&h.index_pages.to_le_bytes());
aad.extend_from_slice(&h.index_len.to_le_bytes());
aad.extend_from_slice(&h.wal_start.to_le_bytes());
aad.extend_from_slice(&h.wal_pages.to_le_bytes());
aad.extend_from_slice(&h.wal_seq.to_le_bytes());
aad.extend_from_slice(&h.manifest_start.to_le_bytes());
aad.extend_from_slice(&h.manifest_pages.to_le_bytes());
aad.extend_from_slice(&h.manifest_len.to_le_bytes());
aad
}
fn rd_u16(b: &[u8], o: usize) -> u16 {
u16::from_le_bytes(b[o..o + 2].try_into().unwrap())
}
fn rd_u32(b: &[u8], o: usize) -> u32 {
u32::from_le_bytes(b[o..o + 4].try_into().unwrap())
}
fn rd_u64(b: &[u8], o: usize) -> u64 {
u64::from_le_bytes(b[o..o + 8].try_into().unwrap())
}
fn rd_i64(b: &[u8], o: usize) -> i64 {
i64::from_le_bytes(b[o..o + 8].try_into().unwrap())
}
impl Header {
pub fn to_page(&self) -> [u8; PAGE_SIZE] {
let mut b = [0u8; PAGE_SIZE];
b[0..4].copy_from_slice(MAGIC);
b[4..6].copy_from_slice(&self.version.to_le_bytes());
b[6..10].copy_from_slice(&self.page_size.to_le_bytes());
b[10..14].copy_from_slice(&self.flags.to_le_bytes());
b[14..18].copy_from_slice(&self.dimensions.to_le_bytes());
b[18..26].copy_from_slice(&self.created_at.to_le_bytes());
b[26..34].copy_from_slice(&self.write_counter.to_le_bytes());
b[34..42].copy_from_slice(&self.next_page.to_le_bytes());
b[42..50].copy_from_slice(&self.catalog_start.to_le_bytes());
b[50..58].copy_from_slice(&self.catalog_pages.to_le_bytes());
b[58..66].copy_from_slice(&self.catalog_len.to_le_bytes());
b[66..74].copy_from_slice(&self.vector_count.to_le_bytes());
b[74..78].copy_from_slice(&self.m_cost.to_le_bytes());
b[78..82].copy_from_slice(&self.t_cost.to_le_bytes());
b[82..86].copy_from_slice(&self.p_cost.to_le_bytes());
b[86..86 + SALT_LEN].copy_from_slice(&self.salt);
b[102..102 + NONCE_LEN].copy_from_slice(&self.dek_nonce);
b[114..114 + WRAPPED_DEK_LEN].copy_from_slice(&self.wrapped_dek);
b[162..170].copy_from_slice(&self.index_start.to_le_bytes());
b[170..178].copy_from_slice(&self.index_pages.to_le_bytes());
b[178..186].copy_from_slice(&self.index_len.to_le_bytes());
b[186..194].copy_from_slice(&self.wal_start.to_le_bytes());
b[194..202].copy_from_slice(&self.wal_pages.to_le_bytes());
b[202..210].copy_from_slice(&self.wal_seq.to_le_bytes());
b[214..222].copy_from_slice(&self.manifest_start.to_le_bytes());
b[222..230].copy_from_slice(&self.manifest_pages.to_le_bytes());
b[230..238].copy_from_slice(&self.manifest_len.to_le_bytes());
let crc = crate::wal::checksum(&b[0..HEADER_CRC_OFF]);
b[HEADER_CRC_OFF..HEADER_CRC_OFF + 4].copy_from_slice(&crc.to_le_bytes());
b
}
pub fn from_page(b: &[u8]) -> Result<Header> {
if b.len() < PAGE_SIZE {
return Err(MnemoError::BadMagic);
}
if &b[0..4] != MAGIC {
return Err(MnemoError::BadMagic);
}
let version = rd_u16(b, 4);
if version != VERSION && !(MIGRATABLE_FROM..VERSION).contains(&version) {
return Err(MnemoError::UnsupportedVersion(version));
}
let stored_crc = rd_u32(b, HEADER_CRC_OFF);
if crate::wal::checksum(&b[0..HEADER_CRC_OFF]) != stored_crc {
return Err(MnemoError::HeaderChecksum);
}
let mut salt = [0u8; SALT_LEN];
salt.copy_from_slice(&b[86..86 + SALT_LEN]);
let mut dek_nonce = [0u8; NONCE_LEN];
dek_nonce.copy_from_slice(&b[102..102 + NONCE_LEN]);
let mut wrapped_dek = [0u8; WRAPPED_DEK_LEN];
wrapped_dek.copy_from_slice(&b[114..114 + WRAPPED_DEK_LEN]);
Ok(Header {
version,
page_size: rd_u32(b, 6),
flags: rd_u32(b, 10),
dimensions: rd_u32(b, 14),
created_at: rd_i64(b, 18),
write_counter: rd_u64(b, 26),
next_page: rd_u64(b, 34),
catalog_start: rd_u64(b, 42),
catalog_pages: rd_u64(b, 50),
catalog_len: rd_u64(b, 58),
vector_count: rd_u64(b, 66),
m_cost: rd_u32(b, 74),
t_cost: rd_u32(b, 78),
p_cost: rd_u32(b, 82),
salt,
dek_nonce,
wrapped_dek,
index_start: rd_u64(b, 162),
index_pages: rd_u64(b, 170),
index_len: rd_u64(b, 178),
wal_start: rd_u64(b, 186),
wal_pages: rd_u64(b, 194),
wal_seq: rd_u64(b, 202),
manifest_start: rd_u64(b, 214),
manifest_pages: rd_u64(b, 222),
manifest_len: rd_u64(b, 230),
seal_nonce: if version >= 7 {
let mut n = [0u8; NONCE_LEN];
n.copy_from_slice(&b[HEADER_SEAL_NONCE_OFF..HEADER_SEAL_NONCE_OFF + NONCE_LEN]);
Some(n)
} else {
None
},
seal_tag: if version >= 7 {
let mut t = [0u8; TAG_LEN];
t.copy_from_slice(&b[HEADER_SEAL_TAG_OFF..HEADER_SEAL_TAG_OFF + TAG_LEN]);
Some(t)
} else {
None
},
})
}
pub fn validate_seal(&self, dek: &[u8; crate::crypto::KEY_LEN]) -> Result<()> {
if self.version < 7 {
return Ok(());
}
let nonce = self
.seal_nonce
.ok_or(MnemoError::HeaderTampered)?;
let tag = self
.seal_tag
.ok_or(MnemoError::HeaderTampered)?;
let aad = header_seal_aad(self);
crate::crypto::aead_decrypt(dek, &nonce, &tag, &aad)
.map(|_| ())
.map_err(|_| MnemoError::HeaderTampered)
}
pub fn apply_seal(
&mut self,
page: &mut [u8; PAGE_SIZE],
dek: &[u8; crate::crypto::KEY_LEN],
) -> Result<()> {
if self.version < 7 {
return Ok(());
}
let nonce = crate::crypto::random_nonce();
let aad = header_seal_aad(self);
let tag_buf = crate::crypto::aead_encrypt(dek, &nonce, &[], &aad)?;
if tag_buf.len() != TAG_LEN {
return Err(MnemoError::Crypto(
"header seal: unexpected tag length".into(),
));
}
let mut tag = [0u8; TAG_LEN];
tag.copy_from_slice(&tag_buf);
page[HEADER_SEAL_NONCE_OFF..HEADER_SEAL_NONCE_OFF + NONCE_LEN]
.copy_from_slice(&nonce);
page[HEADER_SEAL_TAG_OFF..HEADER_SEAL_TAG_OFF + TAG_LEN].copy_from_slice(&tag);
self.seal_nonce = Some(nonce);
self.seal_tag = Some(tag);
Ok(())
}
}
const _: () = assert!(HEADER_CRC_OFF + 4 <= PAGE_SIZE);
const _: () = assert!(TAG_LEN == 16);