1use anyhow::{bail, Result};
12use std::fmt;
13
14pub const GET_METADATA_MGMT: [u8; 5] = [0x00, 0xF7, 0x00, 0x9B, 0x00];
16pub const GET_METADATA_PIN: [u8; 5] = [0x00, 0xF7, 0x00, 0x80, 0x00];
18pub const GET_METADATA_PUK: [u8; 5] = [0x00, 0xF7, 0x00, 0x81, 0x00];
20
21pub(crate) fn get_metadata_apdu(slot: u8) -> [u8; 5] {
23 [0x00, 0xF7, 0x00, slot, 0x00]
24}
25
26const TAG_ALGORITHM: u8 = 0x01;
28const TAG_POLICY: u8 = 0x02;
29const TAG_ORIGIN: u8 = 0x03;
30const TAG_IS_DEFAULT: u8 = 0x05;
31const TAG_RETRIES: u8 = 0x06;
32
33const TOUCH_ALWAYS: u8 = 0x02;
35const TOUCH_CACHED: u8 = 0x03;
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum MgmtAlgo {
40 Tdes,
41 Aes128,
42 Aes192,
43 Aes256,
44}
45
46impl MgmtAlgo {
47 pub fn from_id(id: u8) -> Option<Self> {
49 match id {
50 0x03 => Some(Self::Tdes),
51 0x08 => Some(Self::Aes128),
52 0x0A => Some(Self::Aes192),
53 0x0C => Some(Self::Aes256),
54 _ => None,
55 }
56 }
57
58 pub fn id(self) -> u8 {
61 match self {
62 Self::Tdes => 0x03,
63 Self::Aes128 => 0x08,
64 Self::Aes192 => 0x0A,
65 Self::Aes256 => 0x0C,
66 }
67 }
68
69 pub fn key_len(self) -> usize {
71 match self {
72 Self::Aes128 => 16,
73 Self::Tdes | Self::Aes192 => 24,
74 Self::Aes256 => 32,
75 }
76 }
77
78 pub fn block_size(self) -> usize {
80 match self {
81 Self::Tdes => 8,
82 Self::Aes128 | Self::Aes192 | Self::Aes256 => 16,
83 }
84 }
85
86 pub fn from_name(name: &str) -> Option<Self> {
89 match name.to_ascii_uppercase().replace('-', "").as_str() {
90 "TDES" | "3DES" => Some(Self::Tdes),
91 "AES128" => Some(Self::Aes128),
92 "AES192" => Some(Self::Aes192),
93 "AES256" => Some(Self::Aes256),
94 _ => None,
95 }
96 }
97
98 pub fn check_key_len(self, key: &[u8]) -> Result<()> {
100 if key.len() != self.key_len() {
101 bail!(
102 "management key is {} bytes but the card uses {} ({} bytes)",
103 key.len(),
104 self,
105 self.key_len()
106 );
107 }
108 Ok(())
109 }
110}
111
112impl fmt::Display for MgmtAlgo {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 f.write_str(match self {
115 Self::Tdes => "3DES",
116 Self::Aes128 => "AES-128",
117 Self::Aes192 => "AES-192",
118 Self::Aes256 => "AES-256",
119 })
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum TouchPolicy {
126 Never,
127 Always,
128 Cached,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub struct MgmtMetadata {
134 pub algo: MgmtAlgo,
135 pub touch: TouchPolicy,
136 pub is_default: bool,
138}
139
140impl MgmtMetadata {
141 pub fn touch_required(&self) -> bool {
143 self.touch != TouchPolicy::Never
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub struct CredentialMetadata {
150 pub is_default: bool,
152 pub retries_total: u8,
153 pub retries_left: u8,
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum KeyOrigin {
159 Generated,
160 Imported,
161}
162
163#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct SlotMetadata {
166 pub algorithm: u8,
168 pub origin: Option<KeyOrigin>,
169}
170
171pub fn key_algorithm_name(id: u8) -> String {
173 match id {
174 0x06 => "RSA 1024".to_owned(),
175 0x07 => "RSA 2048".to_owned(),
176 0x05 => "RSA 3072".to_owned(),
177 0x16 => "RSA 4096".to_owned(),
178 0x11 => "EC P-256".to_owned(),
179 0x14 => "EC P-384".to_owned(),
180 0xE0 => "Ed25519".to_owned(),
181 0xE1 => "X25519".to_owned(),
182 other => format!("algorithm 0x{other:02x}"),
183 }
184}
185
186pub const ALGO_ECCP256: u8 = 0x11;
188
189fn is_default_flag(tlv: &std::collections::HashMap<u8, Vec<u8>>) -> bool {
190 tlv.get(&TAG_IS_DEFAULT).map(Vec::as_slice) == Some(&[0x01])
191}
192
193pub(crate) fn parse_credential_metadata(data: &[u8]) -> Result<CredentialMetadata> {
195 let tlv = crate::auxiliaries::parse_tlv_flat(data);
196 let (retries_total, retries_left) = match tlv.get(&TAG_RETRIES).map(Vec::as_slice) {
197 Some([total, left]) => (*total, *left),
198 _ => bail!("GET METADATA (PIN/PUK): missing retry counters"),
199 };
200 Ok(CredentialMetadata {
201 is_default: is_default_flag(&tlv),
202 retries_total,
203 retries_left,
204 })
205}
206
207pub(crate) fn parse_slot_metadata(data: &[u8]) -> Result<SlotMetadata> {
209 let tlv = crate::auxiliaries::parse_tlv_flat(data);
210 let algorithm = match tlv.get(&TAG_ALGORITHM).map(Vec::as_slice) {
211 Some([id]) => *id,
212 _ => bail!("GET METADATA (key slot): missing algorithm"),
213 };
214 let origin = match tlv.get(&TAG_ORIGIN).map(Vec::as_slice) {
215 Some([0x01]) => Some(KeyOrigin::Generated),
216 Some([0x02]) => Some(KeyOrigin::Imported),
217 _ => None,
218 };
219 Ok(SlotMetadata { algorithm, origin })
220}
221
222pub(crate) fn parse_mgmt_metadata(data: &[u8]) -> Result<MgmtMetadata> {
224 let tlv = crate::auxiliaries::parse_tlv_flat(data);
225 let id = match tlv.get(&TAG_ALGORITHM).map(Vec::as_slice) {
226 Some([id]) => *id,
227 _ => bail!("GET METADATA (management key): missing algorithm"),
228 };
229 let algo = MgmtAlgo::from_id(id)
230 .ok_or_else(|| anyhow::anyhow!("unsupported management key algorithm 0x{id:02x}"))?;
231 let touch = match tlv.get(&TAG_POLICY).map(Vec::as_slice) {
232 Some([_, TOUCH_ALWAYS]) => TouchPolicy::Always,
233 Some([_, TOUCH_CACHED]) => TouchPolicy::Cached,
234 _ => TouchPolicy::Never,
235 };
236 Ok(MgmtMetadata {
237 algo,
238 touch,
239 is_default: is_default_flag(&tlv),
240 })
241}
242
243pub(crate) fn parse_retries_remaining(data: &[u8]) -> Option<u8> {
246 let tlv = crate::auxiliaries::parse_tlv_flat(data);
247 match tlv.get(&TAG_RETRIES).map(Vec::as_slice) {
248 Some([_total, remaining]) => Some(*remaining),
249 _ => None,
250 }
251}
252
253#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn ids_round_trip() {
263 for algo in [
264 MgmtAlgo::Tdes,
265 MgmtAlgo::Aes128,
266 MgmtAlgo::Aes192,
267 MgmtAlgo::Aes256,
268 ] {
269 assert_eq!(MgmtAlgo::from_id(algo.id()), Some(algo));
270 }
271 assert_eq!(MgmtAlgo::from_id(0x07), None);
272 }
273
274 #[test]
275 fn tdes_and_aes192_share_key_length() {
276 assert_eq!(MgmtAlgo::Tdes.key_len(), MgmtAlgo::Aes192.key_len());
278 assert_ne!(MgmtAlgo::Tdes.block_size(), MgmtAlgo::Aes192.block_size());
279 }
280
281 #[test]
282 fn from_name_accepts_fixture_spellings() {
283 assert_eq!(MgmtAlgo::from_name("TDES"), Some(MgmtAlgo::Tdes));
284 assert_eq!(MgmtAlgo::from_name("3des"), Some(MgmtAlgo::Tdes));
285 assert_eq!(MgmtAlgo::from_name("AES-192"), Some(MgmtAlgo::Aes192));
286 assert_eq!(MgmtAlgo::from_name("aes256"), Some(MgmtAlgo::Aes256));
287 assert_eq!(MgmtAlgo::from_name("RSA2048"), None);
288 }
289
290 #[test]
291 fn check_key_len_message() {
292 let err = MgmtAlgo::Aes192.check_key_len(&[0u8; 16]).unwrap_err();
293 assert_eq!(
294 err.to_string(),
295 "management key is 16 bytes but the card uses AES-192 (24 bytes)"
296 );
297 assert!(MgmtAlgo::Aes192.check_key_len(&[0u8; 24]).is_ok());
298 }
299
300 #[test]
301 fn parse_metadata_aes192_default() {
302 let data = [0x01, 0x01, 0x0A, 0x02, 0x02, 0x00, 0x01, 0x05, 0x01, 0x01];
305 let md = parse_mgmt_metadata(&data).unwrap();
306 assert_eq!(md.algo, MgmtAlgo::Aes192);
307 assert!(!md.touch_required());
308 assert!(md.is_default);
309 }
310
311 #[test]
312 fn parse_metadata_touch_policy() {
313 let always = [0x01, 0x01, 0x03, 0x02, 0x02, 0x00, 0x02];
314 assert_eq!(
315 parse_mgmt_metadata(&always).unwrap().touch,
316 TouchPolicy::Always
317 );
318 let cached = [0x01, 0x01, 0x03, 0x02, 0x02, 0x00, 0x03];
319 assert!(parse_mgmt_metadata(&cached).unwrap().touch_required());
320 }
321
322 #[test]
323 fn parse_metadata_rejects_unknown_algorithm() {
324 let data = [0x01, 0x01, 0x07];
325 let err = parse_mgmt_metadata(&data).unwrap_err();
326 assert!(err
327 .to_string()
328 .contains("unsupported management key algorithm 0x07"));
329 }
330
331 #[test]
332 fn parse_metadata_requires_algorithm() {
333 assert!(parse_mgmt_metadata(&[0x05, 0x01, 0x01]).is_err());
334 }
335
336 #[test]
337 fn parse_credential_and_slot_metadata() {
338 let md = parse_credential_metadata(&[0x05, 0x01, 0x01, 0x06, 0x02, 0x03, 0x02]).unwrap();
340 assert_eq!(
341 md,
342 CredentialMetadata {
343 is_default: true,
344 retries_total: 3,
345 retries_left: 2
346 }
347 );
348 assert!(parse_credential_metadata(&[0x05, 0x01, 0x01]).is_err());
349 let md = parse_slot_metadata(&[0x01, 0x01, 0x11, 0x03, 0x01, 0x01]).unwrap();
351 assert_eq!(md.algorithm, ALGO_ECCP256);
352 assert_eq!(md.origin, Some(KeyOrigin::Generated));
353 assert_eq!(key_algorithm_name(0x07), "RSA 2048");
354 }
355
356 #[test]
357 fn parse_retries() {
358 let data = [0x05, 0x01, 0x00, 0x06, 0x02, 0x03, 0x00];
360 assert_eq!(parse_retries_remaining(&data), Some(0));
361 assert_eq!(parse_retries_remaining(&[]), None);
362 }
363}