RustCrypto / RustCrypto/formats

ISO7816-4: apdu der-like Encode/Decode crate

Open
#2,211 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
338
Forks
188
Avg merge
4d 6h
Merged PRs (30d)
15

Description

Currently apdu, iso7816, yubikey.rs and emrtd reimplement the same C-APDU logic, but with limited short/extended distinction.

Notably, apdu-core does not allow to create extended C-APDU explicitly, therefore it can't send extended read-binary C-APDU.

I think the best solution would be to start with an enum:

/// Command APDU
pub enum CAPDU<D> {
    /// Short-form C-APDU
    Short(CAPDUshort<D>),

    /// Extended-form C-APDU
    Extended(CAPDUextended<D>),
}

where the D parameter would be der-like object with Encode/Decode traits (or ApduEncode/ApduDecode).

Therefore:

  • iso7816::Command becomes CAPDU<heapless::Vec<u8>>
  • iso7816::CommandView<'a> becomes CAPDU<&[u8]>.
[Spoiler] Example code of `CAPDUshort` / `CAPDUextended`

use std::num::NonZero;


/// Command header
#[derive(Debug, Copy, Clone)]
pub struct Header {
    pub cla: u8,
    pub ins: u8,
    pub p1: u8,
    pub p2: u8,
}

/// Command APDU, short form
#[derive(Clone, Debug)]
pub struct CAPDUshort<D> {
    // CLA, INS, P1, P2
    pub header: Header,

    /// Either:
    /// - None: No Lc is encoded. No data.
    /// - Some(data): Short-form Lc will be encoded before data.
    pub data: Option<D>,

    /// Expected length
    ///
    /// special value: max length of 256 is coded as [0x00]
    pub le: Option<ExpectedLenShort>,
}

/// Command APDU, extended form
#[derive(Clone, Debug)]
pub struct CAPDUextended<D> {
    // CLA, INS, P1, P2
    pub header: Header,

    /// Either:
    /// - None: No Lc is encoded. No data.
    /// - Some(data): Short-form Lc will be encoded before data.
    pub data: Option<D>,

    /// Expected length
    ///
    /// special value: max length of 65536 is coded as [0x00, 0x00]
    pub le: Option<ExpectedLenExtended>,
}

/// Le
///
/// special value: max length of 256 is coded as [0x00]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum ExpectedLenShort {
    /// 1..=255
    Short(NonZero<u8>),
    /// [0x00] means max length of 256
    ShortMax,
}

/// Le
///
/// special value: max length of 65536 is coded as [0x00, 0x00]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum ExpectedLenExtended {
    /// 1..=65535
    Extended(NonZero<u16>),
    /// [0x00, 0x00] means max length of 65536
    ExtendedMax,
}

impl ApduEncode for ExpectedLenShort {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        Ok(der::Length::new(1))
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        let value = match self {
            ExpectedLenShort::Short(v) => v.get(),
            ExpectedLenShort::ShortMax => 0x00,
        };
        encoder.write_byte(value)
    }
}

impl ApduEncode for ExpectedLenExtended {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        Ok(der::Length::new(2))
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        let value = match self {
            ExpectedLenExtended::Extended(v) => u16::to_le_bytes(v.get()),
            ExpectedLenExtended::ExtendedMax => [0x00, 0x00],
        };
        encoder.write(&value)
    }
}

impl<D: ApduEncode> ApduEncode for CAPDU<D> {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        match self {
            CAPDU::Short(capdu) => capdu.apdu_encoded_len(),
            CAPDU::Extended(capdu) => capdu.apdu_encoded_len(),
        }
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        match self {
            CAPDU::Short(capdu) => capdu.apdu_encode(encoder),
            CAPDU::Extended(capdu) => capdu.apdu_encode(encoder),
        }
    }
}

impl<D: ApduEncode> ApduEncode for CAPDUshort<D> {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        // Header
        let mut len = der::Length::new(4);

        if let Some(data) = &self.data {
            // Lc (1 byte)
            len = (len + 1u8)?;
            // data
            len = (len + data.apdu_encoded_len()?)?;
        }

        if let Some(le) = self.le {
            // Le (1 byte)
            len = (len + le.apdu_encoded_len()?)?;
        }

