Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ rust-version = "1.85"

[dependencies]
const-oid = { version = "0.10", features = ["db"] }
der = { version = "0.8.0-rc.7", features = ["alloc", "derive", "oid"] }
der = { version = "0.8.0-rc.7", features = ["ber", "derive", "oid"] }
spki = "0.8.0-rc.4"
x509-cert = { version = "0.3.0-rc.0", default-features = false }

Expand Down
1 change: 1 addition & 0 deletions der/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ derive = ["dep:der_derive"]
oid = ["dep:const-oid"]
pem = ["dep:pem-rfc7468", "alloc", "zeroize"]
real = []
ber = ["alloc"]
Copy link
Copy Markdown
Member

@tarcieri tarcieri Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this... why are you enabling alloc?

Edit: opened #1950

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading constructed octet string requires Vec::new

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but that's impl'd in the OctetString type, which is already gated on alloc

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh

let mut bytes = Vec::with_capacity(length.try_into()?);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That method is likewise gated on alloc already


[package.metadata.docs.rs]
all-features = true
Expand Down
6 changes: 3 additions & 3 deletions der/src/asn1/octet_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ mod tests {
}

#[test]
#[cfg(feature = "alloc")]
#[cfg(feature = "ber")]
fn decode_ber() {
use crate::{Decode, asn1::OctetString};
use hex_literal::hex;
Expand All @@ -391,7 +391,7 @@ mod tests {
}

#[test]
#[cfg(feature = "alloc")]
#[cfg(feature = "ber")]
fn decode_context_specific_ber_explicit() {
use crate::{
EncodingRules, SliceReader, TagNumber,
Expand Down Expand Up @@ -422,7 +422,7 @@ mod tests {
}

#[test]
#[cfg(feature = "alloc")]
#[cfg(feature = "ber")]
fn decode_context_specific_ber_implicit() {
use crate::{
EncodingRules, SliceReader, TagNumber,
Expand Down
11 changes: 9 additions & 2 deletions der/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,13 @@ impl<'a> arbitrary::Arbitrary<'a> for &'a BytesRef {
#[cfg(feature = "alloc")]
pub(crate) mod allocating {
use super::BytesRef;
#[cfg(feature = "ber")]
use crate::{EncodingRules, length::indefinite::read_constructed_vec};

use crate::{
DecodeValue, DerOrd, EncodeValue, EncodingRules, Error, Header, Length, Reader, Result,
Tag, Writer, length::indefinite::read_constructed_vec,
DecodeValue, DerOrd, EncodeValue, Error, Header, Length, Reader, Result, Tag, Writer,
};

use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
use core::{borrow::Borrow, cmp::Ordering, ops::Deref};

Expand Down Expand Up @@ -152,13 +155,17 @@ pub(crate) mod allocating {
inner_tag: Tag,
) -> Result<Self> {
// Reassemble indefinite length string types
#[cfg(feature = "ber")]
if reader.encoding_rules() == EncodingRules::Ber
&& header.length.is_indefinite()
&& !inner_tag.is_constructed()
{
return Self::new(read_constructed_vec(reader, header.length, inner_tag)?);
}

#[cfg(not(feature = "ber"))]
let _ = inner_tag;

Self::decode_value(reader, header)
}
}
Expand Down
7 changes: 6 additions & 1 deletion der/src/decode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Trait definition for [`Decode`].

use crate::{EncodingRules, Error, FixedTag, Header, Reader, SliceReader, reader::read_value};
use crate::{Error, FixedTag, Header, Reader, SliceReader, reader::read_value};

use core::marker::PhantomData;

#[cfg(feature = "pem")]
Expand All @@ -12,6 +13,9 @@ use crate::{ErrorKind, Length, Tag};
#[cfg(feature = "alloc")]
use alloc::boxed::Box;

#[cfg(feature = "ber")]
use crate::EncodingRules;

/// Decoding trait.
///
/// This trait provides the core abstraction upon which all decoding operations
Expand All @@ -30,6 +34,7 @@ pub trait Decode<'a>: Sized + 'a {
///
/// Note that most usages should probably use [`Decode::from_der`]. This method allows some
/// BER productions which are not allowed under DER.
#[cfg(feature = "ber")]
fn from_ber(bytes: &'a [u8]) -> Result<Self, Self::Error> {
let mut reader = SliceReader::new_with_encoding_rules(bytes, EncodingRules::Ber)?;
let result = Self::decode(&mut reader)?;
Expand Down
6 changes: 6 additions & 0 deletions der/src/encoding_rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use core::{fmt, str::FromStr};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
pub enum EncodingRules {
/// Basic Encoding Rules.
#[cfg(feature = "ber")]
Comment thread
tarcieri marked this conversation as resolved.
Ber,

/// Distinguished Encoding Rules.
Expand All @@ -25,6 +26,7 @@ impl FromStr for EncodingRules {

fn from_str(s: &str) -> Result<Self, Error> {
match s {
#[cfg(feature = "ber")]
"ber" | "BER" => Ok(EncodingRules::Ber),
"der" | "DER" => Ok(EncodingRules::Der),
_ => Err(ErrorKind::EncodingRules.into()),
Expand All @@ -35,6 +37,7 @@ impl FromStr for EncodingRules {
impl fmt::Display for EncodingRules {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
#[cfg(feature = "ber")]
Self::Ber => "BER",
Self::Der => "DER",
})
Expand All @@ -50,13 +53,16 @@ mod tests {
#[test]
fn display() {
use alloc::string::ToString;
#[cfg(feature = "ber")]
assert_eq!(EncodingRules::Ber.to_string(), "BER");
assert_eq!(EncodingRules::Der.to_string(), "DER");
}

#[test]
fn parse() {
#[cfg(feature = "ber")]
assert_eq!(EncodingRules::Ber, "ber".parse().unwrap());
#[cfg(feature = "ber")]
assert_eq!(EncodingRules::Ber, "BER".parse().unwrap());
assert_eq!(EncodingRules::Der, "der".parse().unwrap());
assert_eq!(EncodingRules::Der, "DER".parse().unwrap());
Expand Down
11 changes: 8 additions & 3 deletions der/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! ASN.1 DER headers.

use crate::{
Decode, DerOrd, Encode, EncodingRules, Error, ErrorKind, Length, Reader, Result, Tag, Writer,
};
#[cfg(feature = "ber")]
use crate::EncodingRules;
use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Tag, Writer};

use core::cmp::Ordering;

/// ASN.1 DER headers: tag + length component of TLV-encoded values
Expand Down Expand Up @@ -46,11 +47,15 @@ impl<'a> Decode<'a> for Header {
}
})?;

#[cfg(feature = "ber")]
if length.is_indefinite() && !is_constructed {
debug_assert_eq!(reader.encoding_rules(), EncodingRules::Ber);
return Err(reader.error(ErrorKind::IndefiniteLength));
}

#[cfg(not(feature = "ber"))]
debug_assert_eq!(is_constructed, tag.is_constructed());

Ok(Self { tag, length })
}
}
Expand Down
24 changes: 20 additions & 4 deletions der/src/length.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
//! Length calculations for encoded ASN.1 DER values

#[cfg(feature = "ber")]
pub(crate) mod indefinite;

use self::indefinite::INDEFINITE_LENGTH_OCTET;
/// Octet identifying an indefinite length as described in X.690 Section
/// 8.1.3.6.1:
///
/// > The single octet shall have bit 8 set to one, and bits 7 to
/// > 1 set to zero.
pub(super) const INDEFINITE_LENGTH_OCTET: u8 = 0b10000000; // 0x80

use crate::{Decode, DerOrd, Encode, EncodingRules, Error, ErrorKind, Reader, Result, Tag, Writer};
use core::{
cmp::Ordering,
Expand All @@ -20,6 +27,7 @@ pub struct Length {
/// Flag bit which specifies whether the length was indeterminate when decoding ASN.1 BER.
///
/// This should always be false when working with DER.
#[cfg(feature = "ber")]
indefinite: bool,
}

Expand All @@ -34,6 +42,7 @@ impl Length {
pub const MAX: Self = Self::new(u32::MAX);

/// Length of end-of-content octets (i.e. `00 00`).
#[cfg(feature = "ber")]
pub(crate) const EOC_LEN: Self = Self::new(2);

/// Maximum number of octets in a DER encoding of a [`Length`] using the
Expand All @@ -46,6 +55,8 @@ impl Length {
pub const fn new(value: u32) -> Self {
Self {
inner: value,

#[cfg(feature = "ber")]
indefinite: false,
}
}
Expand All @@ -68,6 +79,7 @@ impl Length {
}

/// Was this length decoded from an indefinite length when decoding BER?
#[cfg(feature = "ber")]
pub(crate) const fn is_indefinite(self) -> bool {
self.indefinite
}
Expand All @@ -94,6 +106,7 @@ impl Length {
/// Otherwise (as should always be the case with DER), the length is unchanged.
///
/// This method notably preserves the `indefinite` flag when performing arithmetic.
#[cfg(feature = "ber")]
pub(crate) fn sans_eoc(self) -> Self {
if self.indefinite {
// We expect EOC to be present when this is called.
Expand All @@ -104,6 +117,7 @@ impl Length {
indefinite: true,
}
} else {
// Return DER length
self
}
}
Expand Down Expand Up @@ -250,6 +264,7 @@ impl<'a> Decode<'a> for Length {
// Note: per X.690 Section 8.1.3.6.1 the byte 0x80 encodes indefinite lengths
INDEFINITE_LENGTH_OCTET => match reader.encoding_rules() {
// Indefinite lengths are allowed when decoding BER
#[cfg(feature = "ber")]
EncodingRules::Ber => indefinite::decode_indefinite_length(&mut reader.clone()),
// Indefinite lengths are disallowed when decoding DER
EncodingRules::Der => Err(reader.error(ErrorKind::IndefiniteLength)),
Expand Down Expand Up @@ -333,11 +348,12 @@ impl DerOrd for Length {

impl fmt::Debug for Length {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "ber")]
if self.indefinite {
write!(f, "Length({self} [indefinite])")
} else {
f.debug_tuple("Length").field(&self.inner).finish()
return write!(f, "Length([indefinite])");
}

f.debug_tuple("Length").field(&self.inner).finish()
}
}

Expand Down
7 changes: 0 additions & 7 deletions der/src/length/indefinite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,6 @@ use crate::Tag;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;

/// Octet identifying an indefinite length as described in X.690 Section
/// 8.1.3.6.1:
///
/// > The single octet shall have bit 8 set to one, and bits 7 to
/// > 1 set to zero.
pub(super) const INDEFINITE_LENGTH_OCTET: u8 = 0b10000000; // 0x80

/// The end-of-contents octets can be considered as the encoding of a value whose tag is
/// universal class, whose form is primitive, whose number of the tag is zero, and whose
/// contents are absent.
Expand Down
15 changes: 12 additions & 3 deletions der/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ mod position;

use crate::{
Decode, DecodeValue, Encode, EncodingRules, Error, ErrorKind, FixedTag, Header, Length, Tag,
TagMode, TagNumber, asn1::ContextSpecific, length::indefinite::read_eoc,
TagMode, TagNumber, asn1::ContextSpecific,
};

#[cfg(feature = "alloc")]
use alloc::vec::Vec;

#[cfg(feature = "ber")]
use crate::length::indefinite::read_eoc;

/// Reader trait which reads DER-encoded input.
pub trait Reader<'r>: Clone {
/// Get the [`EncodingRules`] which should be applied when decoding the input.
Expand Down Expand Up @@ -207,16 +210,22 @@ pub trait Reader<'r>: Clone {
///
/// This calls the provided function `f` with a nested reader created using
/// [`Reader::read_nested`].
pub(crate) fn read_value<'r, R, T, F, E>(reader: &mut R, mut header: Header, f: F) -> Result<T, E>
pub(crate) fn read_value<'r, R, T, F, E>(reader: &mut R, header: Header, f: F) -> Result<T, E>
where
R: Reader<'r>,
E: From<Error>,
F: FnOnce(&mut R, Header) -> Result<T, E>,
{
header.length = header.length.sans_eoc();
#[cfg(feature = "ber")]
let header = Header {
length: header.length.sans_eoc(),
tag: header.tag,
};

let ret = reader.read_nested(header.length, |r| f(r, header))?;

// Consume EOC marker if the length is indefinite.
#[cfg(feature = "ber")]
if header.length.is_indefinite() {
read_eoc(reader)?;
}
Expand Down
9 changes: 6 additions & 3 deletions der/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ mod number;

pub use self::{class::Class, mode::TagMode, number::TagNumber};

use crate::{
Decode, DerOrd, Encode, EncodingRules, Error, ErrorKind, Length, Reader, Result, Writer,
};
use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Writer};

#[cfg(feature = "ber")]
use crate::EncodingRules;

use core::{cmp::Ordering, fmt};

/// Indicator bit for constructed form encoding (i.e. vs primitive form)
Expand Down Expand Up @@ -191,6 +193,7 @@ impl Tag {
0x1A => Tag::VisibleString,
0x1B => Tag::GeneralString,
0x1E => Tag::BmpString,
#[cfg(feature = "ber")]
0x24 if reader.encoding_rules() == EncodingRules::Ber => Tag::OctetString,
0x30 => Tag::Sequence, // constructed
0x31 => Tag::Set, // constructed
Expand Down