Skip to content

Commit

Permalink
Refactor X.509 encoding library to use the der crate
Browse files Browse the repository at this point in the history
The der crate-based implementation is significantly less code and follows a
more consistent pattern. Since this allows defining types with encoding
rules, this will make it much simpler to add structures for ML-DSA
structures for Caliptra 2.0.

The der crate is part of RustCrypto and is intended to be
embedded/no_std friendly.
  • Loading branch information
jhand2 committed Sep 13, 2024
1 parent 2e02db5 commit e014dc3
Show file tree
Hide file tree
Showing 10 changed files with 1,179 additions and 1,914 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions crypto/src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@ pub struct EcdsaSig {
pub s: CryptoBuf,
}

pub const MAX_ENCODED_ECDSA_PUB: usize = 1 + (2 * CryptoBuf::MAX_SIZE);

#[derive(ZeroizeOnDrop)]
pub struct EncodedEcdsaPub(pub ArrayVec<u8, MAX_ENCODED_ECDSA_PUB>);

impl From<&EcdsaPub> for EncodedEcdsaPub {
fn from(value: &EcdsaPub) -> Self {
// PANIC FREE: Size of data is same is 1 + x_max + y_max
let mut encoded = EncodedEcdsaPub(ArrayVec::<u8, MAX_ENCODED_ECDSA_PUB>::new());
encoded.0.push(0x4);
encoded.0.try_extend_from_slice(value.x.bytes()).unwrap();
encoded.0.try_extend_from_slice(value.y.bytes()).unwrap();
encoded
}
}

/// An ECDSA public key
#[derive(ZeroizeOnDrop)]
pub struct EcdsaPub {
Expand Down
3 changes: 2 additions & 1 deletion dpe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ caliptra-cfi-lib-git = { workspace = true, default-features = false, features =
caliptra-cfi-derive-git.workspace = true
constant_time_eq = "0.3.0"
crypto = {path = "../crypto", default-features = false}
der = { version = "0.7.9", default-features = false, features = ["oid", "derive"] }
platform = {path = "../platform", default-features = false}
ufmt = { git = "https://github.com/korran/ufmt.git", rev = "1d0743c1ffffc68bc05ca8eeb81c166192863f33", features = ["inline"] }
zerocopy.workspace = true
zeroize = { version = "1.6.0", default-features = false, features = ["zeroize_derive"] }
cfg-if = "1.0.0"

[dev-dependencies]
asn1 = "0.13.0"
asn1 = { version = "0.13.0", default-features = false}
caliptra-cfi-lib-git = { workspace = true, features = ["cfi-test"] }
openssl.workspace = true
x509-parser = "0.15.1"
Expand Down
5 changes: 3 additions & 2 deletions dpe/fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

298 changes: 298 additions & 0 deletions dpe/src/asn1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
use der::{
Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader,
Tag, Writer,
};
use crate::{
response::DpeErrorCode, x509::X509Error,
};
use platform::ArrayVec;

pub struct RawGeneralizedTimeRef<'a> {
time: &'a [u8],
}

impl<'a> RawGeneralizedTimeRef<'a> {
/// Length of an RFC 5280-flavored ASN.1 DER-encoded [`GeneralizedTime`].
const LENGTH: usize = 15;

pub fn new(bytes: &'a [u8]) -> Result<Self, DpeErrorCode> {
if bytes.len() != Self::LENGTH {
return Err(DpeErrorCode::InternalError);
}

Ok(Self { time: bytes })
}
}

impl EncodeValue for RawGeneralizedTimeRef<'_> {
fn value_len(&self) -> Result<Length, der::Error> {
self.time.len().try_into()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
writer.write(self.time)?;
Ok(())
}
}

impl<'a> DecodeValue<'a> for RawGeneralizedTimeRef<'a> {
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> Result<Self, der::Error> {
let time = reader.read_slice(Self::LENGTH.try_into()?)?;
Ok(Self { time })
}
}

impl FixedTag for RawGeneralizedTimeRef<'_> {
const TAG: Tag = Tag::GeneralizedTime;
}

// Wraps any asn1 encodable/decodable type and encodes/decodes it as an octet
// sring
pub struct OctetStringContainer<T>(pub T);

impl<'a, T> EncodeValue for OctetStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
fn value_len(&self) -> Result<Length, der::Error> {
self.0.encoded_len()
}

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

impl<'a, T> DecodeValue<'a> for OctetStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> Result<Self, der::Error> {
Ok(OctetStringContainer::<T>(T::decode(reader)?))
}
}

impl<'a, T> FixedTag for OctetStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
const TAG: Tag = Tag::OctetString;
}

// Wraps any asn1 encodable/decodable type and encodes/decodes it as an octet
// sring
pub struct BitStringContainer<T>(pub T);

impl<'a, T> EncodeValue for BitStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
fn value_len(&self) -> Result<Length, der::Error> {
// Add 1 for unused bits
Ok(self.0.encoded_len()?.saturating_add(Length::ONE))
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
// Write unused bits
writer.write_byte(0u8)?;
self.0.encode(writer)
}
}

impl<'a, T> DecodeValue<'a> for BitStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> Result<Self, der::Error> {
// Unused bits must be 0 for BitStringContainers. Skip unused bits byte.
reader.read_byte()?;
Ok(BitStringContainer::<T>(T::decode(reader)?))
}
}

