use hkdf::Hkdf;
use sha2::Sha256;
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::crypto::format::{KEY_ID_LEN, SUITE_AES_256_SIV};
use crate::{Error, Result};
pub const MASTER_KEY_LEN: usize = 32;
pub const SIV_KEY_LEN: usize = 64;
const INFO_KEY_ID: &[u8] = b"git-xcrypt key-id v1";
const INFO_SUITE_AES_256_SIV: &[u8] = b"git-xcrypt suite 0x01 aes-256-siv";
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct MasterKey([u8; MASTER_KEY_LEN]);
impl MasterKey {
pub fn generate() -> Result<Self> {
let mut bytes = [0u8; MASTER_KEY_LEN];
let drawn = getrandom::fill(&mut bytes).map_err(|err| Error::Entropy(err.to_string()));
let key = drawn.map(|()| Self(bytes));
bytes.zeroize();
key
}
#[must_use]
pub fn from_bytes(bytes: [u8; MASTER_KEY_LEN]) -> Self {
Self(bytes)
}
#[must_use]
pub fn expose_bytes(&self) -> &[u8; MASTER_KEY_LEN] {
&self.0
}
#[must_use]
pub fn key_id(&self) -> [u8; KEY_ID_LEN] {
let mut key_id = [0u8; KEY_ID_LEN];
self.expand(INFO_KEY_ID, &mut key_id);
key_id
}
pub fn suite_key(&self, suite: u8) -> Result<SuiteKey> {
if suite != SUITE_AES_256_SIV {
return Err(Error::Format(format!(
"cipher suite {suite:#04x} needs a newer git-xcrypt"
)));
}
let mut key = SuiteKey([0u8; SIV_KEY_LEN]);
self.expand(INFO_SUITE_AES_256_SIV, &mut key.0);
Ok(key)
}
fn expand(&self, info: &[u8], out: &mut [u8]) {
const HKDF_SHA256_MAX_OUTPUT: usize = 255 * 32;
assert!(
out.len() <= HKDF_SHA256_MAX_OUTPUT,
"HKDF output length is out of range"
);
let hkdf = Hkdf::<Sha256>::new(None, &self.0);
hkdf.expand(info, out)
.expect("HKDF cannot fail for a length already checked above");
}
}
#[derive(Zeroize, ZeroizeOnDrop)]
pub struct SuiteKey([u8; SIV_KEY_LEN]);
impl SuiteKey {
#[must_use]
pub fn expose_bytes(&self) -> &[u8; SIV_KEY_LEN] {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixed_key() -> MasterKey {
MasterKey::from_bytes([7u8; MASTER_KEY_LEN])
}
#[test]
fn key_id_is_stable_for_the_same_key() {
assert_eq!(fixed_key().key_id(), fixed_key().key_id());
}
#[test]
fn the_suite_key_is_not_the_master_key() {
let key = fixed_key();
let suite = key.suite_key(SUITE_AES_256_SIV).expect("known suite");
assert_ne!(&suite.expose_bytes()[..MASTER_KEY_LEN], key.expose_bytes());
}
#[test]
fn the_suite_key_does_not_start_with_the_key_id() {
let key = fixed_key();
let suite = key.suite_key(SUITE_AES_256_SIV).expect("known suite");
assert_ne!(&suite.expose_bytes()[..KEY_ID_LEN], &key.key_id());
}
#[test]
fn an_unknown_suite_has_no_key() {
assert!(fixed_key().suite_key(0xff).is_err());
}
}