use heapless::Vec;
use crate::backend::{KeyHandle, Scp02Backend, Scp02Session};
use crate::command::{BuildError, Capdu};
use crate::error::ScllError;
use crate::limits::RAPDU_MAX;
const INS_INITIALIZE_UPDATE: u8 = 0x50;
const INS_EXTERNAL_AUTHENTICATE: u8 = 0x82;
const SCP_ID_SCP02: u8 = 0x02;
const IU_RESPONSE_LEN: usize = 28;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IuResponse {
pub kvn: u8,
pub seq_counter: [u8; 2],
pub card_challenge: [u8; 6],
pub card_cryptogram: [u8; 8],
}
pub struct Scp02State {
session: Scp02Session,
i_param: u8,
security_level: u8,
kvn: u8,
}
impl Scp02State {
#[must_use]
pub fn session(&self) -> Scp02Session {
self.session
}
#[must_use]
pub fn kvn(&self) -> u8 {
self.kvn
}
#[must_use]
pub fn i_param(&self) -> u8 {
self.i_param
}
#[must_use]
pub fn security_level(&self) -> u8 {
self.security_level
}
pub fn wrap_command<B: Scp02Backend>(
&mut self,
backend: &B,
capdu: &[u8],
) -> Result<Capdu, ScllError> {
Ok(backend.scp02_wrap_command(&mut self.session, capdu)?)
}
pub fn unwrap_response<B: Scp02Backend>(
&mut self,
backend: &B,
rapdu: &[u8],
) -> Result<Vec<u8, RAPDU_MAX>, ScllError> {
Ok(backend.scp02_unwrap_response(&mut self.session, rapdu)?)
}
}
pub fn iu_command(kvn: u8, key_id: u8, host_challenge: &[u8; 8]) -> Result<Capdu, ScllError> {
let mut apdu = Capdu::new();
extend(&mut apdu, &[0x80, INS_INITIALIZE_UPDATE, kvn, key_id, 0x08])?;
extend(&mut apdu, host_challenge)?;
extend(&mut apdu, &[0x00])?;
Ok(apdu)
}
fn ea_plaintext(security_level: u8, host_cryptogram: [u8; 8]) -> Result<Capdu, ScllError> {
let mut apdu = Capdu::new();
extend(
&mut apdu,
&[0x84, INS_EXTERNAL_AUTHENTICATE, security_level, 0x00, 0x08],
)?;
extend(&mut apdu, &host_cryptogram)?;
Ok(apdu)
}
pub fn parse_iu_response(bytes: &[u8]) -> Result<IuResponse, ScllError> {
if bytes.len() != IU_RESPONSE_LEN {
return Err(ScllError::ScpProtocolUnsupported);
}
if bytes[11] != SCP_ID_SCP02 {
return Err(ScllError::ScpProtocolUnsupported);
}
let mut seq_counter = [0u8; 2];
let mut card_challenge = [0u8; 6];
let mut card_cryptogram = [0u8; 8];
seq_counter.copy_from_slice(&bytes[12..14]);
card_challenge.copy_from_slice(&bytes[14..20]);
card_cryptogram.copy_from_slice(&bytes[20..28]);
Ok(IuResponse {
kvn: bytes[10],
seq_counter,
card_challenge,
card_cryptogram,
})
}
pub fn cap_security_level(i_param: u8, requested: u8) -> Result<u8, ScllError> {
let mut allowed = 0x03u8;
if matches!(i_param, 0x15 | 0x55) {
allowed |= 0x10;
}
let effective = requested & allowed;
if effective == 0 {
return Err(ScllError::NoCommonSecurityLevel);
}
Ok(effective)
}
#[allow(clippy::too_many_arguments)]
pub fn begin<B: Scp02Backend>(
backend: &B,
base_enc: &KeyHandle,
base_mac: &KeyHandle,
base_dek: &KeyHandle,
i_param: u8,
kvn_expected: u8,
requested_level: u8,
host_challenge: &[u8; 8],
iu_response: &[u8],
) -> Result<(Scp02State, Capdu), ScllError> {
let iu = parse_iu_response(iu_response)?;
if kvn_expected != 0x00 && iu.kvn != kvn_expected {
return Err(ScllError::KvnMismatch);
}
let mut session = backend.scp02_derive_session(base_enc, base_mac, base_dek, iu.seq_counter)?;
let mut card_ch8 = [0u8; 8];
card_ch8[..2].copy_from_slice(&iu.seq_counter);
card_ch8[2..].copy_from_slice(&iu.card_challenge);
let expected_card = backend.scp02_card_cryptogram(&session, host_challenge, &card_ch8)?;
if !backend.ct_eq(&expected_card, &iu.card_cryptogram) {
return Err(ScllError::CardCryptogramFail);
}
let security_level = cap_security_level(i_param, requested_level)?;
let host_cryptogram = backend.scp02_host_cryptogram(&session, host_challenge, &card_ch8)?;
let ea_plain = ea_plaintext(security_level, host_cryptogram)?;
let ea_wrapped = backend.scp02_wrap_command(&mut session, &ea_plain)?;
Ok((
Scp02State {
session,
i_param,
security_level,
kvn: iu.kvn,
},
ea_wrapped,
))
}
fn extend(apdu: &mut Capdu, src: &[u8]) -> Result<(), ScllError> {
apdu.extend_from_slice(src)
.map_err(|()| ScllError::Build(BuildError::Overflow))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::{KeyBackend, KeyKind};
use crate::error::BackendError;
use crate::limits::{CAPDU_MAX, ENC_KEY_BLOCK_MAX};
use scll_test_util::HexSlice;
struct StubBackend {
card_crypto: [u8; 8],
host_crypto: [u8; 8],
}
impl KeyBackend for StubBackend {
fn import_key(&self, _k: KeyKind, _b: &[u8]) -> Result<KeyHandle, BackendError> {
Ok(KeyHandle::new(0))
}
fn generate_key(&self, _k: KeyKind) -> Result<KeyHandle, BackendError> {
Ok(KeyHandle::new(0))
}
fn compute_kcv(&self, _h: &KeyHandle) -> Result<[u8; 3], BackendError> {
Ok([0; 3])
}
fn random_bytes(&self, out: &mut [u8]) -> Result<(), BackendError> {
out.fill(0);
Ok(())
}
fn ct_eq(&self, a: &[u8], b: &[u8]) -> bool {
a == b
}
}
impl Scp02Backend for StubBackend {
fn scp02_derive_session(
&self,
_e: &KeyHandle,
_m: &KeyHandle,
_d: &KeyHandle,
_seq: [u8; 2],
) -> Result<Scp02Session, BackendError> {
Ok(Scp02Session::new(0))
}
fn scp02_card_cryptogram(
&self,
_s: &Scp02Session,
_h: &[u8; 8],
_c: &[u8; 8],
) -> Result<[u8; 8], BackendError> {
Ok(self.card_crypto)
}
fn scp02_host_cryptogram(
&self,
_s: &Scp02Session,
_h: &[u8; 8],
_c: &[u8; 8],
) -> Result<[u8; 8], BackendError> {
Ok(self.host_crypto)
}
fn scp02_wrap_command(
&self,
_s: &mut Scp02Session,
capdu: &[u8],
) -> Result<Vec<u8, CAPDU_MAX>, BackendError> {
let mut v = Vec::new();
v.extend_from_slice(capdu)
.map_err(|()| BackendError::Crypto(heapless::String::new()))?;
Ok(v)
}
fn scp02_unwrap_response(
&self,
_s: &mut Scp02Session,
rapdu: &[u8],
) -> Result<Vec<u8, RAPDU_MAX>, BackendError> {
let mut v = Vec::new();
v.extend_from_slice(rapdu)
.map_err(|()| BackendError::Crypto(heapless::String::new()))?;
Ok(v)
}
fn scp02_encrypt_put_key_payload_for_session(
&self,
_s: &Scp02Session,
_n: &KeyHandle,
) -> Result<Vec<u8, ENC_KEY_BLOCK_MAX>, BackendError> {
Ok(Vec::new())
}
}
fn iu_bytes(kvn: u8, scp: u8, seq: [u8; 2], challenge: [u8; 6], crypto: [u8; 8]) -> [u8; 28] {
let mut b = [0u8; 28];
b[10] = kvn;
b[11] = scp;
b[12..14].copy_from_slice(&seq);
b[14..20].copy_from_slice(&challenge);
b[20..28].copy_from_slice(&crypto);
b
}
#[test]
fn iu_command_bytes() {
let host = [0, 1, 2, 3, 4, 5, 6, 7];
let apdu = iu_command(0x00, 0x00, &host).unwrap();
assert_eq!(
HexSlice(&apdu),
HexSlice([0x80, 0x50, 0x00, 0x00, 0x08, 0, 1, 2, 3, 4, 5, 6, 7, 0x00])
);
}
#[test]
fn iu_command_carries_key_id_in_p2() {
let apdu = iu_command(0x20, 0x01, &[0u8; 8]).unwrap();
assert_eq!(apdu[2], 0x20); assert_eq!(apdu[3], 0x01); }
#[test]
fn parse_valid_iu() {
let b = iu_bytes(0x01, 0x02, [0x00, 0x05], [9; 6], [0xAA; 8]);
let iu = parse_iu_response(&b).unwrap();
assert_eq!(iu.kvn, 0x01);
assert_eq!(iu.seq_counter, [0x00, 0x05]);
assert_eq!(iu.card_challenge, [9; 6]);
assert_eq!(iu.card_cryptogram, [0xAA; 8]);
}
#[test]
fn parse_rejects_wrong_length() {
assert!(matches!(
parse_iu_response(&[0u8; 29]),
Err(ScllError::ScpProtocolUnsupported)
));
}
#[test]
fn parse_rejects_non_scp02() {
let b = iu_bytes(0x00, 0x03, [0; 2], [0; 6], [0; 8]); assert!(matches!(
parse_iu_response(&b),
Err(ScllError::ScpProtocolUnsupported)
));
}
#[test]
fn level_cap_table() {
assert_eq!(cap_security_level(0x55, 0x13).unwrap(), 0x13);
assert_eq!(cap_security_level(0x55, 0x03).unwrap(), 0x03);
assert_eq!(cap_security_level(0x15, 0x13).unwrap(), 0x13);
assert_eq!(cap_security_level(0x05, 0x13).unwrap(), 0x03);
assert_eq!(cap_security_level(0x55, 0x33).unwrap(), 0x13);
assert!(matches!(
cap_security_level(0x05, 0x20),
Err(ScllError::NoCommonSecurityLevel)
));
}
#[test]
fn begin_happy_path_builds_external_authenticate() {
let backend = StubBackend {
card_crypto: [0xAA; 8],
host_crypto: [0xBB; 8],
};
let enc = KeyHandle::new(0);
let mac = KeyHandle::new(1);
let dek = KeyHandle::new(2);
let host = [0u8; 8];
let iu = iu_bytes(0x00, 0x02, [0x00, 0x01], [9; 6], [0xAA; 8]); let (state, ea) = begin(&backend, &enc, &mac, &dek, 0x55, 0x00, 0x03, &host, &iu).unwrap();
assert_eq!(state.i_param(), 0x55);
assert_eq!(state.security_level(), 0x03);
assert_eq!(
HexSlice(&ea),
HexSlice([
0x84, 0x82, 0x03, 0x00, 0x08, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB
])
);
}
#[test]
fn begin_rejects_bad_card_cryptogram() {
let backend = StubBackend {
card_crypto: [0xAA; 8],
host_crypto: [0xBB; 8],
};
let iu = iu_bytes(0x00, 0x02, [0; 2], [9; 6], [0xCC; 8]); let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
&KeyHandle::new(2),
0x55,
0x00,
0x03,
&[0; 8],
&iu,
);
assert!(matches!(r, Err(ScllError::CardCryptogramFail)));
}
#[test]
fn begin_rejects_kvn_mismatch() {
let backend = StubBackend {
card_crypto: [0xAA; 8],
host_crypto: [0xBB; 8],
};
let iu = iu_bytes(0x05, 0x02, [0; 2], [9; 6], [0xAA; 8]);
let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
&KeyHandle::new(2),
0x55,
0x01,
0x03,
&[0; 8],
&iu,
);
assert!(matches!(r, Err(ScllError::KvnMismatch)));
}
#[test]
fn begin_caps_rmac_for_plain_i55() {
let backend = StubBackend {
card_crypto: [0xAA; 8],
host_crypto: [0xBB; 8],
};
let iu = iu_bytes(0x00, 0x02, [0; 2], [9; 6], [0xAA; 8]);
let (state, ea) = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
&KeyHandle::new(2),
0x55,
0x00,
0x13,
&[0; 8],
&iu,
)
.unwrap();
assert_eq!(state.security_level(), 0x13);
assert_eq!(ea[2], 0x13); }
}