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}