Skip to content

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

@dishmaker

Description

@dishmaker

Currently apdu, iso7816, yubikey.rs and emrtd reimplement the same C-APDU logic, but 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 }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions