iso7816-tlv-0.4.4/.cargo_vcs_info.json0000644000000001360000000000100127640ustar { "git": { "sha1": "392ad9231004a037abf5dabe6d6af1c2ed6ab9df" }, "path_in_vcs": "" }iso7816-tlv-0.4.4/.github/workflows/build.yml000064400000000000000000000004771046102023000170030ustar 00000000000000name: Build on: [push] jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: cargo build --verbose build-release: name: Build Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: cargo build --verbose --release iso7816-tlv-0.4.4/.github/workflows/clippy.yml000064400000000000000000000003061046102023000171730ustar 00000000000000name: Static Analysis on: [push] jobs: checks: name: Static Analysis with Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: cargo clean && cargo clippyiso7816-tlv-0.4.4/.github/workflows/test.yml000064400000000000000000000004621046102023000166550ustar 00000000000000name: Test on: [push] jobs: test: name: Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: cargo test --verbose test-relase: name: Tests Release runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - run: cargo test --verbose --releaseiso7816-tlv-0.4.4/.gitignore000064400000000000000000000000361046102023000135430ustar 00000000000000/target **/*.rs.bk Cargo.lock iso7816-tlv-0.4.4/Cargo.toml0000644000000021700000000000100107620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "iso7816-tlv" version = "0.4.4" authors = ["Julien Kowalski "] description = "tools and utilities for handling TLV data as defined in ISO/IEC 7816-4" readme = "Readme.md" keywords = [ "smartcard", "iso7816", "tlv", "no_std", ] categories = [ "data-structures", "no-std", ] license = "ISC" repository = "https://github.com/jkowalsk/iso7816-tlv" [dependencies.untrusted] version = "0.9" [dev-dependencies.hex-literal] version = "0.3" [dev-dependencies.rand_core] version = "0.6" [dev-dependencies.rand_xorshift] version = "0.3" [dev-dependencies.static-alloc] version = "0.2.1" [features] iso7816-tlv-0.4.4/Cargo.toml.orig000064400000000000000000000012041046102023000144400ustar 00000000000000[package] name = "iso7816-tlv" version = "0.4.4" authors = ["Julien Kowalski "] edition = "2018" license = "ISC" repository = "https://github.com/jkowalsk/iso7816-tlv" description = "tools and utilities for handling TLV data as defined in ISO/IEC 7816-4" keywords = ["smartcard", "iso7816", "tlv", "no_std"] categories = ["data-structures", "no-std"] readme = "Readme.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] [dependencies] untrusted = "0.9" [dev-dependencies] static-alloc = "0.2.1" rand_core = "0.6" rand_xorshift = "0.3" hex-literal="0.3" iso7816-tlv-0.4.4/LICENCE000064400000000000000000000013701046102023000125420ustar 00000000000000Copyright (C) 2019 Julien Kowalski Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.iso7816-tlv-0.4.4/Readme.md000064400000000000000000000014161046102023000132750ustar 00000000000000# iso7816-tlv ![](https://github.com/jkowalsk/iso7816-tlv/workflows/clippy/badge.svg) ![](https://github.com/jkowalsk/iso7816-tlv/workflows/build/badge.svg) ![](https://github.com/jkowalsk/iso7816-tlv/workflows/test/badge.svg) Tools and utilities for handling TLV data as defined in [ISO7816-4][iso7816-4]. This include BER-TLV data or SIMPLE-TLV data objects. ## Features Currently only generating and parsing BER-TLV or SIMPLE-TLV data. More features or functions may be added depending of needs. There is currently no real plan for needed/required features to add. ## Improvements Feel free to open issues for anything, including - API changes - remarks about code style (as i'm quite new to rust) - feature requests [iso7816-4]: https://www.iso.org/standard/54550.htmliso7816-tlv-0.4.4/src/ber/mod.rs000064400000000000000000000005521046102023000142420ustar 00000000000000//! This module provides tools and utilities for handling BER-TLV data as //! defined in [ISO7816-4][iso7816-4]. //! //! //! //! //! [iso7816-4]: https://www.iso.org/standard/54550.html // internal organization mod tag; mod tlv; mod value; // custom reexport (structs at same level for users) pub use tag::{Class, Tag}; pub use tlv::Tlv; pub use value::Value; iso7816-tlv-0.4.4/src/ber/tag.rs000064400000000000000000000345421046102023000142440ustar 00000000000000//! Tag definition and utilities for BER-TLV data as defined in [ISO7816-4] //! use core::convert::TryFrom; use core::fmt; use crate::{Result, TlvError}; use untrusted::Reader; /// Class of a BER-TLV Tag field. /// > Bits 8 and 7 of the first byte of the tag field indicate a class. /// > - The value 00 indicates a data object of the universal class. /// > - The value 01 indicates a data object of the application class. /// > - The value 10 indicates a data object of the context-specific class. /// > - The value 11 indicates a data object of the private class. #[derive(PartialEq, Clone, Debug)] pub enum Class { /// Universal class, not defined in ISO/IEC 7816 Universal, /// Application class, identification defined in [ISO7816-4] Application, /// Context-specific class, defined in ISO/IEC 7816 ContextSpecific, /// Private class, not defined in ISO/IEC 7816 Private, } impl From for Class { fn from(val: u8) -> Self { match val & Tag::CLASS_MASK { 0b0000_0000 => Self::Universal, 0b0100_0000 => Self::Application, 0b1000_0000 => Self::ContextSpecific, _ => Self::Private, } } } /// Tag for BER-TLV data as defined in [ISO7816-4]. /// /// Tags can be generated using the [`TryFrom`][TryFrom] trait /// from integer types (see [Trait Implementations](struct.Tag.html#implementations) for valid input types) /// or hex [str][str]. /// /// /// [TryFrom]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html /// [str]:https://doc.rust-lang.org/std/str/ /// /// Tags are now imported in a more permissivie way. /// Invalid tags in ISO7816 meaning can be checked, see [`Self::iso7816_compliant`] /// /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::ber::Tag; /// # use iso7816_tlv::TlvError; /// /// # fn main() -> () { /// assert!(Tag::try_from("80").is_ok()); /// assert!(Tag::try_from(8u8).is_ok()); /// assert!(Tag::try_from(8u16).is_ok()); /// assert!(Tag::try_from(8u32).is_ok()); /// assert!(Tag::try_from(8i32).is_ok()); /// assert!(Tag::try_from("7f22").is_ok()); /// /// assert!(Tag::try_from("error ").is_err()); /// assert!(Tag::try_from("7fffff01").is_err()); /// assert!(Tag::try_from("7f80ff").is_err()); /// // now more permissive interface : /// assert!(Tag::try_from("7f00").is_ok()); /// assert!(Tag::try_from("7f1e").is_ok()); /// # } /// # /// ``` #[derive(PartialEq, Clone)] pub struct Tag { raw: [u8; 3], len: usize, } impl Tag { const CLASS_MASK: u8 = 0b1100_0000; const CONSTRUCTED_MASK: u8 = 0b0010_0000; const VALUE_MASK: u8 = 0b0001_1111; const MORE_BYTES_MASK: u8 = 0b1000_0000; /// serializes the tag as byte array #[must_use] pub fn to_bytes(&self) -> &[u8] { &self.raw[self.raw.len() - self.len..] } /// length of the tag as byte array /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::ber::Tag; /// # use iso7816_tlv::TlvError; /// /// # fn main() -> Result<(), Box> { /// let tag = Tag::try_from("80")?; /// assert_eq!(1, tag.len_as_bytes()); /// /// let tag = Tag::try_from("7f22")?; /// assert_eq!(2, tag.len_as_bytes()); /// /// let tag = Tag::try_from("7f8122")?; /// assert_eq!(3, tag.len_as_bytes()); /// /// let tag = Tag::try_from("5fff22")?; /// assert_eq!(3, tag.len_as_bytes()); /// /// let tag = Tag::try_from("5f2d")?; /// assert_eq!(2, tag.len_as_bytes()); /// # Ok(()) /// # } /// # /// ``` #[must_use] pub fn len_as_bytes(&self) -> usize { self.len } /// Wether the tag is constructed or not /// > Bit 6 of the first byte of the tag field indicates an encoding. /// > - The value 0 indicates a primitive encoding of the data object, i.e., the value field is not encoded in BER - TLV . /// > - The value 1 indicates a constructed encoding of the data object, i.e., the value field is encoded in BER - TLV /// /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::ber::Tag; /// # use iso7816_tlv::TlvError; /// /// # fn main() -> Result<(), Box> { /// let valid = Tag::try_from(0b0010_0000)?; /// assert!(valid.is_constructed()); /// /// let invalid = Tag::try_from(0b0000_0001)?; /// assert!(!invalid.is_constructed()); /// # Ok(()) /// # } /// # /// ``` #[must_use] pub fn is_constructed(&self) -> bool { !matches!(self.raw[3 - self.len] & Self::CONSTRUCTED_MASK, 0) } /// Valid tags are defined as follow: /// > ISO/IEC 7816 supports tag fields of one, two and three bytes; /// > longer tag fields are reserved for future use /// /// > In tag fields of two or more bytes, /// > the values '00' to '1E' and '80' are invalid for the second byte. /// > - In two-byte tag fields, the second byte consists of bit 8 set to 0 and bits 7 to 1 /// > encoding a number greater than thirty. /// > The second byte is valued from '1F' to '7F; the tag number is from 31 to 127. /// > - In three-byte tag fields, the second byte consists of bit 8 set to 1 and bits 7 to 1 /// > not all set to 0; /// > the third byte consists of bit 8 set to 0 and bits 7 to 1 with any value. /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::ber::Tag; /// # use iso7816_tlv::TlvError; /// /// # fn main() -> Result<(), TlvError> { /// let t = Tag::try_from("7f00")?; /// assert!(!t.iso7816_compliant()); /// // 9f1e is a valid EMV tag, but not ISO7816-4 compliant /// let t = Tag::try_from("9f1e")?; /// assert!(!t.iso7816_compliant()); /// # Ok(()) /// # } /// # #[must_use] pub fn iso7816_compliant(&self) -> bool { let first_byte_ok = if self.len == 1 { (self.raw[2] & Self::VALUE_MASK) != Self::VALUE_MASK } else { (self.raw[2] & Self::VALUE_MASK) == Self::VALUE_MASK }; let other_bytes_ok = match self.len { 1 => true, 2 => { // In two-byte tag fields, the second byte consists of bit 8 set to 0 and bits 7 to 1 encoding a number greater // than thirty. The second byte is valued from '1F' to '7F; the tag number is from 31 to 127 !(self.raw[2] < 0x1F_u8 || self.raw[2] > 0x7F_u8) } 3 => { // The second byte is valued from '81' to 'FF' if self.raw[2] < 0x81_u8 { false } else { //and the third byte from '00' to '7F'; self.raw[3] & Self::MORE_BYTES_MASK == 0 } } _ => false, //rfu }; first_byte_ok & other_bytes_ok } /// Get the tag class. /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::ber::{Tag, Class}; /// # use iso7816_tlv::TlvError; /// /// # fn main() -> Result<(), Box> { /// let tag = Tag::try_from(0b0010_0000)?; /// assert_eq!(Class::Universal, tag.class()); /// let tag = Tag::try_from(0b1100_0000)?; /// assert_eq!(Class::Private, tag.class()); /// /// # Ok(()) /// # } /// # /// ``` #[must_use] pub fn class(&self) -> Class { self.raw[3 - self.len].into() } pub(crate) fn read(r: &mut Reader) -> Result { let first = r.read_byte()?; let mut value = u64::from(first); if first & Self::VALUE_MASK == Self::VALUE_MASK { loop { value = value.checked_shl(8).ok_or(TlvError::InvalidInput)?; let x = r.read_byte()?; value |= u64::from(x); if x & 0x80 == 0 { break; } } } let r = Self::try_from(value)?; Ok(r) } } impl fmt::Display for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let t = [0, self.raw[0], self.raw[1], self.raw[2]]; let as_int: u32 = u32::from_be_bytes(t); write!(f, "Tag {:x} ({:?})", as_int, self.class()) } } impl fmt::Debug for Tag { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let t = [0, self.raw[0], self.raw[1], self.raw[2]]; let as_int: u32 = u32::from_be_bytes(t); let constructed = if self.is_constructed() { "Contructed" } else { "Primitive" }; write!(f, "Tag {:x} ({:?}; {})", as_int, self.class(), constructed) } } // From impl may fail, not the converse #[allow(clippy::from_over_into)] impl Into for Tag { fn into(self) -> u64 { let mut ret = 0_u64; for &b in self.to_bytes() { ret = ret << 8 | u64::from(b); } ret } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: u64) -> Result { let bytes = v.to_be_bytes(); let mut first_non_zero = 0; for &x in &bytes { if x == 0 { first_non_zero += 1; } else { break; } } let raw = [ bytes[bytes.len() - 3], bytes[bytes.len() - 2], bytes[bytes.len() - 1], ]; let bytes = &bytes[first_non_zero..]; let len = bytes.len(); match len { 0 => return Err(TlvError::InvalidInput), 1 => { if (bytes[0] & Self::VALUE_MASK) == Self::VALUE_MASK { return Err(TlvError::InvalidInput); } } 2 => { if (bytes[1] & Self::MORE_BYTES_MASK) == Self::MORE_BYTES_MASK { return Err(TlvError::InvalidInput); } } 3 => { if (bytes[1] & Self::MORE_BYTES_MASK) == 0 { return Err(TlvError::InvalidInput); } if (bytes[2] & Self::MORE_BYTES_MASK) == Self::MORE_BYTES_MASK { return Err(TlvError::InvalidInput); } } _ => return Err(TlvError::TagIsRFU), } Ok(Self { raw, len }) } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: usize) -> Result { Self::try_from(v as u64) } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: u32) -> Result { Self::try_from(u64::from(v)) } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: u16) -> Result { Self::try_from(u64::from(v)) } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: u8) -> Result { Self::try_from(u64::from(v)) } } impl TryFrom for Tag { type Error = TlvError; #[allow(clippy::cast_sign_loss)] fn try_from(v: i32) -> Result { Self::try_from(v as u64) } } impl TryFrom for Tag { type Error = TlvError; #[allow(clippy::cast_sign_loss)] fn try_from(v: i16) -> Result { Self::try_from(v as u64) } } impl TryFrom for Tag { type Error = TlvError; #[allow(clippy::cast_sign_loss)] fn try_from(v: i8) -> Result { Self::try_from(v as u64) } } impl TryFrom<&str> for Tag { type Error = TlvError; fn try_from(v: &str) -> Result { let x = u64::from_str_radix(v, 16)?; Self::try_from(x) } } #[cfg(test)] mod tests { // Note this useful idiom: importing names from outer (for mod tests) scope. use super::*; use untrusted::Input; #[test] fn tag_import_ok() -> Result<()> { assert!(Tag::try_from(0x1).is_ok()); assert_eq!(0x1_u64, Tag::try_from(0x1)?.into()); assert!(Tag::try_from(0x7f22).is_ok()); assert_eq!(0x7f22_u64, Tag::try_from(0x7f22)?.into()); assert!(Tag::try_from(0x7f_ff_22).is_ok()); assert_eq!(0x7f_ff_22_u64, Tag::try_from(0x7f_ff_22)?.into()); assert_eq!(Err(TlvError::InvalidInput), Tag::try_from(0)); assert_eq!(Err(TlvError::InvalidInput), Tag::try_from(0x7f)); assert_eq!(Err(TlvError::InvalidInput), Tag::try_from(0x7f80)); assert_eq!(Err(TlvError::InvalidInput), Tag::try_from(0x7f_7f_00)); assert_eq!(Err(TlvError::TagIsRFU), Tag::try_from(0x7f_80_80_00)); assert!(Tag::try_from("7fff22").is_ok()); assert_eq!(Err(TlvError::ParseIntError), Tag::try_from("bad one")); let t = Tag::try_from("5fff22")?; assert_eq!(3, t.len_as_bytes()); let t = Tag::try_from("5f2d")?; assert_eq!(2, t.len_as_bytes()); Ok(()) } #[test] fn issue_14() -> Result<()> { let testcases = [0x7f1e, 0x007f_8001, 0x7f00, 0x9f1e, 0x9f00]; for v in testcases { let t = Tag::try_from(v)?; assert!(!t.iso7816_compliant()); } Ok(()) } #[test] fn tag_import_2() { assert!(Tag::try_from("80").is_ok()); assert!(Tag::try_from(8_u8).is_ok()); assert!(Tag::try_from(8_u16).is_ok()); assert!(Tag::try_from(8_u32).is_ok()); assert!(Tag::try_from(8_i32).is_ok()); assert!(Tag::try_from("7f22").is_ok()); assert!(Tag::try_from("bad").is_err()); assert!(Tag::try_from("7f80").is_err()); // updte : now ok, but not iso7816 compliant assert!(Tag::try_from("7f00").is_ok()); assert!(Tag::try_from("7f1e").is_ok()); } #[test] fn tag_import() -> Result<()> { let vectors = ["01", "7f22", "7fff22"]; for &v in &vectors { let x = Tag::try_from(v)?; assert_eq!(v.len() / 2, x.len_as_bytes()); assert_eq!(v.len() / 2, x.to_bytes().len()); } Ok(()) } #[test] fn tag_read() -> Result<()> { let vectors: [&[u8]; 3] = [&[1], &[0x7f, 0x22], &[0x7f, 0xff, 0x22]]; for &v in &vectors { let mut r = Reader::new(Input::from(v)); let x = Tag::read(&mut r)?; assert_eq!(v.len(), x.len_as_bytes()); assert_eq!(v.len(), x.to_bytes().len()); assert_eq!(v, x.to_bytes()); } let bad_vectors: [&[u8]; 2] = [&[0x7f, 0xff], &[0x7f, 0xff, 0xff]]; for &v in &bad_vectors { let mut r = Reader::new(Input::from(v)); assert_eq!(Err(TlvError::TruncatedInput), Tag::read(&mut r)); } Ok(()) } } iso7816-tlv-0.4.4/src/ber/tlv.rs000064400000000000000000000334221046102023000142720ustar 00000000000000use alloc::vec::Vec; use core::fmt; use untrusted::{Input, Reader}; use super::{Tag, Value}; use crate::{Result, TlvError}; /// BER-TLV structure, following ISO/IEC 7816-4. /// > # BER-TLV data objects /// > Each BER-TLV data object consists of two or three consecutive fields /// > (see the basic encoding rules of ASN.1 in ISO/IEC 8825-1): /// > a mandatory tag field, a mandatory length field and a conditional value field. /// > - The tag field consists of one or more consecutive bytes. /// > It indicates a class and an encoding and it encodes a tag number. /// > The value '00' is invalid for the first byte of tag fields (see ISO/IEC 8825-1). /// > - The length field consists of one or more consecutive bytes. /// > It encodes a length, i.e., a number denoted N. /// > - If N is zero, there is no value field, i.e., the data object is empty. /// > Otherwise (N > 0), the value field consists of N consecutive bytes. #[derive(PartialEq, Debug, Clone)] pub struct Tlv { tag: Tag, value: Value, } impl Tlv { /// Create a BER-TLV data object from valid tag and value.alloc /// # Errors /// Fails with [`TlvError::Inconsistant`] /// if the tag indicates a contructed value (resp. primitive) and the /// value is primitive (resp. contructed). pub fn new(tag: Tag, value: Value) -> Result { match value { Value::Constructed(_) => { if !tag.is_constructed() { return Err(TlvError::Inconsistant); } } Value::Primitive(_) => { if tag.is_constructed() { return Err(TlvError::Inconsistant); } } } Ok(Self { tag, value }) } /// Get BER-TLV tag. #[must_use] pub fn tag(&self) -> &Tag { &self.tag } /// Get BER-TLV value length #[must_use] pub fn length(&self) -> usize { self.len() } /// Get BER-TLV value #[must_use] pub fn value(&self) -> &Value { &self.value } fn len_length(l: usize) -> usize { match l { 0..=127 => 1, 128..=255 => 2, 256..=65_535 => 3, 65_536..=16_777_215 => 4, _ => 5, } } #[allow(clippy::cast_possible_truncation)] fn inner_len_to_vec(&self) -> Vec { let l = self.value.len_as_bytes(); if l < 0x7f { vec![l as u8] } else { let mut ret: Vec = l .to_be_bytes() .iter() .skip_while(|&x| *x == 0) .copied() .collect(); ret.insert(0, 0x80 | ret.len() as u8); ret } } pub(crate) fn len(&self) -> usize { let inner_len = self.value.len_as_bytes(); self.tag.len_as_bytes() + Self::len_length(inner_len) + inner_len } /// serializes self into a byte vector. #[must_use] pub fn to_vec(&self) -> Vec { let mut ret: Vec = Vec::new(); ret.extend(self.tag.to_bytes().iter()); ret.append(&mut self.inner_len_to_vec()); match &self.value { Value::Primitive(v) => ret.extend(v.iter()), Value::Constructed(tlv) => { for t in tlv { ret.append(&mut t.to_vec()); } } }; ret } fn read_len(r: &mut Reader) -> Result { let mut ret: usize = 0; let x = r.read_byte()?; if x & 0x80 == 0 { ret = x as usize; } else { let n_bytes = x as usize & 0x7f; if n_bytes > 4 { return Err(TlvError::InvalidLength); } for _ in 0..n_bytes { let x = r.read_byte()?; ret = ret << 8 | x as usize; } } Ok(ret) } fn read(r: &mut Reader) -> Result { let tag = Tag::read(r)?; let len = Self::read_len(r)?; let ret = if tag.is_constructed() { let mut val = Value::Constructed(vec![]); while val.len_as_bytes() < len { let tlv = Self::read(r)?; val.push(tlv)?; } Self::new(tag, val)? } else { let content = r.read_bytes(len)?; Self::new(tag, Value::Primitive(content.as_slice_less_safe().to_vec()))? }; if ret.value.len_as_bytes() == len { Ok(ret) } else { Err(TlvError::Inconsistant) } } /// Parses a byte array into a BER-TLV structure. /// This also returns the unprocessed data. pub fn parse(input: &[u8]) -> (Result, &[u8]) { let mut r = Reader::new(Input::from(input)); ( Self::read(&mut r), r.read_bytes_to_end().as_slice_less_safe(), ) } /// Parses a byte array into a vector of BER-TLV. /// # Note /// Errors are discarded and parsing stops at first error /// Prefer using the [`parse()`](Self::parse()) method and iterate over returned processed data. #[must_use] pub fn parse_all(input: &[u8]) -> Vec { let mut ret = Vec::new(); let mut r = Reader::new(Input::from(input)); while !r.at_end() { if Self::read(&mut r).map(|elem| ret.push(elem)).is_err() { break; } } ret } /// Parses a byte array into a BER-TLV structure. /// Input must exactly match a BER-TLV object. /// # Errors /// Fails with [`TlvError::InvalidInput`] if input does not match a BER-TLV object. pub fn from_bytes(input: &[u8]) -> Result { let (r, n) = Self::parse(input); if n.is_empty() { r } else { Err(TlvError::InvalidInput) } } /// Finds first occurence of a TLV object with given tag in self. #[must_use] pub fn find(&self, tag: &Tag) -> Option<&Self> { match &self.value { Value::Primitive(_) => { if self.tag == *tag { Some(self) } else { None } } Value::Constructed(e) => { for x in e { match x.find(tag) { None => (), Some(e) => return Some(e), } } None } } } /// find all occurences of TLV objects with given given tag in self. /// Note that searching [`ContextSpecific`](crate::ber::tag::Class::ContextSpecific) class tag (0x80 for instance) will return /// a vector of possibly unrelated tlv data. #[must_use] pub fn find_all(&self, tag: &Tag) -> Vec<&Self> { let mut ret: Vec<&Self> = Vec::new(); match &self.value { Value::Primitive(_) => { if self.tag == *tag { ret.push(self); } } Value::Constructed(e) => { for x in e { let v = x.find(tag); ret.extend(v); } } } ret } } impl fmt::Display for Tlv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}, ", self.tag)?; write!(f, "len={}, ", self.value.len_as_bytes())?; write!(f, "value:")?; match &self.value { Value::Primitive(e) => { for x in e { write!(f, "{x:02X}")?; } } Value::Constructed(e) => { let padding_len = f.width().map_or(4, |w| w + 4); for x in e { writeln!(f)?; write!( f, "{}{:>padding$}", " ".repeat(padding_len), x, padding = padding_len )?; } } }; Ok(()) } } #[cfg(test)] mod tests { use super::*; use core::convert::TryFrom; #[test] fn tlv_to_from_vec_primitive() -> Result<()> { let tlv = Tlv::new(Tag::try_from(1_u32)?, Value::Primitive(vec![0]))?; assert_eq!(vec![1, 1, 0], tlv.to_vec()); { let data = vec![0_u8; 255]; let tlv = Tlv::new(Tag::try_from(1_u32)?, Value::Primitive(data.clone()))?; let mut expected = vec![1_u8, 0x81, 0xFF]; expected.append(&mut data.clone()); assert_eq!(expected, tlv.to_vec()); assert_eq!(Tag::try_from(1_u32)?, *tlv.tag()); assert_eq!(Value::Primitive(data), *tlv.value()); let mut r = Reader::new(Input::from(&expected)); let read = Tlv::read(&mut r)?; assert_eq!(tlv, read); } { let data = vec![0_u8; 256]; let tlv = Tlv::new(Tag::try_from(1_u32)?, Value::Primitive(data.clone()))?; let mut expected = vec![1_u8, 0x82, 0x01, 0x00]; expected.append(&mut data.clone()); assert_eq!(expected, tlv.to_vec()); assert_eq!(Tag::try_from(1_u32)?, *tlv.tag()); assert_eq!(Value::Primitive(data), *tlv.value()); let mut r = Reader::new(Input::from(&expected)); let read = Tlv::read(&mut r)?; assert_eq!(tlv, read); } { let data = vec![0_u8; 65_536]; let tlv = Tlv::new(Tag::try_from(1_u32)?, Value::Primitive(data.clone()))?; let mut expected = vec![1_u8, 0x83, 0x01, 0x00, 0x00]; expected.append(&mut data.clone()); assert_eq!(expected, tlv.to_vec()); assert_eq!(Tag::try_from(1_u32)?, *tlv.tag()); assert_eq!(Value::Primitive(data), *tlv.value()); let mut r = Reader::new(Input::from(&expected)); let read = Tlv::read(&mut r)?; assert_eq!(tlv, read); } Ok(()) } #[test] #[allow(clippy::cast_possible_truncation)] fn tlv_to_from_vec_constructed() -> Result<()> { let base = Tlv::new(Tag::try_from(1_u32)?, Value::Primitive(vec![0]))?; let mut construct = Value::Constructed(vec![base.clone(), base.clone(), base.clone()]); let tlv = Tlv::new(Tag::try_from("7f22")?, construct.clone())?; let mut expected = vec![0x7f_u8, 0x22, 9]; expected.append(&mut base.to_vec()); expected.append(&mut base.to_vec()); expected.append(&mut base.to_vec()); assert_eq!(expected, tlv.to_vec()); assert_eq!(Tag::try_from("7f22")?, *tlv.tag()); assert_eq!(construct, *tlv.value()); let mut r = Reader::new(Input::from(&expected)); let read = Tlv::read(&mut r)?; assert_eq!(tlv, read); construct.push(base.clone())?; expected[2] += base.len() as u8; expected.append(&mut base.to_vec()); let tlv = Tlv::new(Tag::try_from("7f22")?, construct)?; assert_eq!(expected, tlv.to_vec()); let mut r = Reader::new(Input::from(&expected)); let read = Tlv::read(&mut r)?; assert_eq!(tlv, read); Ok(()) } #[test] fn parse() -> Result<()> { let primitive_bytes = vec![1, 1, 0]; let more_bytes = vec![1_u8; 10]; let mut input = vec![0x7f_u8, 0x22, 9]; input.extend(&primitive_bytes); input.extend(&primitive_bytes); input.extend(&primitive_bytes); let expected = input.clone(); input.extend(&more_bytes); let (tlv, left) = Tlv::parse(&input); assert_eq!(expected, tlv?.to_vec()); assert_eq!(more_bytes, left); Ok(()) } #[cfg(feature = "std")] #[test] #[allow(clippy::redundant_clone)] // keep redundant_clone to have fewer modification if test is expanded fn display() -> Result<()> { let base = Tlv::new(Tag::try_from(0x80_u32)?, Value::Primitive(vec![0]))?; let construct = Value::Constructed(vec![base.clone(), base.clone()]); let tlv = Tlv::new(Tag::try_from("7f22")?, construct.clone())?; let mut construct2 = construct.clone(); construct2.push(tlv)?; construct2.push(base)?; let t = Tag::try_from("3F32")?; let tlv = Tlv::new(t, construct2)?; println!("{}", tlv); Ok(()) } #[test] #[allow(clippy::redundant_clone)] // keep redundant_clone to have fewer modification if test is expanded fn find() -> Result<()> { let base = Tlv::new(Tag::try_from(0x80_u32)?, Value::Primitive(vec![0]))?; let t = base.clone(); // shall return self assert_eq!(Some(&t), t.find(&Tag::try_from(0x80_u32)?)); assert!(t.find(&Tag::try_from(0x81_u32)?).is_none()); let construct = Value::Constructed(vec![t, base.clone()]); let tlv = Tlv::new(Tag::try_from("7f22")?, construct.clone())?; assert_eq!(None, tlv.find(&Tag::try_from(0x81_u32)?)); if let Some(found) = tlv.find(&Tag::try_from(0x80_u32)?) { assert_eq!(base.clone(), *found); } else { panic!("Tlv not found"); } Ok(()) } #[test] #[allow(clippy::redundant_clone)] // keep redundant_clone to have fewer modification if test is expanded fn find_all() -> Result<()> { let base = Tlv::new(Tag::try_from(0x80_u32)?, Value::Primitive(vec![0]))?; let t = base.clone(); // shall return self assert_eq!(1, t.find_all(&Tag::try_from(0x80_u32)?).len()); assert_eq!(0, t.find_all(&Tag::try_from(0x81_u32)?).len()); let construct = Value::Constructed(vec![t, base.clone()]); let tlv = Tlv::new(Tag::try_from("7f22")?, construct.clone())?; assert_eq!(0, tlv.find_all(&Tag::try_from(0x81_u32)?).len()); assert_eq!(2, tlv.find_all(&Tag::try_from(0x80_u32)?).len()); Ok(()) } } iso7816-tlv-0.4.4/src/ber/value.rs000064400000000000000000000023241046102023000145760ustar 00000000000000use super::Tlv; use crate::error::TlvError; use crate::Result; use alloc::vec::Vec; /// Value definition of BER-TLV data #[derive(PartialEq, Debug, Clone)] pub enum Value { /// constructed data object, i.e., the value is encoded in BER-TLV Constructed(Vec), /// primitive data object, i.e., the value is not encoded in BER-TLV /// (may be empty) Primitive(Vec), } impl Value { /// Wether the value is constructed or not #[must_use] pub fn is_constructed(&self) -> bool { matches!(self, Self::Constructed(_)) } /// Get value length once serialized into BER-TLV data #[must_use] pub fn len_as_bytes(&self) -> usize { match &self { Self::Primitive(v) => v.len(), Self::Constructed(tlv) => tlv.iter().fold(0, |sum, x| sum + x.len()), } } /// Append a BER-TLV data object. /// # Errors /// Fails with `TlvError::Inconsistant` on primitive or empty values. pub fn push(&mut self, tlv: Tlv) -> Result<()> { match self { Self::Constructed(t) => { t.push(tlv); Ok(()) } Self::Primitive(_) => Err(TlvError::Inconsistant), } } } iso7816-tlv-0.4.4/src/error.rs000064400000000000000000000025601046102023000140450ustar 00000000000000use core::fmt; /// Error definition for TLV data as defined in [ISO7816-4]. #[allow(clippy::module_name_repetitions)] #[derive(PartialEq, Clone, Debug)] pub enum TlvError { /// Invalid input encountered InvalidInput, /// Read tag is reserved for future usage TagIsRFU, /// conversion error ParseIntError, /// parsing error TruncatedInput, /// Inconsistant (tag, value) pair Inconsistant, /// Read invalid length value InvalidLength, } #[cfg(feature = "std")] impl std::error::Error for TlvError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { _ => None, } } } impl fmt::Display for TlvError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let s = match self { Self::InvalidInput => "Invalid tag encountered", Self::TagIsRFU => "Tag is reserved for future usage", Self::ParseIntError => "Error parsing input as int", Self::TruncatedInput => "Error input too short", Self::Inconsistant => "Inconsistant (tag, value) pair", Self::InvalidLength => "Read invalid length value", }; write!(f, "{s}") } } impl From for TlvError { fn from(_: core::num::ParseIntError) -> Self { Self::ParseIntError } } impl From for TlvError { fn from(_: untrusted::EndOfInput) -> Self { Self::TruncatedInput } } iso7816-tlv-0.4.4/src/lib.rs000064400000000000000000000020751046102023000134630ustar 00000000000000//! This crate provides tools and utilities for handling TLV data as //! defined in [ISO7816-4][iso7816-4]. //! //! This include BER-TLV data or SIMPLE-TLV data objects. //! //! //! //! //! [iso7816-4]: https://www.iso.org/standard/54550.html #![deny(missing_docs)] #![cfg_attr(feature = "cargo-clippy", deny(clippy::all))] #![cfg_attr(feature = "cargo-clippy", deny(clippy::pedantic))] // otherwise cargo doc fails with // error: no global memory allocator found but one is required; link to std or add #[global_allocator] to // a static item that implements the GlobalAlloc trait. #![cfg_attr(not(doc), no_std)] // use custom allocator for tests #[cfg(test)] use static_alloc::Bump; #[cfg(test)] #[global_allocator] static ALLOC: Bump<[u8; 1 << 28]> = Bump::uninit(); // use vectors #[macro_use] extern crate alloc; #[cfg(test)] #[macro_use] extern crate hex_literal; use core::result; // internal organization pub mod ber; mod error; pub mod simple; // custom reexport (structs at same level for users) pub use error::TlvError; type Result = result::Result; iso7816-tlv-0.4.4/src/simple.rs000064400000000000000000000237501046102023000142110ustar 00000000000000//! This module provides tools and utilities for handling SIMPLE-TLV data as //! defined in [ISO7816-4][iso7816-4]. //! //! //! //! //! [iso7816-4]: https://www.iso.org/standard/54550.html //! use alloc::vec::Vec; use core::convert::TryFrom; use untrusted::{Input, Reader}; use crate::{Result, TlvError}; /// Tag for SIMPLE-TLV data as defined in [ISO7816-4]. /// > The tag field consists of a single byte encoding a tag number from 1 to 254. /// > The values '00' and 'FF' are invalid for tag fields. /// /// Tags can be generated using the [`TryFrom`][TryFrom] trait /// from u8 or hex [str][str]. /// /// [TryFrom]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html /// [str]:https://doc.rust-lang.org/std/str/ /// /// # Example /// ```rust /// use std::convert::TryFrom; /// use iso7816_tlv::simple::Tag; /// # use iso7816_tlv::TlvError; /// # fn main() -> Result<(), TlvError> { /// /// // get tag from u8 or &str /// assert!(Tag::try_from("80").is_ok()); /// assert!(Tag::try_from(8u8).is_ok()); /// assert!(Tag::try_from(0x80).is_ok()); /// assert!(Tag::try_from(127).is_ok()); /// /// assert!(Tag::try_from("er").is_err()); /// assert!(Tag::try_from("00").is_err()); /// assert!(Tag::try_from("ff").is_err()); /// /// // get tag as u8 /// let tag = Tag::try_from("80")?; /// let _tag_as_u8: u8 = tag.into(); /// let _tag_as_u8 = Into::::into(tag); /// # Ok(()) /// # } /// # /// ``` #[derive(PartialEq, Debug, Clone, Copy)] pub struct Tag(u8); impl Tag { /// Tries to convert a `u8` into a `Tag`. This is equivalent to the /// [`TryFrom`] impl for `u8`, except that this fn is const and can be used /// in some contexts that `TryFrom` cannot be used in, for example for /// defining constants: /// /// ``` /// use iso7816_tlv::simple::Tag; /// /// const SOME_TAG_CONSTANT: Tag = match Tag::try_from_u8(0x42) { /// Ok(tag) => tag, /// Err(e) => panic!(), /// }; /// ``` /// /// # Errors /// This method returns `Err(TlvError::InvalidInput)` if `v` is not a legal /// tag (e.g., if v is `0x00` or `0xFF`). pub const fn try_from_u8(v: u8) -> Result { match v { 0x00 | 0xFF => Err(TlvError::InvalidInput), _ => Ok(Self(v)), } } } /// Value for SIMPLE-TLV data as defined in [ISO7816-4]. /// > The value field consists of N consecutive bytes. /// > N may be zero. /// > In this case there is no value field. /// /// In this case Value is an empty vector pub type Value = Vec; /// SIMPLE-TLV data object representation. /// > Each SIMPLE-TLV data object shall consist of two or three consecutive fields: /// > a mandatory tag field, a mandatory length field and a conditional value field #[derive(PartialEq, Debug, Clone)] pub struct Tlv { tag: Tag, value: Value, } // From impl may fail, not the converse #[allow(clippy::from_over_into)] impl Into for Tag { fn into(self) -> u8 { self.0 } } impl TryFrom for Tag { type Error = TlvError; fn try_from(v: u8) -> Result { Self::try_from_u8(v) } } impl TryFrom<&str> for Tag { type Error = TlvError; fn try_from(v: &str) -> Result { let x = u8::from_str_radix(v, 16)?; Self::try_from(x) } } impl Tlv { /// Create a SIMPLE-TLV data object from valid tag and value. /// A value has a maximum size of `65_535` bytes. /// /// # Errors /// Fails with `TlvError::InvalidLength` if value is longer than `65_535` bytes. pub fn new(tag: Tag, value: Value) -> Result { if value.len() > 65_536 { Err(TlvError::InvalidLength) } else { Ok(Self { tag, value }) } } /// Get SIMPLE-TLV tag. #[must_use] pub fn tag(&self) -> Tag { self.tag } /// Get SIMPLE-TLV value length #[must_use] pub fn length(&self) -> usize { self.value.len() } /// Get SIMPLE-TLV value #[must_use] pub fn value(&self) -> &[u8] { self.value.as_slice() } /// serializes self into a byte vector. #[allow(clippy::cast_possible_truncation)] #[must_use] pub fn to_vec(&self) -> Vec { let mut ret = vec![self.tag.0]; let len = self.value.len(); if len >= 255 { ret.push(0xFF); ret.push((len >> 8) as u8); } ret.push(len as u8); ret.extend(&self.value); ret } fn read_len(r: &mut Reader) -> Result { let mut ret: usize = 0; let x = r.read_byte()?; if x == 0xFF { for _ in 0..2 { let x = r.read_byte()?; ret = ret << 8 | usize::from(x); } } else { ret = usize::from(x); } Ok(ret) } fn read(r: &mut Reader) -> Result { let tag = Tag::try_from(r.read_byte()?)?; let len = Self::read_len(r)?; let content = r.read_bytes(len)?; Ok(Self { tag, value: content.as_slice_less_safe().to_vec(), }) } /// Parses a byte array into a SIMPLE-TLV structure. /// This also returns the unprocessed data. /// # Example (parse mulitple tlv in input) /// ```rust /// use iso7816_tlv::simple::Tlv; /// use hex_literal::hex; /// /// let in_data = hex!( /// "03 01 01" /// "04 01 04" /// "07 07 85 66 C9 6A 14 49 04" /// "01 08 57 5F 93 6E 01 00 00 00" /// "09 01 00"); /// let mut buf: &[u8] = &in_data; /// let mut parsed_manual = Vec::new(); /// while !buf.is_empty() { /// let (r, remaining) = Tlv::parse(buf); /// buf = remaining; /// if r.map(|res| parsed_manual.push(res)).is_err() { /// break; /// } /// } /// ``` pub fn parse(input: &[u8]) -> (Result, &[u8]) { let mut r = Reader::new(Input::from(input)); ( Self::read(&mut r), r.read_bytes_to_end().as_slice_less_safe(), ) } /// Parses a byte array into a vector of SIMPLE-TLV. /// # Note /// Errors are discarded and parsing stops at first error /// Prefer using the [`parse()`](Self::parse()) method and iterate over returned processed data. #[must_use] pub fn parse_all(input: &[u8]) -> Vec { let mut ret = Vec::new(); let mut r = Reader::new(Input::from(input)); while !r.at_end() { if Self::read(&mut r).map(|elem| ret.push(elem)).is_err() { break; } } ret } /// Parses a byte array into a SIMPLE-TLV structure. /// Input must exactly match a SIMPLE-TLV object. /// # Errors /// Fails with `TlvError::InvalidInput` if input does not match a SIMPLE-TLV object. pub fn from_bytes(input: &[u8]) -> Result { let (r, n) = Self::parse(input); if n.is_empty() { r } else { Err(TlvError::InvalidInput) } } } #[cfg(test)] mod tests { use super::*; use core::convert::TryFrom; use rand_core::{RngCore, SeedableRng}; #[test] fn tag_import() -> Result<()> { assert!(Tag::try_from("80").is_ok()); assert!(Tag::try_from(8_u8).is_ok()); assert_eq!(0x8_u8, Tag::try_from(8_u8)?.into()); assert!(Tag::try_from(0x80).is_ok()); assert_eq!(0x80_u8, Tag::try_from(0x80_u8)?.into()); assert!(Tag::try_from(127).is_ok()); assert_eq!(127_u8, Tag::try_from(127_u8)?.into()); assert!(Tag::try_from("er").is_err()); assert!(Tag::try_from("00").is_err()); assert!(Tag::try_from("ff").is_err()); Ok(()) } #[test] fn parse_1() -> Result<()> { let in_data = [ 0x84_u8, 0x01, 0x2C, 0x97, 0x00, 0x84, 0x01, 0x24, 0x9E, 0x01, 0x42, ]; let (r, in_data) = Tlv::parse(&in_data); assert_eq!(8, in_data.len()); assert!(r.is_ok()); let t = r?; assert_eq!(0x84_u8, t.tag().into()); assert_eq!(1, t.length()); assert_eq!(&[0x2C], t.value()); let (r, in_data) = Tlv::parse(in_data); assert_eq!(6, in_data.len()); assert!(r.is_ok()); let t = r?; assert_eq!(0x97_u8, t.tag().into()); assert_eq!(0, t.length()); let (r, in_data) = Tlv::parse(in_data); assert_eq!(3, in_data.len()); assert!(r.is_ok()); let t = r?; assert_eq!(0x84_u8, t.tag().into()); assert_eq!(1, t.length()); assert_eq!(&[0x24], t.value()); let (r, in_data) = Tlv::parse(in_data); assert_eq!(0, in_data.len()); assert!(r.is_ok()); let t = r?; assert_eq!(0x9E_u8, t.tag().into()); assert_eq!(1, t.length()); assert_eq!(&[0x42], t.value()); Ok(()) } #[test] fn parse_multiple() { let in_data = hex!( "03 01 01" "04 01 04" "07 07 85 66 C9 6A 14 49 04" "01 08 57 5F 93 6E 01 00 00 00" "09 01 00" ); let mut buf: &[u8] = &in_data; let mut parsed_manual = Vec::new(); while !buf.is_empty() { let (r, remaining) = Tlv::parse(buf); buf = remaining; let pushed = r.map(|res| parsed_manual.push(res)); if pushed.is_err() { break; } } let parsed_at_once = Tlv::parse_all(&in_data); assert_eq!(parsed_manual, parsed_at_once); } #[test] #[allow(clippy::cast_possible_truncation)] fn serialize_parse() -> Result<()> { let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(10); for r in 1_u8..0xFF { let v_len = rng.next_u32() % 0xFFFF; let v: Value = (0..v_len).map(|_| rng.next_u32() as u8).collect(); let tlv = Tlv::new(Tag::try_from(r)?, v.clone())?; let ser = tlv.to_vec(); let tlv_2 = Tlv::from_bytes(&ser)?; assert_eq!(tlv, tlv_2); assert_eq!(r, tlv.tag().into()); assert_eq!(v, tlv.value()); } Ok(()) } }