use crate::card::Card;
use crate::data::CardVerifiableCertificate;
use crate::error::{Error, Result};
use crate::tlv::simple;
use crate::transport::Transmit;
pub mod ef {
pub const CARD_IDENTIFIER: u16 = 0x001E;
pub const APPLICATION_FOLDER_LIST: u16 = 0x2F10;
pub const IC_MANUFACTURER_ID: u16 = 0x2F11;
}
pub mod tag {
pub const ISSUER_IDENTIFICATION: u16 = 0x0042;
pub const CARD_IDENTIFICATION: u16 = 0x0045;
pub const CARD_RECOGNITION: u16 = 0x0066;
pub const MUNICIPALITY_CODE: u16 = 0x00F0;
pub const EXPIRY: u16 = 0x00F2;
pub const INTERMEDIATE_KEY_ID: u16 = 0x00F7;
pub const CHAIN_UPPER: u16 = 0x00F8;
pub const CHAIN_LOWER: u16 = 0x7F21;
pub const CONTACT_ATR: u16 = 0x5F51;
}
#[derive(Debug)]
pub struct MasterFile<'a, T> {
card: &'a mut Card<T>,
}
impl<'a, T: Transmit> MasterFile<'a, T> {
pub fn select(card: &'a mut Card<T>) -> Result<Self> {
card.select_df(&crate::ap::DEFAULT_DF)?;
Ok(MasterFile { card })
}
pub fn new(card: &'a mut Card<T>) -> Self {
MasterFile { card }
}
pub fn card(&mut self) -> &mut Card<T> {
self.card
}
pub fn data_object(&mut self, tag: u16) -> Result<Vec<u8>> {
self.card.get_data(tag)
}
pub fn certificate_chain(&mut self) -> Result<Vec<CardVerifiableCertificate>> {
let mut chain = Vec::new();
for tag in [tag::CHAIN_UPPER, tag::CHAIN_LOWER] {
match self.data_object(tag) {
Ok(raw) => chain.push(CardVerifiableCertificate::parse(&raw)?),
Err(Error::Status(sw)) if matches!(sw.value(), 0x6A88 | 0x6A82) => break,
Err(err) => return Err(err),
}
}
Ok(chain)
}
pub fn card_identifier(&mut self) -> Result<CardIdentifier> {
let raw = self.read_all_records(ef::CARD_IDENTIFIER)?;
CardIdentifier::parse(&raw)
}
pub fn application_folders(&mut self) -> Result<ApplicationFolders> {
let raw = self.read_all_records(ef::APPLICATION_FOLDER_LIST)?;
ApplicationFolders::parse(&raw)
}
pub fn ic_manufacturer_id(&mut self) -> Result<IcManufacturerId> {
let raw = self.read_all_records(ef::IC_MANUFACTURER_ID)?;
IcManufacturerId::parse(&raw)
}
pub fn read_all_records(&mut self, id: u16) -> Result<Vec<u8>> {
self.card.select_ef(id)?;
match self.card.read_records_from(1) {
Ok(data) => Ok(data),
Err(Error::Status(sw)) if sw.value() == 0x6A81 => self.read_records_one_by_one(),
Err(err) => Err(err),
}
}
fn read_records_one_by_one(&mut self) -> Result<Vec<u8>> {
let mut out = Vec::new();
for record in 1..=u8::MAX {
match self.card.read_record(record) {
Ok(data) if data.is_empty() => break,
Ok(data) => out.extend_from_slice(&data),
Err(Error::Status(sw)) if sw.value() == 0x6A83 => break,
Err(err) => return Err(err),
}
}
Ok(out)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CardIdentifier {
pub manufacturer: u8,
pub algorithms: Algorithms,
pub version: SpecVersion,
pub optional_functions: Option<OptionalFunctions>,
pub proprietary: Option<Vec<u8>>,
}
impl CardIdentifier {
pub const TAG_MANUFACTURER: u8 = 0x00;
pub const TAG_OPTIONAL_FUNCTIONS: u8 = 0x01;
pub const TAG_PROPRIETARY: u8 = 0x02;
pub fn parse(records: &[u8]) -> Result<Self> {
let manufacturer_record = simple::find(records, Self::TAG_MANUFACTURER)?
.ok_or_else(|| malformed("card identifier has no manufacturer record (tag 00)"))?;
let [manufacturer, algorithms, version] = <[u8; 3]>::try_from(manufacturer_record)
.map_err(|_| {
malformed(&format!(
"manufacturer record must be 3 bytes, got {}",
manufacturer_record.len()
))
})?;
let optional_functions = simple::find(records, Self::TAG_OPTIONAL_FUNCTIONS)?
.and_then(|v| v.first().copied())
.map(OptionalFunctions);
Ok(CardIdentifier {
manufacturer,
algorithms: Algorithms(algorithms),
version: SpecVersion(version),
optional_functions,
proprietary: simple::find(records, Self::TAG_PROPRIETARY)?.map(<[u8]>::to_vec),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Algorithms(pub u8);
impl Algorithms {
pub const fn des(self) -> bool {
self.0 & 0x01 != 0
}
pub const fn rsa(self) -> bool {
self.0 & 0x02 != 0
}
pub const fn feal(self) -> bool {
self.0 & 0x04 != 0
}
pub const fn triple_des(self) -> bool {
self.0 & 0x08 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OptionalFunctions(pub u8);
impl OptionalFunctions {
pub const fn delete_df(self) -> bool {
self.0 & 0x01 != 0
}
pub const fn check_ief_creation(self) -> bool {
self.0 & 0x02 != 0
}
pub const fn unused_df_memory_size_check(self) -> bool {
self.0 & 0x04 != 0
}
pub const fn secure_messaging_confidentiality(self) -> bool {
self.0 & 0x08 != 0
}
pub const fn secure_messaging_integrity(self) -> bool {
self.0 & 0x10 != 0
}
pub const fn secure_messaging_both(self) -> bool {
self.0 & 0x20 != 0
}
pub const fn ecb_mode(self) -> bool {
self.0 & 0x40 != 0
}
pub const fn cbc_mode(self) -> bool {
self.0 & 0x80 != 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpecVersion(pub u8);
impl SpecVersion {
pub const fn name(self) -> Option<&'static str> {
match self.0 {
0x01 => Some("1.0"),
0x02 => Some("1.1"),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ApplicationFolders {
pub own_name: Vec<u8>,
pub children: Vec<Vec<u8>>,
}
impl ApplicationFolders {
pub const TAG_SELF: u8 = 0x01;
pub const TAG_CHILD: u8 = 0x02;
pub const TAG_INVALID: u8 = 0xFE;
pub fn parse(records: &[u8]) -> Result<Self> {
let mut folders = ApplicationFolders::default();
for tlv in simple::iter(records) {
let tlv = tlv?;
match tlv.tag {
Self::TAG_SELF => folders.own_name = tlv.value.to_vec(),
Self::TAG_CHILD => folders.children.push(tlv.value.to_vec()),
Self::TAG_INVALID => {}
simple::TAG_UNUSED => {}
other => {
return Err(malformed(&format!(
"unexpected tag {other:02X} in an application folder list"
)));
}
}
}
Ok(folders)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IcManufacturerId {
pub embedder: [u8; 5],
pub ic_manufacturer: u8,
pub ic_type: u16,
}
impl IcManufacturerId {
pub const TAG_EMBEDDER: u8 = 0x45;
pub const TAG_MANUFACTURER: u8 = 0x46;
pub fn parse(records: &[u8]) -> Result<Self> {
let embedder = simple::find(records, Self::TAG_EMBEDDER)?
.ok_or_else(|| malformed("no embedder record (tag 45)"))?;
let embedder = <[u8; 5]>::try_from(embedder).map_err(|_| {
malformed(&format!(
"embedder record must be 5 bytes, got {}",
embedder.len()
))
})?;
let manufacturer = simple::find(records, Self::TAG_MANUFACTURER)?
.ok_or_else(|| malformed("no IC manufacturer record (tag 46)"))?;
let [ic_manufacturer, type_hi, type_lo] =
<[u8; 3]>::try_from(manufacturer).map_err(|_| {
malformed(&format!(
"IC manufacturer record must be 3 bytes, got {}",
manufacturer.len()
))
})?;
Ok(IcManufacturerId {
embedder,
ic_manufacturer,
ic_type: u16::from_be_bytes([type_hi, type_lo]),
})
}
pub fn country(&self) -> Option<&str> {
std::str::from_utf8(&self.embedder[..2]).ok()
}
}
fn malformed(what: &str) -> Error {
Error::Malformed(what.to_owned())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transport::mock::MockTransport;
#[test]
fn parses_a_card_identifier() {
let records = [
0x00, 0x03, 0x07, 0x0A, 0x02, 0x01, 0x01, 0x05, 0x02, 0x02, 0xDE, 0xAD,
];
let id = CardIdentifier::parse(&records).unwrap();
assert_eq!(id.manufacturer, 0x07);
assert!(id.algorithms.rsa() && id.algorithms.triple_des());
assert!(!id.algorithms.des() && !id.algorithms.feal());
assert_eq!(id.version.name(), Some("1.1"));
let options = id.optional_functions.unwrap();
assert!(options.delete_df() && options.unused_df_memory_size_check());
assert!(!options.check_ief_creation() && !options.secure_messaging_confidentiality());
assert_eq!(id.proprietary.as_deref(), Some(&[0xDE, 0xAD][..]));
}
#[test]
fn card_identifier_needs_the_mandatory_record() {
assert!(CardIdentifier::parse(&[0x01, 0x01, 0x00]).is_err());
assert!(CardIdentifier::parse(&[0x00, 0x02, 0x07, 0x0A]).is_err());
}
#[test]
fn parses_an_application_folder_list() {
let records = [
0x01, 0x00, 0x02, 0x02, 0x11, 0x22, 0x02, 0x03, 0x33, 0x44, 0x55, 0xFE, 0x02, 0x00, 0x00,
];
let folders = ApplicationFolders::parse(&records).unwrap();
assert!(folders.own_name.is_empty());
assert_eq!(folders.children, [vec![0x11, 0x22], vec![0x33, 0x44, 0x55]]);
}
#[test]
fn parses_an_ic_manufacturer_id() {
let records = [
0x45, 0x05, b'J', b'P', b'0', b'7', b' ', 0x46, 0x03, 0x07, 0x12, 0x34,
];
let id = IcManufacturerId::parse(&records).unwrap();
assert_eq!(id.country(), Some("JP"));
assert_eq!(id.ic_manufacturer, 0x07);
assert_eq!(id.ic_type, 0x1234);
}
#[test]
fn get_data_falls_back_to_an_extended_le() {
let big = vec![0xAA; 300];
let mut card = Card::new(MockTransport::new([
vec![0x67, 0x00],
[big.clone(), vec![0x90, 0x00]].concat(),
]));
let mut mf = MasterFile::new(&mut card);
assert_eq!(mf.data_object(tag::CHAIN_UPPER).unwrap(), big);
assert_eq!(
mf.card().transport().sent,
vec![
vec![0x00, 0xCA, 0x00, 0xF8, 0x00],
vec![0x00, 0xCA, 0x00, 0xF8, 0x00, 0x00, 0x00],
]
);
}
#[test]
fn selects_the_default_issuer_security_domain() {
let mut card = Card::new(MockTransport::new([vec![0x90, 0x00]]));
let mut mf = MasterFile::select(&mut card).unwrap();
assert_eq!(
mf.card().transport().sent,
[vec![
0x00, 0xA4, 0x04, 0x0C, 0x07, 0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00,
]]
);
}
#[test]
fn get_data_uses_one_apdu_when_the_object_is_small() {
let mut card = Card::new(MockTransport::new([vec![
b'1', b'3', b'2', b'2', b'1', 0x90, 0x00,
]]));
let mut mf = MasterFile::new(&mut card);
assert_eq!(mf.data_object(tag::MUNICIPALITY_CODE).unwrap(), b"13221");
assert_eq!(mf.card().transport().sent.len(), 1);
}
#[test]
fn a_missing_second_certificate_ends_the_chain_rather_than_failing() {
let cert = std::fs::read(format!(
"{}/tests/fixtures/mf-do-F8.bin",
env!("CARGO_MANIFEST_DIR")
))
.unwrap();
let mut card = Card::new(MockTransport::new([
[cert.clone(), vec![0x90, 0x00]].concat(),
vec![0x6A, 0x88],
]));
let chain = MasterFile::new(&mut card).certificate_chain().unwrap();
assert_eq!(chain.len(), 1);
assert_eq!(chain[0].issuer_key_id.number(), "6000020");
}
#[test]
fn reads_records_one_at_a_time_when_the_card_rejects_the_multi_record_form() {
let mut card = Card::new(MockTransport::new([
vec![0x90, 0x00], vec![0x6A, 0x81], vec![0x00, 0x03, 0x07, 0x0A, 0x02, 0x90, 0x00], vec![0x6A, 0x83], ]));
let mut mf = MasterFile::new(&mut card);
let id = mf.card_identifier().unwrap();
assert_eq!(id.manufacturer, 0x07);
assert_eq!(
card.transport().sent[0],
[0x00, 0xA4, 0x02, 0x0C, 0x02, 0x00, 0x1E]
);
assert_eq!(card.transport().sent[1], [0x00, 0xB2, 0x01, 0x05, 0x00]);
assert_eq!(card.transport().sent[2], [0x00, 0xB2, 0x01, 0x04, 0x00]);
}
}