Add Put DATA parsing support

This commit is contained in:
Sosthène Guédon
2022-12-16 11:34:08 +01:00
parent ce28dcb189
commit 25bd7c1a71
4 changed files with 144 additions and 44 deletions
+41 -7
View File
@@ -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<Self, Self::Error> {
todo!();
fn try_from(data: &'data [u8]) -> Result<Self, Self::Error> {
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))
}
}
+3 -37
View File
@@ -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<Container> 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<Tag<'_>> for Container {
impl TryFrom<&[u8]> for Container {
type Error = ();
fn try_from(tag: Tag<'_>) -> Result<Self, ()> {
fn try_from(tag: &[u8]) -> Result<Self, ()> {
use Container::*;
Ok(match tag.0 {
Ok(match tag {
hex!("5FC107") => CardCapabilityContainer,
hex!("5FC102") => CardHolderUniqueIdentifier,
hex!("5FC105") => X509CertificateFor9A,
+1
View File
@@ -24,6 +24,7 @@ mod dispatch;
pub mod piv_types;
mod reply;
pub mod state;
mod tlv;
pub use piv_types::{AsymmetricAlgorithms, Pin, Puk};
+99
View File
@@ -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())
);
}
}