use heapless::Vec;
use crate::backend::{KeyHandle, Scp03Backend, Scp03Session, ScpMode};
use crate::command::{BuildError, Capdu};
use crate::error::ScllError;
use crate::limits::{RAPDU_MAX, SCP03_S16_MAX};
const INS_INITIALIZE_UPDATE: u8 = 0x50;
const INS_EXTERNAL_AUTHENTICATE: u8 = 0x82;
const SCP_ID_SCP03: u8 = 0x03;
const IU_PREFIX_LEN: usize = 13;
const IU_I_OFFSET: usize = 12;
const SEQ_COUNTER_LEN: usize = 3;
const I_S16: u8 = 0x08;
const I_PSEUDO_RANDOM: u8 = 0x10;
const I_RMAC: u8 = 0x20;
const I_RENC: u8 = 0x40;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IuResponse {
pub kvn: u8,
pub i_param: u8,
pub mode: ScpMode,
pub card_challenge: Vec<u8, SCP03_S16_MAX>,
pub card_cryptogram: Vec<u8, SCP03_S16_MAX>,
pub sequence_counter: Option<[u8; SEQ_COUNTER_LEN]>,
}
pub struct Scp03State {
session: Scp03Session,
i_param: u8,
security_level: u8,
kvn: u8,
}
impl Scp03State {
#[must_use]
pub fn session(&self) -> Scp03Session {
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: Scp03Backend>(
&mut self,
backend: &B,
capdu: &[u8],
) -> Result<Capdu, ScllError> {
Ok(backend.scp03_wrap_command(&mut self.session, capdu)?)
}
pub fn unwrap_response<B: Scp03Backend>(
&mut self,
backend: &B,
rapdu: &[u8],
) -> Result<Vec<u8, RAPDU_MAX>, ScllError> {
Ok(backend.scp03_unwrap_response(&mut self.session, rapdu)?)
}
}
pub fn iu_command(kvn: u8, host_challenge: &[u8]) -> Result<Capdu, ScllError> {
let lc =
u8::try_from(host_challenge.len()).map_err(|_| ScllError::Build(BuildError::Overflow))?;
let mut apdu = Capdu::new();
extend(&mut apdu, &[0x80, INS_INITIALIZE_UPDATE, kvn, 0x00, lc])?;
extend(&mut apdu, host_challenge)?;
extend(&mut apdu, &[0x00])?;
Ok(apdu)
}
fn ea_plaintext(security_level: u8, host_cryptogram: &[u8]) -> Result<Capdu, ScllError> {
let lc =
u8::try_from(host_cryptogram.len()).map_err(|_| ScllError::Build(BuildError::Overflow))?;
let mut apdu = Capdu::new();
extend(
&mut apdu,
&[0x84, INS_EXTERNAL_AUTHENTICATE, security_level, 0x00, lc],
)?;
extend(&mut apdu, host_cryptogram)?;
Ok(apdu)
}
#[must_use]
pub fn i_supported(i_param: u8) -> bool {
if i_param & !(I_S16 | I_PSEUDO_RANDOM | I_RMAC | I_RENC) != 0 {
return false;
}
!(i_param & I_RENC != 0 && i_param & I_RMAC == 0)
}
pub fn parse_iu_response(bytes: &[u8]) -> Result<IuResponse, ScllError> {
if bytes.len() < IU_PREFIX_LEN || bytes[11] != SCP_ID_SCP03 {
return Err(ScllError::ScpProtocolUnsupported);
}
let i_param = bytes[IU_I_OFFSET];
if !i_supported(i_param) {
return Err(ScllError::NoCommonSecurityLevel);
}
let mode = ScpMode::from_i(i_param);
let field = mode.field_len();
let pseudo = i_param & I_PSEUDO_RANDOM != 0;
let expected = IU_PREFIX_LEN + 2 * field + if pseudo { SEQ_COUNTER_LEN } else { 0 };
if bytes.len() != expected {
return Err(ScllError::ScpProtocolUnsupported);
}
let chal_start = IU_PREFIX_LEN; let crypto_start = chal_start + field;
let seq_start = crypto_start + field;
let mut card_challenge: Vec<u8, SCP03_S16_MAX> = Vec::new();
let mut card_cryptogram: Vec<u8, SCP03_S16_MAX> = Vec::new();
card_challenge
.extend_from_slice(&bytes[chal_start..crypto_start])
.map_err(|()| ScllError::ScpProtocolUnsupported)?;
card_cryptogram
.extend_from_slice(&bytes[crypto_start..seq_start])
.map_err(|()| ScllError::ScpProtocolUnsupported)?;
let sequence_counter = if pseudo {
let mut sc = [0u8; SEQ_COUNTER_LEN];
sc.copy_from_slice(&bytes[seq_start..seq_start + SEQ_COUNTER_LEN]);
Some(sc)
} else {
None
};
Ok(IuResponse {
kvn: bytes[10],
i_param,
mode,
card_challenge,
card_cryptogram,
sequence_counter,
})
}
pub fn cap_security_level(i_param: u8, requested: u8) -> Result<u8, ScllError> {
let mut allowed = 0x03u8;
if i_param & I_RMAC != 0 {
allowed |= 0x10;
}
if i_param & I_RENC != 0 {
allowed |= 0x20;
}
let effective = requested & allowed;
if effective == 0 {
return Err(ScllError::NoCommonSecurityLevel);
}
Ok(effective)
}
pub fn begin<B: Scp03Backend>(
backend: &B,
static_enc: &KeyHandle,
static_mac: &KeyHandle,
kvn_expected: u8,
requested_level: u8,
host_challenge: &[u8],
invoker_aid: &[u8],
iu_response: &[u8],
) -> Result<(Scp03State, 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.scp03_derive_session(
static_enc,
static_mac,
iu.mode,
host_challenge,
&iu.card_challenge,
)?;
let expected_card =
backend.scp03_card_cryptogram(&session, host_challenge, &iu.card_challenge)?;
if !backend.ct_eq(&expected_card, &iu.card_cryptogram) {
return Err(ScllError::CardCryptogramFail);
}
if let Some(seq) = iu.sequence_counter {
let expected_challenge =
backend.scp03_pseudo_card_challenge(static_enc, iu.mode, &seq, invoker_aid)?;
if !backend.ct_eq(&expected_challenge, &iu.card_challenge) {
return Err(ScllError::CardChallengeFail);
}
}
let security_level = cap_security_level(iu.i_param, requested_level)?;
let host_cryptogram =
backend.scp03_host_cryptogram(&session, host_challenge, &iu.card_challenge)?;
let ea_plain = ea_plaintext(security_level, &host_cryptogram)?;
let ea_wrapped = backend.scp03_wrap_command(&mut session, &ea_plain)?;
Ok((
Scp03State {
session,
i_param: iu.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;
fn v(bytes: &[u8]) -> Vec<u8, SCP03_S16_MAX> {
let mut out: Vec<u8, SCP03_S16_MAX> = Vec::new();
out.extend_from_slice(bytes).unwrap();
out
}
struct StubBackend {
card_crypto: Vec<u8, SCP03_S16_MAX>,
host_crypto: Vec<u8, SCP03_S16_MAX>,
pseudo_challenge: Vec<u8, SCP03_S16_MAX>,
}
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 Scp03Backend for StubBackend {
fn scp03_derive_session(
&self,
_e: &KeyHandle,
_m: &KeyHandle,
_mode: ScpMode,
_h: &[u8],
_c: &[u8],
) -> Result<Scp03Session, BackendError> {
Ok(Scp03Session::new(0))
}
fn scp03_card_cryptogram(
&self,
_s: &Scp03Session,
_h: &[u8],
_c: &[u8],
) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError> {
Ok(self.card_crypto.clone())
}
fn scp03_host_cryptogram(
&self,
_s: &Scp03Session,
_h: &[u8],
_c: &[u8],
) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError> {
Ok(self.host_crypto.clone())
}
fn scp03_pseudo_card_challenge(
&self,
_e: &KeyHandle,
_mode: ScpMode,
_seq: &[u8; 3],
_aid: &[u8],
) -> Result<Vec<u8, SCP03_S16_MAX>, BackendError> {
Ok(self.pseudo_challenge.clone())
}
fn scp03_wrap_command(
&self,
_s: &mut Scp03Session,
capdu: &[u8],
) -> Result<Vec<u8, CAPDU_MAX>, BackendError> {
let mut out = Vec::new();
out.extend_from_slice(capdu)
.map_err(|()| BackendError::Crypto(heapless::String::new()))?;
Ok(out)
}
fn scp03_unwrap_response(
&self,
_s: &mut Scp03Session,
rapdu: &[u8],
) -> Result<Vec<u8, RAPDU_MAX>, BackendError> {
let mut out = Vec::new();
out.extend_from_slice(rapdu)
.map_err(|()| BackendError::Crypto(heapless::String::new()))?;
Ok(out)
}
fn scp03_encrypt_put_key_payload(
&self,
_d: &KeyHandle,
_n: &KeyHandle,
) -> Result<Vec<u8, ENC_KEY_BLOCK_MAX>, BackendError> {
Ok(Vec::new())
}
}
fn iu_bytes(kvn: u8, scp: u8, i: u8, challenge: &[u8], crypto: &[u8]) -> Vec<u8, 48> {
let mut b: Vec<u8, 48> = Vec::new();
let _ = b.extend_from_slice(&[0u8; 10]); let _ = b.push(kvn);
let _ = b.push(scp);
let _ = b.push(i);
let _ = b.extend_from_slice(challenge);
let _ = b.extend_from_slice(crypto);
if i & I_PSEUDO_RANDOM != 0 {
let _ = b.extend_from_slice(&[0xAB, 0xCD, 0xEF]); }
b
}
#[test]
fn iu_command_bytes_s8() {
let host = [0, 1, 2, 3, 4, 5, 6, 7];
let apdu = iu_command(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_bytes_s16() {
let host = [0u8; 16];
let apdu = iu_command(0x00, &host).unwrap();
assert_eq!(apdu[4], 0x10);
assert_eq!(apdu.len(), 5 + 16 + 1);
}
#[test]
fn parse_valid_iu_random_s8() {
let b = iu_bytes(0x01, 0x03, 0x60, &[9; 8], &[0xAA; 8]);
assert_eq!(b.len(), 29);
let iu = parse_iu_response(&b).unwrap();
assert_eq!(iu.kvn, 0x01);
assert_eq!(iu.i_param, 0x60);
assert_eq!(iu.mode, ScpMode::S8);
assert_eq!(&iu.card_challenge[..], &[9u8; 8]);
assert_eq!(&iu.card_cryptogram[..], &[0xAAu8; 8]);
assert_eq!(iu.sequence_counter, None);
}
#[test]
fn parse_valid_iu_pseudo_random_s8() {
let b = iu_bytes(0x01, 0x03, 0x70, &[9; 8], &[0xAA; 8]);
assert_eq!(b.len(), 32);
let iu = parse_iu_response(&b).unwrap();
assert_eq!(iu.mode, ScpMode::S8);
assert_eq!(iu.sequence_counter, Some([0xAB, 0xCD, 0xEF]));
}
#[test]
fn parse_valid_iu_random_s16() {
let b = iu_bytes(0x01, 0x03, 0x68, &[9; 16], &[0xAA; 16]);
assert_eq!(b.len(), 45);
let iu = parse_iu_response(&b).unwrap();
assert_eq!(iu.i_param, 0x68);
assert_eq!(iu.mode, ScpMode::S16);
assert_eq!(&iu.card_challenge[..], &[9u8; 16]);
assert_eq!(&iu.card_cryptogram[..], &[0xAAu8; 16]);
assert_eq!(iu.sequence_counter, None);
}
#[test]
fn parse_valid_iu_pseudo_random_s16() {
let b = iu_bytes(0x01, 0x03, 0x78, &[9; 16], &[0xAA; 16]);
assert_eq!(b.len(), 48);
let iu = parse_iu_response(&b).unwrap();
assert_eq!(iu.mode, ScpMode::S16);
assert_eq!(&iu.card_challenge[..], &[9u8; 16]);
assert_eq!(iu.sequence_counter, Some([0xAB, 0xCD, 0xEF]));
}
#[test]
fn parse_rejects_wrong_length() {
assert!(matches!(
parse_iu_response(&[0u8; 12]),
Err(ScllError::ScpProtocolUnsupported)
));
let mut wrong = iu_bytes(0x00, 0x03, 0x60, &[0; 8], &[0; 8]);
let _ = wrong.push(0x00);
assert!(matches!(
parse_iu_response(&wrong),
Err(ScllError::ScpProtocolUnsupported)
));
}
#[test]
fn parse_rejects_seq_counter_mismatch() {
let mut short = iu_bytes(0x00, 0x03, 0x70, &[9; 8], &[0xAA; 8]);
short.truncate(29);
assert!(matches!(
parse_iu_response(&short),
Err(ScllError::ScpProtocolUnsupported)
));
let mut long = iu_bytes(0x00, 0x03, 0x60, &[9; 8], &[0xAA; 8]);
let _ = long.extend_from_slice(&[0, 0, 0]);
assert!(matches!(
parse_iu_response(&long),
Err(ScllError::ScpProtocolUnsupported)
));
}
#[test]
fn parse_rejects_non_scp03() {
let b = iu_bytes(0x00, 0x02, 0x70, &[0; 8], &[0; 8]); assert!(matches!(
parse_iu_response(&b),
Err(ScllError::ScpProtocolUnsupported)
));
}
#[test]
fn parse_rejects_out_of_scope_i() {
let b = iu_bytes(0x00, 0x03, 0x04, &[0; 8], &[0; 8]);
assert!(matches!(
parse_iu_response(&b),
Err(ScllError::NoCommonSecurityLevel)
));
}
#[test]
fn i_supported_table() {
for i in [
0x00, 0x10, 0x20, 0x30, 0x60, 0x70, 0x08, 0x18, 0x28, 0x38, 0x68, 0x78, ] {
assert!(i_supported(i), "{i:#04x} should be supported");
}
for i in [0x40, 0x48, 0x50, 0x04, 0x02, 0x01] {
assert!(!i_supported(i), "{i:#04x} should be unsupported");
}
}
#[test]
fn level_cap_table() {
assert_eq!(cap_security_level(0x70, 0x33).unwrap(), 0x33);
assert_eq!(cap_security_level(0x30, 0x33).unwrap(), 0x13);
assert_eq!(cap_security_level(0x10, 0x33).unwrap(), 0x03);
assert_eq!(cap_security_level(0x78, 0x33).unwrap(), 0x33);
assert!(matches!(
cap_security_level(0x10, 0x20),
Err(ScllError::NoCommonSecurityLevel)
));
}
#[test]
fn begin_happy_path_s8_pseudo_random() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[9; 8]),
};
let enc = KeyHandle::new(0);
let mac = KeyHandle::new(1);
let host = [0u8; 8];
let aid = [0xA0u8; 8];
let iu = iu_bytes(0x00, 0x03, 0x70, &[9; 8], &[0xAA; 8]);
let (state, ea) = begin(&backend, &enc, &mac, 0x00, 0x33, &host, &aid, &iu).unwrap();
assert_eq!(state.i_param(), 0x70);
assert_eq!(state.security_level(), 0x33);
assert_eq!(
HexSlice(&ea),
HexSlice([
0x84, 0x82, 0x33, 0x00, 0x08, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB
])
);
}
#[test]
fn begin_happy_path_s16() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 16]),
host_crypto: v(&[0xBB; 16]),
pseudo_challenge: v(&[9; 16]),
};
let host = [0u8; 16];
let aid = [0xA0u8; 8];
let iu = iu_bytes(0x00, 0x03, 0x78, &[9; 16], &[0xAA; 16]);
let (state, ea) = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x00,
0x33,
&host,
&aid,
&iu,
)
.unwrap();
assert_eq!(state.i_param(), 0x78);
assert_eq!(state.security_level(), 0x33);
assert_eq!(ea[4], 0x10);
assert_eq!(&ea[5..], &[0xBBu8; 16]);
}
#[test]
fn begin_random_skips_challenge_check() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[0xFF; 8]), };
let iu = iu_bytes(0x00, 0x03, 0x60, &[9; 8], &[0xAA; 8]);
let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x00,
0x33,
&[0u8; 8],
&[0xA0u8; 8],
&iu,
);
assert!(r.is_ok());
}
#[test]
fn begin_rejects_bad_pseudo_challenge() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[0xFF; 8]), };
let iu = iu_bytes(0x00, 0x03, 0x70, &[9; 8], &[0xAA; 8]);
let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x00,
0x33,
&[0u8; 8],
&[0xA0u8; 8],
&iu,
);
assert!(matches!(r, Err(ScllError::CardChallengeFail)));
}
#[test]
fn begin_rejects_bad_card_cryptogram() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[9; 8]),
};
let iu = iu_bytes(0x00, 0x03, 0x70, &[9; 8], &[0xCC; 8]); let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x00,
0x33,
&[0u8; 8],
&[0xA0u8; 8],
&iu,
);
assert!(matches!(r, Err(ScllError::CardCryptogramFail)));
}
#[test]
fn begin_rejects_kvn_mismatch() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[9; 8]),
};
let iu = iu_bytes(0x05, 0x03, 0x70, &[9; 8], &[0xAA; 8]);
let r = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x01,
0x33,
&[0u8; 8],
&[0xA0u8; 8],
&iu,
);
assert!(matches!(r, Err(ScllError::KvnMismatch)));
}
#[test]
fn begin_caps_level_for_i30() {
let backend = StubBackend {
card_crypto: v(&[0xAA; 8]),
host_crypto: v(&[0xBB; 8]),
pseudo_challenge: v(&[9; 8]),
};
let iu = iu_bytes(0x00, 0x03, 0x30, &[9; 8], &[0xAA; 8]);
let (state, ea) = begin(
&backend,
&KeyHandle::new(0),
&KeyHandle::new(1),
0x00,
0x33,
&[0u8; 8],
&[0xA0u8; 8],
&iu,
)
.unwrap();
assert_eq!(state.security_level(), 0x13);
assert_eq!(ea[2], 0x13); }
}