Import TLV parsing from opcard

This commit is contained in:
Sosthène Guédon
2023-09-22 11:27:49 +02:00
committed by sosthene-nitrokey
parent 6e245bb1ad
commit e364b2a8be
2 changed files with 171 additions and 0 deletions
+1
View File
@@ -20,6 +20,7 @@ pub mod response;
pub use aid::{Aid, App};
pub use command::{Command, Instruction};
pub use response::{Response, Status};
pub mod tlv;
#[cfg(test)]
mod tests {
+170
View File
@@ -0,0 +1,170 @@
//! BER-TLV writer and parser
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct Tag([u8; 3]);
impl From<u8> for Tag {
fn from(value: u8) -> Self {
Tag([value, 0, 0])
}
}
impl From<u16> for Tag {
fn from(value: u16) -> Self {
value.to_be_bytes().into()
}
}
impl From<[u8; 1]> for Tag {
fn from([value]: [u8; 1]) -> Self {
value.into()
}
}
impl From<[u8; 2]> for Tag {
fn from([b1, b2]: [u8; 2]) -> Self {
if b1 == 0 {
Tag([b2, 0, 0])
} else {
Tag([b1, b2, 0])
}
}
}
impl From<[u8; 3]> for Tag {
fn from([b1, b2, b3]: [u8; 3]) -> Self {
if b1 == 0 {
[b2, b3].into()
} else {
Tag([b1, b2, 0])
}
}
}
impl Tag {
pub fn serialize(&self) -> heapless::Vec<u8, 3> {
let [b1, b2, b3] = self.0;
if b1 == 0 {
if b2 == 0 {
debug_assert_ne!(b3 & 0b11111, 0b11111, "Invalid encoding for 1 byte tag");
heapless::Vec::try_from([b3].as_slice()).unwrap()
} else {
debug_assert_eq!(
b3 & 0b11111,
0b11111,
"Invalid encoding for first byte of tag"
);
debug_assert!(
(0x1F..=0x7F).contains(&b3),
"Invalid encoding for first byte of tag"
);
heapless::Vec::try_from([b2, b3].as_slice()).unwrap()
}
} else {
debug_assert_eq!(
b1 & 0b11111,
0b11111,
"Invalid encoding for first byte of tag"
);
debug_assert!(b2 > 0x80);
debug_assert!((0x00..0x7F).contains(&b3));
heapless::Vec::try_from([b1, b2, b3].as_slice()).unwrap()
}
}
}
pub fn get_do<'input>(tag_path: &[Tag], 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)
fn take_do(data: &[u8]) -> Option<(Tag, &[u8], &[u8])> {
let (tag, remainder) = take_tag(data)?;
let (len, remainder) = take_len(remainder)?;
if remainder.len() < len {
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
pub fn take_tag(data: &[u8]) -> Option<(Tag, &[u8])> {
let b1 = *data.first()?;
if (b1 & 0x1f) == 0x1f {
let b2 = *data.get(1)?;
if (0x00..0x1E).contains(&b2) || b2 == 0x80 {
return None;
}
if (0x81..0xFF).contains(&b2) {
let b3 = *data.get(2)?;
if (0x81..0xFF).contains(&b3) {
return None;
}
Some((Tag([b1, b2, b3]), &data[3..]))
} else {
Some((Tag([b1, b2, 0]), &data[2..]))
}
} else {
Some((Tag([b1, 0, 0]), &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 {
return None;
}
let l2 = *data.get(1)?;
let l3 = *data.get(2)?;
let len = u16::from_be_bytes([l2, l3]) as usize;
Some((len, &data[3..]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
fn dos() {
assert_eq!(
get_do(&[0x02u16].map(Tag::from), &hex!("02 02 1DB9 02 02 1DB9")),
Some(hex!("1DB9").as_slice())
);
assert_eq!(
get_do(&[0xA6u16, 0x7F49, 0x86].map(Tag::from), &hex!("A6 26 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")),
Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice())
);
// Multiple nested
assert_eq!(
get_do(&[0xA6u16, 0x7F49, 0x86].map(Tag::from), &hex!("A6 2A 02 02 DEAD 7F49 23 86 21 04 2525252525252525252525252525252525252525252525252525252525252525")),
Some(hex!("04 2525252525252525252525252525252525252525252525252525252525252525").as_slice())
);
}
}