Skip to main content

osdp/secure/
crypto.rs

1//! AES-128 primitives, key derivation, cryptograms.
2//!
3//! # Spec: Annex D.4
4//!
5//! Session keys are derived by AES-128 encrypting fixed templates with the
6//! SCBK:
7//!
8//! - `S-ENC  = AES_SCBK([0x01, 0x82, RND.A[0..6], 0,0,0,0,0,0,0,0])`
9//! - `S-MAC1 = AES_SCBK([0x01, 0x01, RND.A[0..6], 0,0,0,0,0,0,0,0])`
10//! - `S-MAC2 = AES_SCBK([0x01, 0x02, RND.A[0..6], 0,0,0,0,0,0,0,0])`
11//!
12//! Cryptograms:
13//!
14//! - `ClientCryptogram = AES_S-ENC(RND.A || RND.B)`
15//! - `ServerCryptogram = AES_S-ENC(RND.B || RND.A)`
16
17use aes::Aes128;
18use aes::cipher::generic_array::GenericArray;
19use aes::cipher::{BlockDecrypt, BlockEncrypt, KeyInit};
20use zeroize::{Zeroize, ZeroizeOnDrop};
21
22/// 128-bit AES block.
23pub type Block = [u8; 16];
24
25/// AES-128 encrypt of a single block.
26#[inline]
27pub fn aes128_encrypt(key: &[u8; 16], block: &Block) -> Block {
28    let cipher = Aes128::new(GenericArray::from_slice(key));
29    let mut buf = *GenericArray::from_slice(block);
30    cipher.encrypt_block(&mut buf);
31    let mut out = [0u8; 16];
32    out.copy_from_slice(&buf);
33    out
34}
35
36/// AES-128 decrypt of a single block.
37#[inline]
38pub fn aes128_decrypt(key: &[u8; 16], block: &Block) -> Block {
39    let cipher = Aes128::new(GenericArray::from_slice(key));
40    let mut buf = *GenericArray::from_slice(block);
41    cipher.decrypt_block(&mut buf);
42    let mut out = [0u8; 16];
43    out.copy_from_slice(&buf);
44    out
45}
46
47/// Build the 16-byte template: `[tag1, tag2, RND.A[0..6], 0,0,0,0,0,0,0,0]`.
48fn key_template(tag1: u8, tag2: u8, rnd_a: &[u8; 8]) -> Block {
49    let mut t = [0u8; 16];
50    t[0] = tag1;
51    t[1] = tag2;
52    t[2..8].copy_from_slice(&rnd_a[0..6]);
53    t
54}
55
56/// Derived session keys.
57///
58/// Zeroized on drop so cancelled or panicking sessions don't leave key
59/// material in heap or stack memory.
60#[derive(Debug, Clone, PartialEq, Eq, Default, Zeroize, ZeroizeOnDrop)]
61pub struct SessionKeys {
62    /// `S-ENC` — encryption key.
63    pub s_enc: [u8; 16],
64    /// `S-MAC1` — MAC key for all blocks except the last.
65    pub s_mac1: [u8; 16],
66    /// `S-MAC2` — MAC key for the last block.
67    pub s_mac2: [u8; 16],
68}
69
70impl SessionKeys {
71    /// Derive `S-ENC`/`S-MAC1`/`S-MAC2` from `scbk` and `RND.A`.
72    pub fn derive(scbk: &[u8; 16], rnd_a: &[u8; 8]) -> Self {
73        Self {
74            s_enc: aes128_encrypt(scbk, &key_template(0x01, 0x82, rnd_a)),
75            s_mac1: aes128_encrypt(scbk, &key_template(0x01, 0x01, rnd_a)),
76            s_mac2: aes128_encrypt(scbk, &key_template(0x01, 0x02, rnd_a)),
77        }
78    }
79}
80
81/// `ClientCryptogram = AES_S-ENC(RND.A || RND.B)`.
82pub fn client_cryptogram(s_enc: &[u8; 16], rnd_a: &[u8; 8], rnd_b: &[u8; 8]) -> Block {
83    let mut block = [0u8; 16];
84    block[..8].copy_from_slice(rnd_a);
85    block[8..].copy_from_slice(rnd_b);
86    aes128_encrypt(s_enc, &block)
87}
88
89/// `ServerCryptogram = AES_S-ENC(RND.B || RND.A)`.
90pub fn server_cryptogram(s_enc: &[u8; 16], rnd_a: &[u8; 8], rnd_b: &[u8; 8]) -> Block {
91    let mut block = [0u8; 16];
92    block[..8].copy_from_slice(rnd_b);
93    block[8..].copy_from_slice(rnd_a);
94    aes128_encrypt(s_enc, &block)
95}
96
97/// `Initial R-MAC = AES_S-MAC2( AES_S-MAC1( ServerCryptogram ) )`.
98///
99/// # Spec: Annex D.4
100pub fn initial_rmac(s_mac1: &[u8; 16], s_mac2: &[u8; 16], server_cryptogram: &Block) -> Block {
101    let first = aes128_encrypt(s_mac1, server_cryptogram);
102    aes128_encrypt(s_mac2, &first)
103}
104
105/// Legacy key diversification: `SCBK = AES_MK( cUID || ~cUID )`.
106///
107/// # Spec: Annex D.7 (deprecated)
108pub fn diversify_scbk_legacy(mk: &[u8; 16], cuid: &[u8; 8]) -> [u8; 16] {
109    let mut block = [0u8; 16];
110    block[..8].copy_from_slice(cuid);
111    for (i, &b) in cuid.iter().enumerate() {
112        block[8 + i] = !b;
113    }
114    aes128_encrypt(mk, &block)
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    /// AES-128 inversion.
122    #[test]
123    fn enc_then_dec_identity() {
124        let key = [0u8; 16];
125        let plain = [0xABu8; 16];
126        let ct = aes128_encrypt(&key, &plain);
127        let pt = aes128_decrypt(&key, &ct);
128        assert_eq!(pt, plain);
129    }
130
131    /// FIPS-197 known answer — AES-128 with all-zero key + plaintext.
132    #[test]
133    fn fips_known_answer() {
134        let key = [0u8; 16];
135        let plain = [0u8; 16];
136        let ct = aes128_encrypt(&key, &plain);
137        let expected = [
138            0x66, 0xE9, 0x4B, 0xD4, 0xEF, 0x8A, 0x2C, 0x3B, 0x88, 0x4C, 0xFA, 0x59, 0xCA, 0x34,
139            0x2B, 0x2E,
140        ];
141        assert_eq!(ct, expected);
142    }
143
144    #[test]
145    fn keys_differ() {
146        let scbk = [0xAA; 16];
147        let rnd_a = [0x55u8; 8];
148        let k = SessionKeys::derive(&scbk, &rnd_a);
149        assert_ne!(k.s_enc, k.s_mac1);
150        assert_ne!(k.s_mac1, k.s_mac2);
151        assert_ne!(k.s_enc, k.s_mac2);
152    }
153
154    #[test]
155    fn cryptograms_distinct() {
156        let s_enc = [0; 16];
157        let rnd_a = [1u8; 8];
158        let rnd_b = [2u8; 8];
159        let c = client_cryptogram(&s_enc, &rnd_a, &rnd_b);
160        let s = server_cryptogram(&s_enc, &rnd_a, &rnd_b);
161        assert_ne!(c, s);
162    }
163}