use heapless::Vec;
use crate::aid::Aid;
use crate::backend::{KeyBackend, KeyHandle, Scp02Backend, Scp03Backend, ScpMode};
use crate::error::ScllError;
use crate::limits::SCP03_S16_MAX;
use crate::model::ScpVariant;
use crate::report::{OpenScpParams, OpenScpReport, ScpTargetKind};
use crate::scp::{self, scp02, scp03, ScpSession};
use crate::transport::Transport;
use crate::workflow::session::{self, SW_OK};
const SCP02_KEY_ID: u8 = 0x00;
#[derive(Debug, Clone, Copy)]
pub struct SdKeys {
pub enc: KeyHandle,
pub mac: KeyHandle,
pub dek: KeyHandle,
}
pub struct OpenScpArgs<'a> {
pub target_aid: &'a [u8],
pub target_kind: ScpTargetKind,
pub sd_keys: SdKeys,
pub advertised: &'a [ScpVariant],
pub force_scp: Option<ScpVariant>,
pub kvn: u8,
pub requested_level: u8,
}
#[allow(clippy::similar_names)] pub fn open_scp<B>(
t: &mut dyn Transport,
backend: &B,
args: &OpenScpArgs<'_>,
) -> Result<OpenScpReport, ScllError>
where
B: KeyBackend + Scp02Backend + Scp03Backend,
{
let variant =
scp::select(args.advertised, args.force_scp).ok_or(ScllError::ScpProtocolUnsupported)?;
let fci = session::select(t, args.target_aid)?;
let target_aid = Aid::new(args.target_aid)?;
let invoker_aid = session::fci_df_name(&fci)
.ok()
.flatten()
.and_then(|name| Aid::new(name).ok())
.unwrap_or_else(|| target_aid.clone());
let (session, kvn_effective, i_param_effective, security_level_effective) = match variant {
ScpVariant::Scp03 { i_param } => {
let mode = ScpMode::from_i(i_param);
let mut host_buf = [0u8; SCP03_S16_MAX];
let host_len = mode.field_len();
backend.random_bytes(&mut host_buf[..host_len])?;
let host_challenge = &host_buf[..host_len];
let iu_capdu = scp03::iu_command(args.kvn, host_challenge)?;
let (iu_data, sw) = session::transmit_plain(t, &iu_capdu)?;
if sw != SW_OK {
return Err(ScllError::from_general_sw(sw));
}
let iu = scp03::parse_iu_response(&iu_data)?;
let (state, ea) = scp03::begin(
backend,
&args.sd_keys.enc,
&args.sd_keys.mac,
args.kvn,
args.requested_level,
host_challenge,
invoker_aid.as_bytes(), &iu_data,
)?;
let level = state.security_level();
let (_d, ea_sw) = session::transmit_plain(t, &ea)?;
if ea_sw != SW_OK {
return Err(ScllError::ExternalAuthFail { sw: ea_sw });
}
(ScpSession::Scp03(state), iu.kvn, iu.i_param, level)
}
ScpVariant::Scp02 { i_param } => {
let mut host_challenge = [0u8; 8];
backend.random_bytes(&mut host_challenge)?;
let iu_capdu = scp02::iu_command(args.kvn, SCP02_KEY_ID, &host_challenge)?;
let (iu_data, sw) = session::transmit_plain(t, &iu_capdu)?;
if sw != SW_OK {
return Err(ScllError::from_general_sw(sw));
}
let iu = scp02::parse_iu_response(&iu_data)?;
let (state, ea) = scp02::begin(
backend,
&args.sd_keys.enc,
&args.sd_keys.mac,
&args.sd_keys.dek,
i_param,
args.kvn,
args.requested_level,
&host_challenge,
&iu_data,
)?;
let level = state.security_level();
let (_d, ea_sw) = session::transmit_plain(t, &ea)?;
if ea_sw != SW_OK {
return Err(ScllError::ExternalAuthFail { sw: ea_sw });
}
(ScpSession::Scp02(state), iu.kvn, i_param, level)
}
};
let session_id = session.session_id();
let scp_protocol_effective = session.protocol();
Ok(OpenScpReport {
session,
effective: OpenScpParams {
target_aid: target_aid.clone(),
target_kind: args.target_kind,
sd_aid_used_for_keys: target_aid,
scp_protocol_effective,
kvn_requested: args.kvn,
kvn_effective,
i_param_effective,
security_level_requested: args.requested_level,
security_level_effective,
session_id,
invoker_aid_used: invoker_aid,
},
warnings: Vec::new(),
})
}