scll_core/command/rmac_session.rs
1//! BEGIN / END R-MAC SESSION (CLA 80, INS 7A / 78) — SCP02 response integrity.
2//!
3//! These frame an SCP02 R-MAC session: `BEGIN R-MAC SESSION` tells the card to
4//! start appending an R-MAC to each response, and `END R-MAC SESSION` retrieves
5//! (and optionally ends) the accumulated R-MAC. They build the **plaintext**
6//! C-APDU only; the session/backend applies `CLA | 0x04` + C-MAC afterwards (the
7//! commands must themselves be sent inside the secure channel).
8//!
9//! Coding (GPCS v2.3.1 Appendix E; the parameter tables mirror Amendment D
10//! §7.x for SCP03, and are cross-checked byte-for-byte against the `skythen/scp02`
11//! Go reference, `beginRMACSession`/`EndRMACSession`):
12//! * BEGIN: INS `0x7A`, P1 `0x10` (each response carries an R-MAC — for SCP02
13//! which has no response encryption, `0x30`/R-ENC does not apply), P2 `0x00`.
14//! The data field is an LV-coded 'data' element (`len ‖ data`). The card does
15//! not interpret 'data' but folds it into the R-MAC, letting the host inject
16//! a challenge; the LV total is ≤ 25 bytes, so `data` is `1..=24` bytes.
17//! * END: INS `0x78`, P1 `0x00`, P2 `0x03` (end the session **and** return the
18//! R-MAC) or `0x01` (return the current R-MAC without ending). No data field;
19//! `Le = 00` requests the 8-byte R-MAC in the response.
20
21use crate::command::{build, push_lv, BuildError, Capdu};
22
23/// P1: begin an R-MAC session in which each response carries an R-MAC.
24const BEGIN_P1_RMAC: u8 = 0x10;
25/// P2: end the R-MAC session and return the accumulated R-MAC.
26const END_P2_END_AND_RETURN: u8 = 0x03;
27/// P2: return the current R-MAC without ending the session.
28const END_P2_RETURN_ONLY: u8 = 0x01;
29
30/// Build a `BEGIN R-MAC SESSION` (CLA 80, INS 7A) plaintext C-APDU.
31///
32/// `data` is the caller's optional R-MAC challenge, sent LV-coded (`len ‖ data`)
33/// and folded into the card's R-MAC. It must be `1..=24` bytes.
34///
35/// # Errors
36/// [`BuildError::Overflow`] if `data` is empty or longer than 24 bytes.
37#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
38pub fn begin_rmac_session(data: &[u8]) -> Result<Capdu, BuildError> {
39 if data.is_empty() || data.len() > 24 {
40 return Err(BuildError::Overflow);
41 }
42 let mut field = Capdu::new();
43 push_lv(&mut field, data)?; // 'data' element: len ‖ data
44 build(0x80, 0x7A, BEGIN_P1_RMAC, 0x00, &field, true)
45}
46
47/// Build an `END R-MAC SESSION` (CLA 80, INS 78) plaintext C-APDU. `end_session`
48/// selects P2 `0x03` (end and return the R-MAC) vs `0x01` (return only). The
49/// response returns the 8-byte R-MAC, so `Le = 00` is present.
50#[must_use]
51#[allow(clippy::module_name_repetitions)] // GP command name; intentional public API
52pub fn end_rmac_session(end_session: bool) -> Capdu {
53 let p2 = if end_session {
54 END_P2_END_AND_RETURN
55 } else {
56 END_P2_RETURN_ONLY
57 };
58 // 80 78 00 P2 00 — four-byte header + Le, no data field. Five bytes is far
59 // under CAPDU_MAX, so the push cannot fail (no-panic invariant, §10.5).
60 let mut apdu = Capdu::new();
61 let _ = apdu.extend_from_slice(&[0x80, 0x78, 0x00, p2, 0x00]);
62 apdu
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use scll_test_util::HexSlice;
69
70 #[test]
71 fn begin_wraps_data_lv_with_p1_10() {
72 // 80 7A 10 00 | Lc=04 | LV(03 A1 B2 C3) | Le=00
73 let apdu = begin_rmac_session(&[0xA1, 0xB2, 0xC3]).unwrap();
74 assert_eq!(
75 HexSlice(&apdu),
76 HexSlice([0x80, 0x7A, 0x10, 0x00, 0x04, 0x03, 0xA1, 0xB2, 0xC3, 0x00])
77 );
78 }
79
80 #[test]
81 fn begin_rejects_empty_and_oversized_data() {
82 assert_eq!(begin_rmac_session(&[]), Err(BuildError::Overflow));
83 assert_eq!(begin_rmac_session(&[0u8; 25]), Err(BuildError::Overflow));
84 // Boundary: 24 data bytes → 25-byte LV, still valid.
85 assert!(begin_rmac_session(&[0u8; 24]).is_ok());
86 }
87
88 #[test]
89 fn end_p2_selects_end_vs_return_only() {
90 // End + return: 80 78 00 03 00
91 assert_eq!(
92 HexSlice(&end_rmac_session(true)),
93 HexSlice([0x80, 0x78, 0x00, 0x03, 0x00])
94 );
95 // Return only: 80 78 00 01 00
96 assert_eq!(
97 HexSlice(&end_rmac_session(false)),
98 HexSlice([0x80, 0x78, 0x00, 0x01, 0x00])
99 );
100 }
101}