use aes::Aes128;
use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use crate::apdu::{Command, StatusWord, cla};
use crate::card::{Card, ShortEfId, ins};
use crate::data::{RsaPublicKey, malformed};
use crate::error::{Error, Result};
use crate::pin::Pin;
use crate::tlv::ber;
use crate::transport::Transmit;
type CbcEncryptor = cbc::Encryptor<Aes128>;
type CbcDecryptor = cbc::Decryptor<Aes128>;
const BLOCK: usize = 16;
pub const SEED_LEN: usize = 32;
const TAG_CRYPTOGRAM: u8 = 0x86;
const TAG_EXPECTED_LENGTH: u8 = 0x96;
const PADDING_INDICATOR: u8 = 0x01;
const TAG_KEY_DELIVERY: u8 = 0xA0;
const TAG_ENCRYPTION_KEY: u8 = 0x80;
#[derive(Debug)]
pub struct SecureSession<'a, T> {
card: &'a mut Card<T>,
key: [u8; BLOCK],
counter: u32,
broken: bool,
}
impl<'a, T: Transmit> SecureSession<'a, T> {
pub fn establish(
card: &'a mut Card<T>,
public_key: &RsaPublicKey,
seed: &[u8; SEED_LEN],
) -> Result<Self> {
let mut message = Vec::with_capacity(4 + SEED_LEN);
message.extend_from_slice(&[
TAG_KEY_DELIVERY,
(2 + SEED_LEN) as u8,
TAG_ENCRYPTION_KEY,
SEED_LEN as u8,
]);
message.extend_from_slice(seed);
let delivered = public_key.encrypt_oaep_sha256(&message)?;
card.call_ok(&Command::with_data(
cla::SYSTEM,
ins::SET_SESSION_KEY,
0x00,
0x00,
delivered,
))?;
let mut key = [0u8; BLOCK];
key.copy_from_slice(&seed[..BLOCK]);
Ok(SecureSession {
card,
key,
counter: 1,
broken: false,
})
}
pub fn counter(&self) -> u32 {
self.counter
}
pub fn is_broken(&self) -> bool {
self.broken
}
pub fn card(&mut self) -> &mut Card<T> {
self.card
}
pub fn verify(&mut self, key: u16, value: &Pin) -> Result<()> {
let p2 = 0x80 | ShortEfId::from_ef_id(key)?.value();
let cryptogram = self.encrypt(value.as_bytes());
self.call(&Command::with_data(
cla::SM_WITHOUT_INTEGRITY,
ins::VERIFY,
0x00,
p2,
cryptogram_object(&cryptogram),
))?;
Ok(())
}
pub fn select_ef(&mut self, id: u16) -> Result<()> {
let cryptogram = self.encrypt(&id.to_be_bytes());
self.call(&Command::with_data(
cla::SM_WITHOUT_INTEGRITY,
ins::SELECT_FILE,
0x02,
0x0C,
cryptogram_object(&cryptogram),
))?;
Ok(())
}
pub fn read_binary(&mut self, offset: u16, length: u16) -> Result<Vec<u8>> {
let expected = [
TAG_EXPECTED_LENGTH,
0x03,
0x00,
(length >> 8) as u8,
length as u8,
];
let response = self.call(&Command::with_data_le(
cla::SM_WITHOUT_INTEGRITY,
ins::READ_BINARY,
(offset >> 8) as u8,
offset as u8,
expected,
65536,
))?;
let counter = self.counter - 1;
self.decrypt(&parse_cryptogram(&response)?, counter)
}
pub fn read_ef(&mut self, id: u16) -> Result<Vec<u8>> {
let mut out = self.read_ef_physical(id)?;
if out.is_empty() {
return Ok(out);
}
match ber::parse_header(&out).map(|header| header.total_len()) {
Ok(total) if total <= out.len() => out.truncate(total),
Ok(total) => {
while out.len() < total {
let offset =
u16::try_from(out.len()).map_err(|_| Error::OffsetOutOfRange(out.len()))?;
let want = u16::try_from(total - out.len()).unwrap_or(u16::MAX);
let chunk = self.read_binary(offset, want)?;
if chunk.is_empty() {
break;
}
out.extend_from_slice(&chunk);
}
}
Err(_) => {}
}
Ok(out)
}
pub fn read_ef_physical(&mut self, id: u16) -> Result<Vec<u8>> {
self.select_ef(id)?;
self.read_binary(0, 0)
}
fn call(&mut self, command: &Command) -> Result<Vec<u8>> {
if self.broken {
return Err(Error::Status(StatusWord::new(0x69FC)));
}
let response = self.card.call(command)?;
self.counter += 1;
let sw = response.status;
if is_secure_messaging_error(sw) {
self.broken = true;
}
if sw.is_success() {
Ok(response.data)
} else {
Err(Error::from_status(sw))
}
}
fn iv(&self, n: u32) -> [u8; BLOCK] {
let mut block = [0u8; BLOCK];
block[BLOCK - 4..].copy_from_slice(&n.to_be_bytes());
let mut cipher = CbcEncryptor::new(&self.key.into(), &[0u8; BLOCK].into());
cipher.encrypt_block_mut(GenericArray::from_mut_slice(&mut block));
block
}
fn encrypt(&self, plaintext: &[u8]) -> Vec<u8> {
let iv = self.iv(self.counter);
let mut buffer = pad(plaintext);
let mut cipher = CbcEncryptor::new(&self.key.into(), &iv.into());
for block in buffer.chunks_exact_mut(BLOCK) {
cipher.encrypt_block_mut(GenericArray::from_mut_slice(block));
}
buffer
}
fn decrypt(&self, ciphertext: &[u8], n: u32) -> Result<Vec<u8>> {
if ciphertext.is_empty() || ciphertext.len() % BLOCK != 0 {
return Err(malformed(&format!(
"a cryptogram of {} bytes is not a whole number of blocks",
ciphertext.len()
)));
}
let iv = self.iv(n);
let mut buffer = ciphertext.to_vec();
let mut cipher = CbcDecryptor::new(&self.key.into(), &iv.into());
for block in buffer.chunks_exact_mut(BLOCK) {
cipher.decrypt_block_mut(GenericArray::from_mut_slice(block));
}
unpad(buffer)
}
}
fn cryptogram_object(cryptogram: &[u8]) -> Vec<u8> {
let inner = cryptogram.len() + 1;
let mut out = Vec::with_capacity(inner + 4);
out.push(TAG_CRYPTOGRAM);
if inner < 0x80 {
out.push(inner as u8);
} else if inner <= 0xFF {
out.push(0x81);
out.push(inner as u8);
} else {
out.push(0x82);
out.extend_from_slice(&(inner as u16).to_be_bytes());
}
out.push(PADDING_INDICATOR);
out.extend_from_slice(cryptogram);
out
}
fn parse_cryptogram(response: &[u8]) -> Result<Vec<u8>> {
let tlv = ber::parse(response)?;
if tlv.tag != u32::from(TAG_CRYPTOGRAM) {
return Err(malformed(&format!(
"expected a cryptogram under tag 86, got {:02X}",
tlv.tag
)));
}
match tlv.value.split_first() {
Some((&PADDING_INDICATOR, cryptogram)) => Ok(cryptogram.to_vec()),
Some((other, _)) => Err(malformed(&format!(
"padding-content indicator is {other:02X}, expected 01"
))),
None => Err(malformed("the cryptogram object is empty")),
}
}
fn pad(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len() + BLOCK);
out.extend_from_slice(data);
out.push(0x80);
while out.len() % BLOCK != 0 {
out.push(0x00);
}
out
}
fn unpad(mut data: Vec<u8>) -> Result<Vec<u8>> {
while let Some(&last) = data.last() {
match last {
0x00 => {
data.pop();
}
0x80 => {
data.pop();
return Ok(data);
}
_ => break,
}
}
Err(malformed(
"decrypted data does not end in 80 00 … padding; the message counter is probably out of \
step with the card",
))
}
fn is_secure_messaging_error(sw: StatusWord) -> bool {
matches!(sw.value(), 0x6987 | 0x6988 | 0x69FC | 0x6882)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pads_to_a_whole_block_and_always_adds_one() {
assert_eq!(pad(b"1234").len(), BLOCK);
assert_eq!(pad(b"537686677188").len(), BLOCK);
assert_eq!(pad(&[0u8; BLOCK]).len(), BLOCK * 2);
assert_eq!(pad(b"1234")[4], 0x80);
}
#[test]
fn unpad_reverses_pad() {
for message in [&b""[..], b"1234", b"537686677188", &[0xFFu8; 40][..]] {
assert_eq!(unpad(pad(message)).unwrap(), message);
}
}
#[test]
fn unpad_rejects_data_without_the_marker() {
assert!(unpad(vec![0x01; BLOCK]).is_err());
assert!(unpad(vec![0x00; BLOCK]).is_err());
}
#[test]
fn builds_the_data_object_the_card_accepts() {
let object = cryptogram_object(&[0xAA; 16]);
assert_eq!(object.len(), 0x13);
assert_eq!(&object[..3], &[0x86, 0x11, 0x01]);
}
#[test]
fn switches_to_the_long_length_form_when_needed() {
let object = cryptogram_object(&[0xAA; 127]);
assert_eq!(&object[..3], &[0x86, 0x81, 0x80]);
let object = cryptogram_object(&[0xAA; 640]);
assert_eq!(&object[..4], &[0x86, 0x82, 0x02, 0x81]);
assert_eq!(object.len(), 640 + 5);
}
#[test]
fn parses_a_response_object() {
let mut response = vec![0x86, 0x11, 0x01];
response.extend_from_slice(&[0xBB; 16]);
assert_eq!(parse_cryptogram(&response).unwrap(), vec![0xBB; 16]);
}
#[test]
fn rejects_a_response_with_the_wrong_indicator() {
let mut response = vec![0x86, 0x11, 0x02];
response.extend_from_slice(&[0xBB; 16]);
assert!(parse_cryptogram(&response).is_err());
}
#[test]
fn the_iv_depends_only_on_the_key_and_the_counter() {
let session = |key: [u8; BLOCK]| SecureSession::<crate::transport::mock::MockTransport> {
card: Box::leak(Box::new(Card::new(
crate::transport::mock::MockTransport::new([]),
))),
key,
counter: 1,
broken: false,
};
let a = session([0x11; BLOCK]);
let b = session([0x11; BLOCK]);
let c = session([0x22; BLOCK]);
assert_eq!(a.iv(1), b.iv(1));
assert_ne!(a.iv(1), a.iv(2));
assert_ne!(a.iv(1), c.iv(1));
}
}