use anyhow::{Result, bail};
use zeroize::Zeroizing;
use crate::KdfParams;
use crate::crypto::algorithm::Algorithm;
pub mod tlv;
pub mod v1;
pub mod v2;
pub const MAGIC: &[u8; 4] = b"KNST";
pub const MAGIC_LEN: usize = 4;
pub const VER_LEN: usize = 1;
pub const CURRENT_VERSION: u8 = v2::VERSION_V2;
#[derive(Debug, Clone)]
pub struct Header {
pub(crate) version: u8,
pub(crate) kdf: KdfParams,
pub(crate) algorithm: Algorithm,
pub(crate) salt: Vec<u8>,
pub(crate) nonce: Vec<u8>,
}
impl Header {
pub fn new(kdf: KdfParams, algorithm: Algorithm, salt: Vec<u8>, nonce: Vec<u8>) -> Self {
Self {
version: CURRENT_VERSION,
kdf,
algorithm,
salt,
nonce,
}
}
pub fn version(&self) -> u8 {
self.version
}
pub fn kdf(&self) -> &KdfParams {
&self.kdf
}
pub fn algorithm(&self) -> Algorithm {
self.algorithm
}
pub fn salt(&self) -> &[u8] {
&self.salt
}
pub fn nonce(&self) -> &[u8] {
&self.nonce
}
pub fn build_aad(&self) -> Vec<u8> {
build_header_aad(self)
}
pub fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
let aad = self.build_aad();
self.algorithm.decrypt(key, self.nonce(), ciphertext, &aad)
}
pub fn encrypt_store(
kdf: KdfParams,
algorithm: Algorithm,
salt: Vec<u8>,
key: &[u8],
plaintext: &[u8],
) -> Result<(Self, Vec<u8>)> {
let tmp = Self::new(kdf, algorithm, salt.clone(), vec![]);
let aad = tmp.build_aad();
let (ciphertext, nonce) = algorithm.encrypt(key, plaintext, &aad)?;
let header = Self::new(kdf, algorithm, salt, nonce);
Ok((header, ciphertext))
}
}
#[derive(Debug)]
pub struct KeystoreFile {
pub(crate) header: Header,
pub(crate) ciphertext: Vec<u8>,
}
impl KeystoreFile {
pub fn new(header: Header, ciphertext: Vec<u8>) -> Self {
Self { header, ciphertext }
}
pub fn version(&self) -> u8 {
self.header.version()
}
pub fn kdf(&self) -> &KdfParams {
self.header.kdf()
}
pub fn algorithm(&self) -> Algorithm {
self.header.algorithm()
}
pub fn salt(&self) -> &[u8] {
self.header.salt()
}
pub fn nonce(&self) -> &[u8] {
self.header.nonce()
}
pub fn ciphertext(&self) -> &[u8] {
&self.ciphertext
}
pub fn decrypt(&self, key: &[u8]) -> Result<Zeroizing<Vec<u8>>> {
self.header.decrypt(key, self.ciphertext())
}
}
pub fn parse(data: &[u8]) -> Result<KeystoreFile> {
if data.len() < MAGIC_LEN + VER_LEN {
bail!("file too short");
}
if &data[..MAGIC_LEN] != MAGIC {
bail!("invalid magic");
}
let version = data[MAGIC_LEN];
match version {
v1::VERSION_V1 => v1::parse(data),
v2::VERSION_V2 => v2::parse(data),
_ => bail!("unsupported version"),
}
}
pub fn serialize(file: &KeystoreFile) -> Result<Vec<u8>> {
v2::serialize(file)
}
fn build_header_aad(header: &Header) -> Vec<u8> {
match header.version() {
v2::VERSION_V2 => v2::build_header_aad(header),
_ => unreachable!(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_invalid_magic_fails() {
let mut data = vec![0u8; 10];
data[..4].copy_from_slice(b"FAIL");
let result = parse(&data);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("invalid magic"));
}
#[test]
fn parse_unsupported_version_fails() {
let mut data = vec![0u8; 10];
data[..4].copy_from_slice(b"KNST"); data[4] = 99;
let result = parse(&data);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("unsupported version")
);
}
}