Skip to main content

scll_core/command/
put_key.rs

1//! PUT KEY (CLA 84, INS D8) — PDD §5.3.1, GPCS v2.3.1 §11.8.
2//!
3//! P1 = KVN when replacing, `0x00` when adding (KVN inside data). P2 = `0x81`
4//! (multi-key bit + first KID `0x01`). Data = `new_kvn | key_block{ENC,MAC,DEK}`,
5//! each block `key_type | len | encrypted_key | kcv_len | kcv`. Card echoes
6//! `new_kvn | KCV_ENC | KCV_MAC | KCV_DEK`.
7
8use crate::command::{build, push, BuildError, Capdu};
9
10/// SCP03 AES key type (GPCS Amendment D). AES key blocks carry an extra inner
11/// length byte (the clear-key length); 3DES blocks (`0x80`) do not.
12const KEY_TYPE_AES: u8 = 0x88;
13
14/// One encrypted key block plus its KCV (built from backend output). The
15/// encrypted key already borrows (`&[u8]`) — no alloc here.
16pub struct KeyBlock<'a> {
17    pub key_type: u8, // 0x88 AES (SCP03) | 0x80 3DES (SCP02)
18    pub encrypted_key: &'a [u8],
19    pub kcv: [u8; 3],
20    /// Plaintext key length in bytes (16/24/32 for AES). Emitted as the AES
21    /// clear-key-length byte (Amendment D §7.2); ignored for 3DES blocks. This
22    /// differs from `encrypted_key.len()` for AES-192 (24-byte key, 32-byte
23    /// ciphertext).
24    pub clear_key_len: u8,
25}
26
27/// Build the PUT KEY C-APDU plaintext for a 3-key set.
28///
29/// `CLA=84 INS=D8`, `P1 = p1` (caller passes `new_kvn` to replace or `0x00` to
30/// add), P2 fixed `0x81` (multiple-key + first KID `0x01`). Data =
31/// `new_kvn ‖ key_block{ENC,MAC,DEK}` (GPCS §11.8.2.3.1).
32///
33/// Per key block:
34/// * **SCP03 AES** (`key_type == 0x88`, GPCS Amendment D v1.1.x §7.2):
35///   `key_type ‖ block_len ‖ aes_key_len ‖ enc_key ‖ 0x03 ‖ KCV`, where the
36///   encrypted key value is preceded by the clear AES key length and
37///   `block_len = 1 + len(enc_key)`. (For AES-128 the clear length equals the
38///   16-byte ciphertext length.) Omitting `aes_key_len` makes JCOP / the JCDK
39///   simulator reject the command (observed `6A88`).
40/// * **SCP02 3DES** (`key_type == 0x80`): `key_type ‖ len(enc_key) ‖ enc_key ‖
41///   0x03 ‖ KCV` — no inner length.
42///
43/// The KCV length is fixed at 3 ([`crate::limits::KCV_LEN`], matching
44/// `KeyBlock::kcv: [u8; 3]`).
45///
46/// # Errors
47/// Returns [`BuildError::Overflow`] if the encoded inputs would exceed the
48/// short-APDU plaintext buffer (`CAPDU_MAX`).
49pub fn put_key(p1: u8, new_kvn: u8, blocks: &[KeyBlock<'_>; 3]) -> Result<Capdu, BuildError> {
50    let mut data = Capdu::new();
51    push(&mut data, &[new_kvn])?;
52    for block in blocks {
53        let enc_len = u8::try_from(block.encrypted_key.len()).map_err(|_| BuildError::Overflow)?;
54        if block.key_type == KEY_TYPE_AES {
55            // AES: block length covers the inner clear-key-length byte + the
56            // (possibly padded) ciphertext. The inner byte is the PLAINTEXT key
57            // length (16/24/32), which differs from `enc_len` for AES-192.
58            let block_len = enc_len.checked_add(1).ok_or(BuildError::Overflow)?;
59            push(&mut data, &[block.key_type, block_len, block.clear_key_len])?;
60        } else {
61            push(&mut data, &[block.key_type, enc_len])?;
62        }
63        push(&mut data, block.encrypted_key)?;
64        // KCV_len is fixed at 3 (limits::KCV_LEN); KeyBlock::kcv is [u8; 3].
65        push(&mut data, &[0x03])?;
66        push(&mut data, &block.kcv)?;
67    }
68    build(0x84, 0xD8, p1, 0x81, &data, true)
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use scll_test_util::HexSlice;
75
76    fn aes_block(enc: &[u8], kcv: [u8; 3]) -> KeyBlock<'_> {
77        // 128-style fixtures where the clear length equals the (un-padded)
78        // ciphertext length; the 192/256 cases below set them independently.
79        KeyBlock {
80            key_type: 0x88,
81            encrypted_key: enc,
82            kcv,
83            clear_key_len: u8::try_from(enc.len()).unwrap(),
84        }
85    }
86
87    fn aes_block_clear(enc: &[u8], clear_key_len: u8, kcv: [u8; 3]) -> KeyBlock<'_> {
88        KeyBlock {
89            key_type: 0x88,
90            encrypted_key: enc,
91            kcv,
92            clear_key_len,
93        }
94    }
95
96    #[test]
97    fn replace_three_aes_blocks_golden() {
98        // Compact (2-byte) encrypted values so the full data field is spellable.
99        let enc = [0xDE, 0xAD];
100        let blocks = [
101            aes_block(&enc, [0x01, 0x02, 0x03]),
102            aes_block(&enc, [0x01, 0x02, 0x03]),
103            aes_block(&enc, [0x01, 0x02, 0x03]),
104        ];
105        let apdu = put_key(0x30, 0x30, &blocks).unwrap();
106        // AES block: key_type ‖ block_len(=1+enc) ‖ clear_len(=enc len here) ‖ enc ‖ KCV_len ‖ KCV
107        let block = [0x88, 0x03, 0x02, 0xDE, 0xAD, 0x03, 0x01, 0x02, 0x03];
108        let mut expected = heapless::Vec::<u8, 64>::new();
109        // 84 D8 P1=30 P2=81 Lc=0x1C(28) | new_kvn=30 | block×3 | Le=00
110        expected
111            .extend_from_slice(&[0x84, 0xD8, 0x30, 0x81, 0x1C, 0x30])
112            .unwrap();
113        for _ in 0..3 {
114            expected.extend_from_slice(&block).unwrap();
115        }
116        expected.extend_from_slice(&[0x00]).unwrap();
117        assert_eq!(HexSlice(&apdu), HexSlice(&expected));
118    }
119
120    #[test]
121    fn add_uses_p1_00_and_kvn_inside_data() {
122        let enc = [0x11; 16];
123        let blocks = [
124            aes_block(&enc, [0xAA, 0xBB, 0xCC]),
125            aes_block(&enc, [0xAA, 0xBB, 0xCC]),
126            aes_block(&enc, [0xAA, 0xBB, 0xCC]),
127        ];
128        let apdu = put_key(0x00, 0x31, &blocks).unwrap();
129        assert_eq!(&apdu[0..4], &[0x84, 0xD8, 0x00, 0x81]); // P1=00 add, P2=81
130        assert_eq!(apdu[5], 0x31); // new_kvn is the first data byte
131        assert_eq!(apdu[6], 0x88); // first block key_type
132        assert_eq!(apdu[7], 0x11); // block len = 1 + len(enc) = 1 + 16
133        assert_eq!(apdu[8], 0x10); // AES clear-key length = 16
134        assert_eq!(apdu[25], 0x03); // KCV_len (after 16-byte key)
135        assert_eq!(&apdu[26..29], &[0xAA, 0xBB, 0xCC]);
136        assert_eq!(*apdu.last().unwrap(), 0x00); // Le
137    }
138
139    #[test]
140    fn aes192_and_aes256_clear_length_byte() {
141        // AES-192's 24-byte key is 0x80-padded to a 32-byte ciphertext, the same
142        // length as AES-256, so only the inner clear-key-length byte tells them
143        // apart: 0x18 (24) vs 0x20 (32). Amendment D §7.2. Layout per block:
144        // [6]=key_type [7]=block_len(=1+32) [8]=clear_len [9..41]=enc …
145        let enc = [0x11u8; 32];
146
147        let b192 = [
148            aes_block_clear(&enc, 24, [1, 2, 3]),
149            aes_block_clear(&enc, 24, [1, 2, 3]),
150            aes_block_clear(&enc, 24, [1, 2, 3]),
151        ];
152        let a192 = put_key(0x00, 0x31, &b192).unwrap();
153        assert_eq!(a192[6], 0x88); // key_type AES
154        assert_eq!(a192[7], 0x21); // block_len = 1 + 32
155        assert_eq!(a192[8], 0x18); // clear-key length = 24 (NOT the 32-byte ciphertext)
156
157        let b256 = [
158            aes_block_clear(&enc, 32, [1, 2, 3]),
159            aes_block_clear(&enc, 32, [1, 2, 3]),
160            aes_block_clear(&enc, 32, [1, 2, 3]),
161        ];
162        let a256 = put_key(0x00, 0x31, &b256).unwrap();
163        assert_eq!(a256[7], 0x21); // block_len = 1 + 32
164        assert_eq!(a256[8], 0x20); // clear-key length = 32
165    }
166
167    #[test]
168    fn oversized_key_value_overflows() {
169        let enc = [0x00u8; 255];
170        let blocks = [
171            aes_block(&enc, [0; 3]),
172            aes_block(&enc, [0; 3]),
173            aes_block(&enc, [0; 3]),
174        ];
175        assert_eq!(put_key(0x30, 0x30, &blocks), Err(BuildError::Overflow));
176    }
177}