From 25bd7c1a716d72e767904fecd3ca46602c472845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Fri, 2 Dec 2022 17:17:01 +0100 Subject: [PATCH] Add Put DATA parsing support --- src/commands.rs | 48 +++++++++++++++++++---- src/container.rs | 40 ++----------------- src/lib.rs | 1 + src/tlv.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 44 deletions(-) create mode 100644 src/tlv.rs diff --git a/src/commands.rs b/src/commands.rs index 7e79010..84f539d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -11,6 +11,8 @@ use core::convert::{TryFrom, TryInto}; // use flexiber::Decodable; use iso7816::{Instruction, Status}; +use crate::container::Container; + use crate::state::TouchPolicy; pub use crate::{ container::{ @@ -57,7 +59,7 @@ pub enum Command<'l> { /// In particular, this can also decrypt or similar. GeneralAuthenticate(GeneralAuthenticate), /// Store a data object / container. - PutData(PutData), + PutData(PutData<'l>), GenerateAsymmetric(AsymmetricKeyReference), /* Yubico commands */ @@ -111,8 +113,7 @@ impl TryFrom<&[u8]> for GetData { if tagged_slice.tag() != flexiber::Tag::application(0x1C) { return Err(Status::IncorrectDataParameter); } - let container: containers::Container = containers::Tag::new(tagged_slice.as_bytes()) - .try_into() + let container = containers::Container::try_from(tagged_slice.as_bytes()) .map_err(|_| Status::IncorrectDataParameter)?; info!("request to GetData for container {:?}", container); @@ -246,12 +247,45 @@ pub struct AuthenticateArguments<'l> { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct PutData {} +pub enum PutData<'data> { + DiscoveryObject(&'data [u8]), + BitGroupTemplate(&'data [u8]), + Any(Container, &'data [u8]), +} -impl TryFrom<&[u8]> for PutData { +impl<'data> TryFrom<&'data [u8]> for PutData<'data> { type Error = Status; - fn try_from(_data: &[u8]) -> Result { - todo!(); + fn try_from(data: &'data [u8]) -> Result { + use crate::tlv::take_do; + let (tag, inner, rem) = take_do(data).ok_or_else(|| { + warn!("Failed to parse PUT DATA: {:02x?}", data); + Status::IncorrectDataParameter + })?; + if matches!(tag, 0x7E | 0x7F61) && !rem.is_empty() { + warn!("Empty remainder expected, got: {:02x?}", rem); + } + + let container: Container = match tag { + 0x7E => return Ok(PutData::DiscoveryObject(inner)), + 0x7F61 => return Ok(PutData::BitGroupTemplate(inner)), + 0x5C => Container::try_from(inner).map_err(|_| Status::IncorrectDataParameter)?, + _ => return Err(Status::IncorrectDataParameter), + }; + + let (tag, inner, rem) = take_do(data).ok_or_else(|| { + warn!("Failed to parse PUT DATA's second field: {:02x?}", data); + Status::IncorrectDataParameter + })?; + + if !rem.is_empty() { + warn!("Empty second remainder expected, got: {:02x?}", rem); + } + + if tag != 0x53 { + warn!("Expected 0x53 tag, got: 0x{:02x?}", rem); + } + + Ok(PutData::Any(container, inner)) } } diff --git a/src/container.rs b/src/container.rs index dd2fd1b..db5faa0 100644 --- a/src/container.rs +++ b/src/container.rs @@ -74,13 +74,6 @@ macro_rules! enum_subset { pub(crate) use enum_subset; -pub struct Tag<'a>(&'a [u8]); -impl<'a> Tag<'a> { - pub fn new(slice: &'a [u8]) -> Self { - Self(slice) - } -} - /// Security condition for the use of a given key. pub enum SecurityCondition { Pin, @@ -257,33 +250,6 @@ pub enum Container { PairingCodeReferenceDataContainer, } -pub struct ContainerId(u16); - -impl From for ContainerId { - fn from(container: Container) -> Self { - use Container::*; - Self(match container { - CardCapabilityContainer => 0xDB00, - CardHolderUniqueIdentifier => 0x3000, - X509CertificateFor9A => 0x0101, - CardholderFingerprints => 0x6010, - SecurityObject => 0x9000, - CardholderFacialImage => 0x6030, - X509CertificateFor9E => 0x0500, - X509CertificateFor9C => 0x0100, - X509CertificateFor9D => 0x0102, - PrintedInformation => 0x3001, - DiscoveryObject => 0x6050, - KeyHistoryObject => 0x6060, - RetiredX509Certificate(RetiredIndex(i)) => 0x1000u16 + i as u16, - CardholderIrisImages => 0x1015, - BiometricInformationTemplatesGroupTemplate => 0x1016, - SecureMessagingCertificateSigner => 0x1017, - PairingCodeReferenceDataContainer => 0x1018, - }) - } -} - // these are just the "contact" rules, need to model "contactless" also pub enum ReadAccessRule { Always, @@ -326,11 +292,11 @@ pub enum ReadAccessRule { // } // } -impl TryFrom> for Container { +impl TryFrom<&[u8]> for Container { type Error = (); - fn try_from(tag: Tag<'_>) -> Result { + fn try_from(tag: &[u8]) -> Result { use Container::*; - Ok(match tag.0 { + Ok(match tag { hex!("5FC107") => CardCapabilityContainer, hex!("5FC102") => CardHolderUniqueIdentifier, hex!("5FC105") => X509CertificateFor9A, diff --git a/src/lib.rs b/src/lib.rs index a196852..61c4b2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ mod dispatch; pub mod piv_types; mod reply; pub mod state; +mod tlv; pub use piv_types::{AsymmetricAlgorithms, Pin, Puk}; diff --git a/src/tlv.rs b/src/tlv.rs new file mode 100644 index 0000000..110b231 --- /dev/null +++ b/src/tlv.rs @@ -0,0 +1,99 @@ +// Copyright (C) 2022 Nitrokey GmbH +// SPDX-License-Identifier: LGPL-3.0-only + +//! Utilities for dealing with TLV (Tag-Length-Value) encoded data + +pub fn get_do<'input>(tag_path: &[u16], data: &'input [u8]) -> Option<&'input [u8]> { + let mut to_ret = data; + let mut remainder = data; + for tag in tag_path { + loop { + let (cur_tag, cur_value, cur_remainder) = take_do(remainder)?; + remainder = cur_remainder; + if *tag == cur_tag { + to_ret = cur_value; + remainder = cur_value; + break; + } + } + } + Some(to_ret) +} + +/// Returns (tag, data, remainder) +pub fn take_do(data: &[u8]) -> Option<(u16, &[u8], &[u8])> { + let (tag, remainder) = take_tag(data)?; + let (len, remainder) = take_len(remainder)?; + if remainder.len() < len { + warn!("Tried to parse TLV with data length shorter that the length data"); + None + } else { + let (value, remainder) = remainder.split_at(len); + Some((tag, value, remainder)) + } +} + +// See +// https://www.emvco.com/wp-content/uploads/2017/05/EMV_v4.3_Book_3_Application_Specification_20120607062110791.pdf +// Annex B1 +fn take_tag(data: &[u8]) -> Option<(u16, &[u8])> { + let b1 = *data.first()?; + if (b1 & 0x1f) == 0x1f { + let b2 = *data.get(1)?; + + if (b2 & 0b10000000) != 0 { + // OpenPGP doesn't have any DO with a tag longer than 2 bytes + warn!("Got a tag larger than 2 bytes: {data:x?}"); + return None; + } + Some((u16::from_be_bytes([b1, b2]), &data[2..])) + } else { + Some((u16::from_be_bytes([0, b1]), &data[1..])) + } +} + +pub fn take_len(data: &[u8]) -> Option<(usize, &[u8])> { + let l1 = *data.first()?; + if l1 <= 0x7F { + Some((l1 as usize, &data[1..])) + } else if l1 == 0x81 { + Some((*data.get(1)? as usize, &data[2..])) + } else { + if l1 != 0x82 { + warn!( + "Got an unexpected length tag: {l1:x}, data: {:x?}", + &data[..3] + ); + return None; + } + let l2 = *data.get(1)?; + let l3 = *data.get(2)?; + let len = u16::from_be_bytes([l2, l3]) as usize; + Some((len as usize, &data[3..])) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex_literal::hex; + use test_log::test; + + #[test] + fn dos() { + assert_eq!( + get_do(&[0x02], &hex!("02 02 1DB9 02 02 1DB9")), + Some(hex!("1DB9").as_slice()) + ); + assert_eq!( + get_do(&[0xA6, 0x7F49, 0x86], &hex!("A6 26 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")), + Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice()) + ); + + // Multiple nested + assert_eq!( + get_do(&[0xA6, 0x7F49, 0x86], &hex!("A6 2A 02 02 DEAD 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")), + Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice()) + ); + } +}