use heapless::Vec;
use crate::aid::Aid;
use crate::backend::{KeyBackend, KeyHandle, KeyKind, Scp02Backend, Scp03Backend};
use crate::command::delete::delete_key;
use crate::command::put_key::{put_key, KeyBlock};
use crate::error::ScllError;
use crate::limits::ENC_KEY_BLOCK_MAX;
use crate::model::KeyType;
use crate::report::{DeleteKeyParams, DeleteKeyReport, PutKeysParams, PutKeysReport, ScpProtocol};
use crate::scp::ScpSession;
use crate::transport::Transport;
use crate::workflow::session::{self, SW_OK, SW_REF_NOT_FOUND};
const KEY_TYPE_AES: u8 = 0x88;
const KEY_TYPE_3DES: u8 = 0x80;
const P1_ADD: u8 = 0x00;
const KCV_ECHO_LEN: usize = 1 + 9;
#[derive(Debug, Clone, Copy)]
pub struct NewKeyset {
pub enc: KeyHandle,
pub mac: KeyHandle,
pub dek: KeyHandle,
pub kind: KeyKind,
}
pub struct PutSdKeysArgs<'a> {
pub dek: KeyHandle,
pub new_keys: NewKeyset,
pub new_kvn: u8,
pub target_sd_aid: &'a [u8],
}
#[allow(clippy::similar_names)] pub fn put_sd_keys<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
args: &PutSdKeysArgs<'_>,
) -> Result<PutKeysReport, ScllError>
where
B: KeyBackend + Scp02Backend + Scp03Backend,
{
let keyset = args.new_keys;
let is_scp03 = matches!(session.protocol(), ScpProtocol::Scp03);
let key_type_byte = if is_scp03 {
KEY_TYPE_AES
} else {
KEY_TYPE_3DES
};
let report_key_type = if is_scp03 { KeyType::Aes } else { KeyType::Des };
let enc_blob = encrypt(backend, session, args.dek, keyset.enc)?;
let mac_blob = encrypt(backend, session, args.dek, keyset.mac)?;
let dek_blob = encrypt(backend, session, args.dek, keyset.dek)?;
let kcv_enc = backend.compute_kcv(&keyset.enc)?;
let kcv_mac = backend.compute_kcv(&keyset.mac)?;
let kcv_dek = backend.compute_kcv(&keyset.dek)?;
let clear_len = if is_scp03 {
u8::try_from(keyset.kind.clear_len()).unwrap_or(16)
} else {
16
};
let blocks = [
key_block(key_type_byte, enc_blob.as_slice(), kcv_enc, clear_len),
key_block(key_type_byte, mac_blob.as_slice(), kcv_mac, clear_len),
key_block(key_type_byte, dek_blob.as_slice(), kcv_dek, clear_len),
];
let capdu = put_key(P1_ADD, args.new_kvn, &blocks)?;
let (echo, sw) = session::transmit_in_session(t, backend, session, &capdu)?;
if sw != SW_OK {
return Err(ScllError::from_general_sw(sw));
}
let kcvs = verify_kcv_echo(&echo, args.new_kvn, kcv_enc, kcv_mac, kcv_dek)?;
Ok(PutKeysReport {
effective: PutKeysParams {
target_sd_aid: Aid::new(args.target_sd_aid)?,
scp_protocol: session.protocol(),
new_kvn: args.new_kvn,
key_type: report_key_type,
key_length: clear_len,
kcvs,
},
warnings: Vec::new(),
})
}
pub fn delete_sd_keyset<B>(
t: &mut dyn Transport,
backend: &B,
session: &mut ScpSession,
kvn: u8,
target_sd_aid: &[u8],
) -> Result<DeleteKeyReport, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
let capdu = delete_key(None, Some(kvn))?;
let (_data, sw) = session::transmit_in_session(t, backend, session, &capdu)?;
match sw {
SW_OK => {}
SW_REF_NOT_FOUND => return Err(ScllError::KeyNotFound),
other => return Err(ScllError::from_general_sw(other)),
}
Ok(DeleteKeyReport {
effective: DeleteKeyParams {
target_sd_aid: Aid::new(target_sd_aid)?,
kvn,
},
warnings: Vec::new(),
})
}
fn key_block(key_type: u8, encrypted_key: &[u8], kcv: [u8; 3], clear_key_len: u8) -> KeyBlock<'_> {
KeyBlock {
key_type,
encrypted_key,
kcv,
clear_key_len,
}
}
#[allow(clippy::similar_names)] fn verify_kcv_echo(
echo: &[u8],
new_kvn: u8,
kcv_enc: [u8; 3],
kcv_mac: [u8; 3],
kcv_dek: [u8; 3],
) -> Result<[u8; 9], ScllError> {
let mut kcvs = [0u8; 9];
kcvs[0..3].copy_from_slice(&kcv_enc);
kcvs[3..6].copy_from_slice(&kcv_mac);
kcvs[6..9].copy_from_slice(&kcv_dek);
if echo.len() >= KCV_ECHO_LEN {
let mut expected = [0u8; KCV_ECHO_LEN];
expected[0] = new_kvn;
expected[1..].copy_from_slice(&kcvs);
if echo[..KCV_ECHO_LEN] != expected {
return Err(ScllError::KeyCheckValueMismatch);
}
}
Ok(kcvs)
}
fn encrypt<B>(
backend: &B,
session: &ScpSession,
dek: KeyHandle,
new: KeyHandle,
) -> Result<Vec<u8, ENC_KEY_BLOCK_MAX>, ScllError>
where
B: Scp02Backend + Scp03Backend,
{
match session {
ScpSession::Scp03(_) => Ok(backend.scp03_encrypt_put_key_payload(&dek, &new)?),
ScpSession::Scp02(s) => {
Ok(backend.scp02_encrypt_put_key_payload_for_session(&s.session(), &new)?)
}
}
}