scll_core/backend/scp03.rs
1//! `Scp03Backend` — SCP03, Amendment D v1.1.2. PDD §3.3 / §4.1.
2//!
3//! `no_std`: `wrap`/`unwrap`/`encrypt_put_key_payload` return bounded
4//! `heapless::Vec` (a wrapped C-APDU is still ≤ `CAPDU_MAX`; a stripped R-APDU
5//! ≤ `RAPDU_MAX`; an encrypted key block ≤ `ENC_KEY_BLOCK_MAX`).
6
7use heapless::Vec;
8
9use crate::backend::key::{KeyBackend, KeyHandle};
10use crate::error::BackendError;
11use crate::limits::{CAPDU_MAX, ENC_KEY_BLOCK_MAX, RAPDU_MAX, SCP03_S16_MAX};
12
13/// SCP03 secure-channel size mode (Amendment D §5.1 Table 5-1, bit b4 `0x08`):
14/// `S8` uses 8-byte challenges/cryptograms and 8-byte (truncated) MACs (legacy);
15/// `S16` (Amendment D v1.2) uses 16-byte challenges/cryptograms and full 16-byte
16/// MACs. The MAC *chaining* value is 16 bytes in both modes.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ScpMode {
19 /// 8-byte fields, 8-byte truncated MACs.
20 S8,
21 /// 16-byte fields, full 16-byte MACs (Amendment D v1.2).
22 S16,
23}
24
25impl ScpMode {
26 /// Derive the mode from the SCP03 `i` parameter (bit b4 `0x08` ⇒ S16).
27 #[must_use]
28 pub const fn from_i(i_param: u8) -> Self {
29 if i_param & 0x08 != 0 {
30 Self::S16
31 } else {
32 Self::S8
33 }
34 }
35 /// Challenge / cryptogram length in bytes (8 or 16).
36 #[must_use]
37 pub const fn field_len(self) -> usize {
38 match self {
39 Self::S8 => 8,
40 Self::S16 => 16,
41 }
42 }
43 /// Length of the MAC appended to commands/responses: 8 bytes (truncated) in
44 /// S8, the full 16 bytes in S16 (Amendment D §6.2.4/§6.2.5).
45 #[must_use]
46 pub const fn mac_len(self) -> usize {
47 self.field_len()
48 }
49 /// KDF "L" (derived-data length in bits) for challenges and cryptograms:
50 /// `0x0040` (64) in S8, `0x0080` (128) in S16 (Amendment D §6.2.2).
51 #[must_use]
52 pub const fn l_bits(self) -> u16 {
53 match self {
54 Self::S8 => 0x0040,
55 Self::S16 => 0x0080,
56 }
57 }
58}
59
60/// Backend-defined opaque SCP03 session: an **index into the backend's session
61/// table**, which holds the derived S-ENC/S-MAC/S-RMAC, MAC chaining value and
62/// fixed security level. Opaque to callers; never exposes key material. The
63/// `new`/`index` accessors are backend-facing.
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub struct Scp03Session(u16);
66
67impl Scp03Session {
68 /// Construct from a backend session-slot index.
69 #[must_use]
70 pub const fn new(index: u16) -> Self {
71 Self(index)
72 }
73 /// The backend session-slot index.
74 #[must_use]
75 pub const fn index(self) -> u16 {
76 self.0
77 }
78}
79
80/// SCP03 crypto flow. Required by the default card manager (§3.6).
81pub trait Scp03Backend: KeyBackend {
82 /// Derive the SCP03 session keys (S-ENC/S-MAC/S-RMAC) from the static keys
83 /// and the host/card challenges, returning an opaque [`Scp03Session`]. The
84 /// `mode` (S8/S16) is recorded in the session and fixes the MAC width and
85 /// cryptogram length for its lifetime. `host_challenge`/`card_challenge` are
86 /// `mode.field_len()` bytes (8 or 16).
87 ///
88 /// # Errors
89 /// Returns [`BackendError::Crypto`] if key derivation fails or a challenge
90 /// length does not match `mode`, or [`BackendError::KeyGen`] if no session
91 /// slot is free.
92 fn scp03_derive_session(
93 &self,
94 static_enc: &KeyHandle,
95 static_mac: &KeyHandle,
96 mode: ScpMode,
97 host_challenge: &[u8],
98 card_challenge: &[u8],
99 ) -> Result<Scp03Session, BackendError>;
100
101 /// Compute the expected card cryptogram for verification (8 or 16 bytes per
102 /// the session's mode).
103 ///
104 /// # Errors
105 /// Returns [`BackendError::Crypto`] if the session is invalid or the MAC
106 /// computation fails.
107 fn scp03_card_cryptogram(
108 &self,
109 s: &Scp03Session,
110 host_ch: &[u8],
111 card_ch: &[u8],
112 ) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError>;
113
114 /// Compute the host cryptogram for EXTERNAL AUTHENTICATE (8 or 16 bytes per
115 /// the session's mode).
116 ///
117 /// # Errors
118 /// Returns [`BackendError::Crypto`] if the session is invalid or the MAC
119 /// computation fails.
120 fn scp03_host_cryptogram(
121 &self,
122 s: &Scp03Session,
123 host_ch: &[u8],
124 card_ch: &[u8],
125 ) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError>;
126
127 /// Recompute the expected **pseudo-random** card challenge for verification
128 /// (Amendment D §6.2.2.1): KDF keyed by the static `Key-ENC`, derivation
129 /// constant `0x02`, `L = mode.l_bits()`, context = `seq_counter ‖
130 /// invoker_aid`. Returns 8 or 16 bytes per `mode`. The caller compares it
131 /// (constant-time) to the card challenge from the IU response when a
132 /// sequence counter is present.
133 ///
134 /// # Errors
135 /// Returns [`BackendError::Crypto`] if the handle is invalid or derivation
136 /// fails.
137 fn scp03_pseudo_card_challenge(
138 &self,
139 static_enc: &KeyHandle,
140 mode: ScpMode,
141 seq_counter: &[u8; 3],
142 invoker_aid: &[u8],
143 ) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError>;
144
145 // No `level` arg: the session fixes its security level at open (§4.1).
146 /// Apply the session security level (C-MAC, optional C-ENC) to `capdu`.
147 ///
148 /// # Errors
149 /// Returns [`BackendError::Crypto`] if the session is invalid or wrapping
150 /// fails.
151 fn scp03_wrap_command(
152 &self,
153 s: &mut Scp03Session,
154 capdu: &[u8],
155 ) -> Result<Vec<u8, CAPDU_MAX>, BackendError>;
156
157 /// Verify and strip the R-MAC/R-ENC from `rapdu`.
158 ///
159 /// # Errors
160 /// Returns [`BackendError::Crypto`] if R-MAC verification fails or the
161 /// session is invalid.
162 fn scp03_unwrap_response(
163 &self,
164 s: &mut Scp03Session,
165 rapdu: &[u8],
166 ) -> Result<Vec<u8, RAPDU_MAX>, BackendError>;
167
168 // New key is a HANDLE, not bytes (HSM-wrappable, no host plaintext).
169 // SCP03 uses the static DEK (Amendment D §6.2.6).
170 /// Encrypt `new_key` under the static DEK for a PUT KEY payload
171 /// (Amendment D §6.2.6).
172 ///
173 /// # Errors
174 /// Returns [`BackendError::Crypto`] if encryption fails, or
175 /// [`BackendError::Unsupported`] if a handle is not encryptable.
176 fn scp03_encrypt_put_key_payload(
177 &self,
178 dek: &KeyHandle,
179 new_key: &KeyHandle,
180 ) -> Result<Vec<u8, ENC_KEY_BLOCK_MAX>, BackendError>;
181
182 /// Release the backend session slot held by `s`, zeroizing its session
183 /// keys and MAC chaining value. Called by the card manager on channel
184 /// close so a long-lived backend does not leak slots across many
185 /// open/close cycles (PDD §3.6 / §4.1). The default is a no-op, so
186 /// stateless or stub backends that keep no slot table need not override it.
187 fn scp03_close_session(&self, _s: Scp03Session) {}
188}