        Ok(len)
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        self.header.apdu_encode(encoder)?;
        if let Some(data) = &self.data {
            let data_len = u32::from(data.apdu_encoded_len()?);
            let lc: u8 = data_len.try_into()?;

            // Write Lc
            encoder.write_byte(lc)?;

            data.apdu_encode(encoder)?;
        }
        if let Some(le) = self.le {
            le.apdu_encode(encoder)?;
        }
        Ok(())
    }
}

impl<D: ApduEncode> ApduEncode for CAPDUextended<D> {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        // Header
        let mut len = der::Length::new(4);

        if let Some(data) = &self.data {
            // Lc (3 bytes)
            len = (len + 3u8)?;
            // data
            len = (len + data.apdu_encoded_len()?)?;
        }

        if let Some(le) = self.le {
            if self.data.is_some() {
                // special case: '00' byte before Le
                len = (len + 1u8)?;
            }
            // Le (2 bytes)
            len = (len + le.apdu_encoded_len()?)?;
        }

        Ok(len)
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        self.header.apdu_encode(encoder)?;

        if let Some(data) = &self.data {
            let data_len = u32::from(data.apdu_encoded_len()?);
            let lc: u16 = data_len.try_into()?;
            // Write extended Lc
            encoder.write_byte(0u8)?;
            encoder.write_byte((lc >> 8) as u8)?;
            encoder.write_byte((lc & 0xFF) as u8)?;

            data.apdu_encode(encoder)?;
        }
        if let Some(le) = self.le {
            if self.data.is_some() {
                encoder.write_byte(0)?;
            }
            le.apdu_encode(encoder)?;
        }

        Ok(())
    }
}

impl ApduEncode for Header {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        Ok(der::Length::new(4))
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        encoder.write(&[self.cla, self.ins, self.p1, self.p2])?;
        Ok(())
    }
}



/// APDU encoding trait, similar to [`der::Encode`]. Distinct in order to prevent TLV header mismatch.
pub trait ApduEncode {
    /// Compute the length of this APDU part in bytes.
    fn apdu_encoded_len(&self) -> der::Result<der::Length>;

    /// Encode this APDU part using the provided [`Writer`].
    fn apdu_encode(&self, writer: &mut impl der::Writer) -> der::Result<()>;

    /// Encode this APDU part to the provided byte slice, returning a sub-slice
    /// containing the encoded message.
    fn encode_to_slice<'a>(&self, buf: &'a mut [u8]) -> der::Result<&'a [u8]> {
        let mut writer = der::SliceWriter::new(buf);
        self.apdu_encode(&mut writer)?;
        writer.finish()
    }
}

[Spoiler] Proxy `der::Encode` to `ApduEncode`

/// Useful with DER-TLV structure that is [`der::EncodeValue`], for example:
/// - `CAPDU<WithoutTagLength<SecureMessagingCommand>>`
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct WithoutTagLength<V>(pub V);

impl<V: der::EncodeValue> ApduEncode for WithoutTagLength<V> {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        self.0.value_len()
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        self.0.encode_value(encoder)
    }
}

/// Always returns error [`der::ErrorKind::Overlength`].
///
/// Useful with read-binary, for example:
/// - `CAPDU<NeverData>`
#[derive(Debug)]
pub struct NeverData;

impl ApduEncode for NeverData {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        Ok(0u8.into())
    }

    fn apdu_encode(&self, _encoder: &mut impl der::Writer) -> der::Result<()> {
        Err(der::ErrorKind::Overlength.into())
    }
}


/// Useful with update-binary C-APDU, for example:
/// - `CAPDU<RawBytesRef>`
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct RawBytesRef<'a> {
    pub bytes: &'a [u8],
}


impl<'a> ApduEncode for RawBytesRef<'a> {
    fn apdu_encoded_len(&self) -> der::Result<der::Length> {
        self.bytes.len().try_into()
    }

    fn apdu_encode(&self, encoder: &mut impl der::Writer) -> der::Result<()> {
        encoder.write(&self.bytes)
    }
}

impl<'a> From<&'a [u8]> for RawBytesRef<'a> {
    fn from(bytes: &'a [u8]) -> Self {
        Self { bytes }
    }
}

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by comparing the APDU implementations referenced in the apdu, iso7816, yubikey.rs, and emrtd links, then inspect the apdu-core limitation on creating extended C-APDUs. Done means defining and integrating short and extended CAPDU encoding/decoding with the proposed ApduEncode/ApduDecode-style interfaces.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cryptography
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.