impl<'a, T> FixedTag for BitStringContainer<T>
where
T: der::Encode + der::Decode<'a>,
{
const TAG: Tag = Tag::BitString;
}

/// UncheckedSetOf provides a smaller SET OF implementation than der::SetOf
/// It assumes the caller has already ensured the set items are ordered properly
/// which removes the need to sort the items in the set.
///
/// DPE certificates generally only have one or two items in SetOf collections,
/// so keeping them manually sorted is trivial.
///
/// At the time of this writing, this removes ~8KiB from the X.509
/// implementation.
pub struct UncheckedSetOf<T, const N: usize> {
values: ArrayVec<T, N>,
}

impl <T, const N: usize> UncheckedSetOf<T, N> {
pub fn new() -> Self {
Self {
values: ArrayVec::default(),
}
}

pub fn insert(&mut self, val: T) {
self.values.push(val);
}
}

impl<'a, T, const N: usize> DecodeValue<'a> for UncheckedSetOf<T, N>
where
T: Decode<'a>,
{
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self, der::Error> {
reader.read_nested(header.length, |reader| {
let mut result = Self::new();

while !reader.is_finished() {
result.values.push(T::decode(reader)?);
}

Ok(result)
})
}
}

impl<'a, T, const N: usize> EncodeValue for UncheckedSetOf<T, N>
where
T: 'a + Decode<'a> + Encode,
{
fn value_len(&self) -> Result<Length, der::Error> {
self.values.iter()
.fold(Ok(Length::ZERO), |len, elem| len + elem.encoded_len()?)
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
for elem in self.values.iter() {
elem.encode(writer)?;
}

Ok(())
}
}

impl<'a, T, const N: usize> FixedTag for UncheckedSetOf<T, N>
where
T: Decode<'a>,
{
const TAG: Tag = Tag::Set;
}

pub struct RawDerSequenceRef<'a> {
val: &'a [u8],
}

impl<'a> RawDerSequenceRef<'a> {
pub fn new(data: &'a [u8]) -> Result<Self, DpeErrorCode> {
// Skip header
let mut reader = der::SliceReader::new(data)
.map_err(|_| DpeErrorCode::from(X509Error::InvalidRawDer))?;
let header = Header::decode(&mut reader)
.map_err(|_| DpeErrorCode::from(X509Error::InvalidRawDer))?;
let len: usize = header
.length
.try_into()
.map_err(|_| DpeErrorCode::from(X509Error::InvalidRawDer))?;
let offset = reader
.position()
.try_into()
.map_err(|_| DpeErrorCode::from(X509Error::InvalidRawDer))?;

Ok(Self {
val: &data[offset..offset + len],
})
}
}

impl<'a> EncodeValue for RawDerSequenceRef<'a> {
fn value_len(&self) -> Result<Length, der::Error> {
self.val.len().try_into()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
writer.write(self.val)?;
Ok(())
}
}

impl<'a> DecodeValue<'a> for RawDerSequenceRef<'a> {
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self, der::Error> {
let val = reader.read_slice(header.length)?;
Ok(Self { val })
}
}

impl<'a> FixedTag for RawDerSequenceRef<'a> {
const TAG: Tag = Tag::Sequence;
}

// Saves a few-hundred bytes over using der crate PrintableStringRef
pub struct UncheckedPrintableStringRef<'a> {
s: &'a [u8],
}

impl<'a> UncheckedPrintableStringRef<'a> {
pub fn new(s: &'a [u8]) -> Self {
Self{
s,
}
}
}

impl<'a> EncodeValue for UncheckedPrintableStringRef<'a> {
fn value_len(&self) -> Result<Length, der::Error> {
// PANIC FREE: Values guaranteed to be less than u16 max
Ok(self.s.len().try_into().unwrap())
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
writer.write(self.s)?;
Ok(())
}
}

impl<'a> DecodeValue<'a> for UncheckedPrintableStringRef<'a> {
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self, der::Error> {
let s = reader.read_slice(header.length)?;
Ok(Self { s })
}
}

impl<'a> FixedTag for UncheckedPrintableStringRef<'a> {
const TAG: Tag = Tag::PrintableString;
}

pub struct U32OctetString(u32);

impl U32OctetString {
const LENGTH: usize = 4;
}

impl FixedTag for U32OctetString {
const TAG: Tag = Tag::OctetString;
}

impl EncodeValue for U32OctetString {
fn value_len(&self) -> Result<Length, der::Error> {
Self::LENGTH.try_into()
}

fn encode_value(&self, writer: &mut impl Writer) -> Result<(), der::Error> {
writer.write(&self.0.to_be_bytes())?;
Ok(())
}
}

impl<'a> DecodeValue<'a> for U32OctetString {
fn decode_value<R: Reader<'a>>(reader: &mut R, _header: Header) -> Result<Self, der::Error> {
let val = reader.read_slice(Self::LENGTH.try_into()?)?;
// PANIC FREE: val is guaranteed to be 4 bytes
Ok(Self(u32::from_be_bytes(val.try_into().unwrap())))
}
}
Loading

0 comments on commit e014dc3

Please sign in to comment.