scll_core/backend/scp02.rs
1//! `Scp02Backend` — SCP02, GPCS v2.3.1 §E. PDD §3.3 / §4.2.
2//!
3//! Separate trait so SCP03-only backends omit it (and the 3DES/Retail-MAC path).
4//!
5//! `no_std`: same bounded-`heapless::Vec` returns as the SCP03 trait.
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};
12
13/// Backend-defined opaque SCP02 session: an **index into the backend's session
14/// table** (session ENC/MAC/DEK, ICV, fixed level). The `new`/`index` accessors
15/// are backend-facing.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct Scp02Session(u16);
18
19impl Scp02Session {
20 /// Construct from a backend session-slot index.
21 #[must_use]
22 pub const fn new(index: u16) -> Self {
23 Self(index)
24 }
25 /// The backend session-slot index.
26 #[must_use]
27 pub const fn index(self) -> u16 {
28 self.0
29 }
30}
31
32/// SCP02 crypto flow. The INITIALIZE UPDATE response carries a 2-byte sequence
33/// counter and a 6-byte card challenge; the card/host cryptograms (GPCS v2.3.1
34/// Appendix E.4.4) are computed over the **8-byte card challenge** =
35/// `sequence_counter(2) ‖ card_challenge(6)`, which is what the two cryptogram
36/// methods below receive in `card_ch`.
37pub trait Scp02Backend: KeyBackend {
38 /// Derive the SCP02 session from the base keys and `seq_counter`,
39 /// returning an opaque [`Scp02Session`].
40 ///
41 /// # Errors
42 /// Returns [`BackendError::Crypto`] if key derivation fails, or
43 /// [`BackendError::KeyGen`] if no session slot is free.
44 fn scp02_derive_session(
45 &self,
46 base_enc: &KeyHandle,
47 base_mac: &KeyHandle,
48 base_dek: &KeyHandle,
49 seq_counter: [u8; 2],
50 ) -> Result<Scp02Session, BackendError>;
51
52 /// Compute the expected card cryptogram for verification. `card_ch` is the
53 /// 8-byte `sequence_counter(2) ‖ card_challenge(6)` (GPCS v2.3.1 §E.4.4).
54 ///
55 /// # Errors
56 /// Returns [`BackendError::Crypto`] if the session is invalid or the MAC
57 /// computation fails.
58 fn scp02_card_cryptogram(
59 &self,
60 s: &Scp02Session,
61 host_ch: &[u8; 8],
62 card_ch: &[u8; 8],
63 ) -> Result<[u8; 8], BackendError>;
64
65 /// Compute the host cryptogram for EXTERNAL AUTHENTICATE. `card_ch` is the
66 /// 8-byte `sequence_counter(2) ‖ card_challenge(6)` (GPCS v2.3.1 §E.4.4).
67 ///
68 /// # Errors
69 /// Returns [`BackendError::Crypto`] if the session is invalid or the MAC
70 /// computation fails.
71 fn scp02_host_cryptogram(
72 &self,
73 s: &Scp02Session,
74 host_ch: &[u8; 8],
75 card_ch: &[u8; 8],
76 ) -> Result<[u8; 8], BackendError>;
77
78 /// Apply the session security level (C-MAC, optional C-ENC) to `capdu`.
79 ///
80 /// # Errors
81 /// Returns [`BackendError::Crypto`] if the session is invalid or wrapping
82 /// fails.
83 fn scp02_wrap_command(
84 &self,
85 s: &mut Scp02Session,
86 capdu: &[u8],
87 ) -> Result<Vec<u8, CAPDU_MAX>, BackendError>;
88
89 // R-MAC verify/strip (level 0x13). Closes the v0.5 unwrap gap (§4.2).
90 /// Verify and strip the R-MAC (level `0x13`) from `rapdu`.
91 ///
92 /// # Errors
93 /// Returns [`BackendError::Crypto`] if R-MAC verification fails or the
94 /// session is invalid.
95 fn scp02_unwrap_response(
96 &self,
97 s: &mut Scp02Session,
98 rapdu: &[u8],
99 ) -> Result<Vec<u8, RAPDU_MAX>, BackendError>;
100
101 /// Encrypt `new_key` for a PUT KEY payload under **session `s`'s own
102 /// derived DEK** (`S-DEK`, GPCS v2.3.1 §E.4.1) rather than a
103 /// caller-supplied static key.
104 ///
105 /// SCP02 PUT KEY over a direct channel against the target SD must encrypt
106 /// the new key material under the session DEK derived at channel open
107 /// (from the base DEK + the sequence counter read from INITIALIZE UPDATE),
108 /// not the static base DEK — confirmed both by GPCS §E.4.1 and empirically
109 /// against a live NXP JCOP 4 P71 SSD, where `GlobalPlatformPro`'s own
110 /// debug trace shows it encrypting under this derived value (distinct
111 /// from the static key it authenticated with) before a PUT KEY that the
112 /// card accepts (static-DEK encryption is rejected with `6982`). The
113 /// workflow layer (`workflow::keys::put_sd_keys`) always calls this for
114 /// SCP02, so callers of `put_sd_keys` never need to
115 /// supply the (inaccessible) session DEK themselves.
116 ///
117 /// # Errors
118 /// Returns [`BackendError::Crypto`] if encryption fails, or
119 /// [`BackendError::Unsupported`] if `new_key` is not encryptable, or if
120 /// `s` is not a live session (its DEK cannot be read).
121 fn scp02_encrypt_put_key_payload_for_session(
122 &self,
123 s: &Scp02Session,
124 new_key: &KeyHandle,
125 ) -> Result<Vec<u8, ENC_KEY_BLOCK_MAX>, BackendError>;
126
127 /// Release the backend session slot held by `s`, zeroizing its session
128 /// keys, DEK and ICV. Called by the card manager on channel close so a
129 /// long-lived backend does not leak slots across many open/close cycles
130 /// (PDD §3.6 / §4.2). The default is a no-op, so stateless or stub
131 /// backends that keep no slot table need not override it.
132 fn scp02_close_session(&self, _s: Scp02Session) {}
133}