use hmac::{Hmac, KeyInit, Mac};
use sha2::Sha256;
use zeroize::Zeroizing;
pub const SEED_LEN: usize = 32;
pub const STORAGE_KEY_LEN: usize = 16;
pub const IDENTITY_LABEL: &[u8] = b"IDENTITY\0";
const STORAGE: &[u8] = b"STORAGE\0";
const INTEGRITY: &[u8] = b"INTEGRITY\0";
fn kdfa(key: &[u8], label: &[u8], context_u: &[u8], context_v: &[u8], out: &mut [u8]) {
let bits = u32::try_from(out.len().saturating_mul(8)).unwrap_or(u32::MAX).to_be_bytes();
for (counter, chunk) in (1u32..).zip(out.chunks_mut(32)) {
if let Ok(mut mac) = <Hmac<Sha256> as KeyInit>::new_from_slice(key) {
mac.update(&counter.to_be_bytes());
mac.update(label);
mac.update(context_u);
mac.update(context_v);
mac.update(&bits);
let block = Zeroizing::new(mac.finalize().into_bytes());
let take = chunk.len();
if let Some(head) = block.get(..take) {
chunk.copy_from_slice(head);
}
}
}
}
#[must_use]
pub fn storage_key(seed: &[u8; SEED_LEN], object_name: &[u8]) -> Zeroizing<[u8; STORAGE_KEY_LEN]> {
let mut key = Zeroizing::new([0u8; STORAGE_KEY_LEN]);
kdfa(seed, STORAGE, object_name, &[], key.as_mut_slice());
key
}
#[must_use]
pub fn integrity_tag(seed: &[u8; SEED_LEN], enc_identity: &[u8], object_name: &[u8]) -> [u8; 32] {
let mut hmac_key = Zeroizing::new([0u8; 32]);
kdfa(seed, INTEGRITY, &[], &[], hmac_key.as_mut_slice());
let mut tag = [0u8; 32];
if let Ok(mut mac) = <Hmac<Sha256> as KeyInit>::new_from_slice(hmac_key.as_slice()) {
mac.update(enc_identity);
mac.update(object_name);
tag.copy_from_slice(mac.finalize().into_bytes().as_slice());
}
tag
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::indexing_slicing)]
mod tests {
use super::*;
#[test]
fn keys_depend_on_the_seed_and_the_name() {
let seed = [7u8; SEED_LEN];
let name_a = [1u8; 34];
let name_b = [2u8; 34];
assert_ne!(*storage_key(&seed, &name_a), *storage_key(&seed, &name_b));
assert_ne!(*storage_key(&seed, &name_a), *storage_key(&[8u8; SEED_LEN], &name_a));
assert_ne!(integrity_tag(&seed, b"x", &name_a), integrity_tag(&seed, b"x", &name_b));
assert_ne!(integrity_tag(&seed, b"x", &name_a), integrity_tag(&seed, b"y", &name_a));
}
#[test]
fn tpm_labels_carry_their_terminating_zero() {
for label in [STORAGE, INTEGRITY, IDENTITY_LABEL] {
assert_eq!(label.last(), Some(&0u8));
assert_eq!(label.iter().filter(|b| **b == 0).count(), 1);
}
}
#[test]
fn kdfa_counter_starts_at_one_and_advances() {
let mut long = [0u8; 40];
kdfa(b"key", b"L\0", b"u", b"v", &mut long);
let mut mac = <Hmac<Sha256> as KeyInit>::new_from_slice(b"key").unwrap();
mac.update(&1u32.to_be_bytes());
mac.update(b"L\0uv");
mac.update(&320u32.to_be_bytes());
assert_eq!(&long[..32], mac.finalize().into_bytes().as_slice());
let mut mac = <Hmac<Sha256> as KeyInit>::new_from_slice(b"key").unwrap();
mac.update(&2u32.to_be_bytes());
mac.update(b"L\0uv");
mac.update(&320u32.to_be_bytes());
assert_eq!(&long[32..], &mac.finalize().into_bytes()[..8]);
}
}