use crate::types::KeyNumber;
use aes::{
Aes128,
cipher::{Array, BlockCipherEncrypt, BlockSizeUser, KeyInit},
};
type Block = Array<u8, <Aes128 as BlockSizeUser>::BlockSize>;
const DIV_CONST_AES128: u8 = 0x01;
const RB: u8 = 0x87;
pub const NTAG424_AID: [u8; 7] = [0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01];
pub const MAX_SYSTEM_ID_LEN: usize = 16;
pub fn diversify_ntag424(
master_key: &[u8; 16],
uid: &[u8; 7],
key_number: KeyNumber,
system_identifier: &[u8],
) -> [u8; 16] {
assert!(
system_identifier.len() <= MAX_SYSTEM_ID_LEN,
"system identifier must be at most {} bytes, got {}",
MAX_SYSTEM_ID_LEN,
system_identifier.len(),
);
let mut m = [0u8; 31];
let len = 7 + 7 + 1 + system_identifier.len();
m[..7].copy_from_slice(uid);
m[7..14].copy_from_slice(&NTAG424_AID);
m[14] = key_number.as_byte();
m[15..15 + system_identifier.len()].copy_from_slice(system_identifier);
diversify_aes128(master_key, &m[..len])
}
pub fn diversify_aes128(master_key: &[u8; 16], diversification_input: &[u8]) -> [u8; 16] {
assert!(
!diversification_input.is_empty() && diversification_input.len() <= 31,
"diversification input must be 1–31 bytes, got {}",
diversification_input.len(),
);
let cipher = Aes128::new(master_key.into());
let mut l = Block::default();
cipher.encrypt_block(&mut l);
let k1 = double(&l);
let k2 = double(&k1);
let mut d = [0u8; 32];
d[0] = DIV_CONST_AES128;
d[1..1 + diversification_input.len()].copy_from_slice(diversification_input);
let padded = diversification_input.len() < 31;
if padded {
d[1 + diversification_input.len()] = 0x80;
}
let subkey = if padded { &k2 } else { &k1 };
for i in 0..16 {
d[16 + i] ^= subkey[i];
}
let mut state = Block::default();
for i in 0..16 {
state[i] ^= d[i];
}
cipher.encrypt_block(&mut state);
for i in 0..16 {
state[i] ^= d[16 + i];
}
cipher.encrypt_block(&mut state);
state.into()
}
fn double(block: &Block) -> Block {
let mut out = Block::default();
let mut carry = 0u8;
for i in (0..16).rev() {
out[i] = (block[i] << 1) | carry;
carry = block[i] >> 7;
}
if block[0] & 0x80 != 0 {
out[15] ^= RB;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{hex_array, hex_bytes};
#[test]
fn an10922_aes128_vector() {
let k: [u8; 16] = hex_array("00112233445566778899AABBCCDDEEFF");
let cipher = Aes128::new((&k).into());
let mut l = Block::default();
cipher.encrypt_block(&mut l);
let k0: [u8; 16] = l.into();
assert_eq!(k0, hex_array::<16>("FDE4FBAE4A09E020EFF722969F83832B"));
let k1 = double(&l);
let k1_bytes: [u8; 16] = k1.into();
assert_eq!(
k1_bytes,
hex_array::<16>("FBC9F75C9413C041DFEE452D3F0706D1")
);
let k2 = double(&k1);
let k2_bytes: [u8; 16] = k2.into();
assert_eq!(
k2_bytes,
hex_array::<16>("F793EEB928278083BFDC8A5A7E0E0D25")
);
let m = hex_bytes("04782E21801D803042F54E585020416275");
assert_eq!(m.len(), 17);
let expected: [u8; 16] = hex_array("A8DD63A3B89D54B37CA802473FDA9175");
assert_eq!(diversify_aes128(&k, &m), expected);
}
#[test]
fn max_length_input_uses_k1() {
let key = [0u8; 16];
let m = [0xAA; 31];
let _ = diversify_aes128(&key, &m);
}
#[test]
fn min_length_input() {
let key = [0u8; 16];
let m = [0x42];
let _ = diversify_aes128(&key, &m);
}
#[test]
#[should_panic(expected = "1–31 bytes")]
fn empty_input_panics() {
diversify_aes128(&[0; 16], &[]);
}
#[test]
#[should_panic(expected = "1–31 bytes")]
fn too_long_input_panics() {
diversify_aes128(&[0; 16], &[0; 32]);
}
#[test]
fn ntag424_per_key_diversification() {
let master = [0x42u8; 16];
let uid = [0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let sys = b"test";
let keys: [_; 5] = [
KeyNumber::Key0,
KeyNumber::Key1,
KeyNumber::Key2,
KeyNumber::Key3,
KeyNumber::Key4,
]
.map(|kn| diversify_ntag424(&master, &uid, kn, sys));
for (i, a) in keys.iter().enumerate() {
for b in &keys[i + 1..] {
assert_ne!(a, b);
}
}
}
#[test]
fn ntag424_matches_manual_construction() {
let master = hex_array::<16>("00112233445566778899AABBCCDDEEFF");
let uid = [0x04, 0x78, 0x2E, 0x21, 0x80, 0x1D, 0x80];
let sys = b"myapp";
let mut m = alloc::vec::Vec::new();
m.extend_from_slice(&uid);
m.extend_from_slice(&NTAG424_AID);
m.push(KeyNumber::Key2.as_byte());
m.extend_from_slice(sys);
assert_eq!(
diversify_ntag424(&master, &uid, KeyNumber::Key2, sys),
diversify_aes128(&master, &m),
);
}
#[test]
#[should_panic(expected = "at most 16 bytes")]
fn ntag424_long_sysid_panics() {
diversify_ntag424(&[0; 16], &[0; 7], KeyNumber::Key0, &[0; 17]);
}
}