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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions