Skip to main content

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}