Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions core-modules/asn1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ default = []
[dependencies]
regex = "1.12.2"
thiserror = "2.0"
zeroize = { version = "1.8.1", features = ["zeroize_derive"] }
19 changes: 10 additions & 9 deletions core-modules/asn1/src/cv_certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,13 @@ fn parse_date_bytes(bytes: &[u8]) -> Result<CertificateDate, Asn1DecoderError> {
mod tests {
use super::*;
use crate::encoder::Asn1Encoder;
use crate::maybe_zeroing_vec::VecOfU8;
use crate::tag::TagNumberExt;

type EncResult = Result<(), crate::error::Asn1EncoderError>;

fn build_test_certificate(with_extensions: bool) -> Vec<u8> {
Asn1Encoder::write::<crate::error::Asn1EncoderError>(|w| {
fn build_test_certificate(with_extensions: bool) -> VecOfU8 {
Asn1Encoder::write_nonzeroizing::<crate::error::Asn1EncoderError>(|w| {
w.write_tagged_object(TAG_CV_CERTIFICATE.application_tag().constructed(), |cert| -> EncResult {
cert.write_tagged_object(TAG_CERTIFICATE_BODY.application_tag().constructed(), |body| -> EncResult {
body.write_tagged_object(TAG_PROFILE_IDENTIFIER.application_tag(), |field| -> EncResult {
Expand Down Expand Up @@ -401,7 +402,7 @@ mod tests {
chr: &[u8],
effective: &[u8],
expiration: &[u8],
) -> Vec<u8> {
) -> VecOfU8 {
build_certificate_with_fields_and_signature(profile_bytes, car, chr, effective, expiration, &[0x00])
}

Expand All @@ -412,8 +413,8 @@ mod tests {
effective: &[u8],
expiration: &[u8],
signature: &[u8],
) -> Vec<u8> {
Asn1Encoder::write::<crate::error::Asn1EncoderError>(|w| {
) -> VecOfU8 {
Asn1Encoder::write_nonzeroizing::<crate::error::Asn1EncoderError>(|w| {
w.write_tagged_object(TAG_CV_CERTIFICATE.application_tag().constructed(), |cert| -> EncResult {
cert.write_tagged_object(TAG_CERTIFICATE_BODY.application_tag().constructed(), |body| -> EncResult {
body.write_tagged_object(TAG_PROFILE_IDENTIFIER.application_tag(), |field| -> EncResult {
Expand Down Expand Up @@ -472,8 +473,8 @@ mod tests {
.expect("encoding must succeed")
}

fn build_certificate_with_chat_data(chat_data: &[u8]) -> Vec<u8> {
Asn1Encoder::write::<crate::error::Asn1EncoderError>(|w| {
fn build_certificate_with_chat_data(chat_data: &[u8]) -> VecOfU8 {
Asn1Encoder::write_nonzeroizing::<crate::error::Asn1EncoderError>(|w| {
w.write_tagged_object(TAG_CV_CERTIFICATE.application_tag().constructed(), |cert| -> EncResult {
cert.write_tagged_object(TAG_CERTIFICATE_BODY.application_tag().constructed(), |body| -> EncResult {
body.write_tagged_object(TAG_PROFILE_IDENTIFIER.application_tag(), |field| -> EncResult {
Expand Down Expand Up @@ -532,8 +533,8 @@ mod tests {
.expect("encoding must succeed")
}

fn build_certificate_with_public_key(public_key: &[u8]) -> Vec<u8> {
Asn1Encoder::write::<crate::error::Asn1EncoderError>(|w| {
fn build_certificate_with_public_key(public_key: &[u8]) -> VecOfU8 {
Asn1Encoder::write_nonzeroizing::<crate::error::Asn1EncoderError>(|w| {
w.write_tagged_object(TAG_CV_CERTIFICATE.application_tag().constructed(), |cert| -> EncResult {
cert.write_tagged_object(TAG_CERTIFICATE_BODY.application_tag().constructed(), |body| -> EncResult {
body.write_tagged_object(TAG_PROFILE_IDENTIFIER.application_tag(), |field| -> EncResult {
Expand Down
12 changes: 6 additions & 6 deletions core-modules/asn1/src/date_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ mod tests {
#[test]
fn write_utc_time_basic_z() {
let value = Asn1Time::Utc(Asn1UtcTime::new(2025, 1, 1, 12, 0, None, None).unwrap());
let out = crate::encoder::Asn1Encoder::write(|w| -> Result<(), Asn1EncoderError> {
let out = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| -> Result<(), Asn1EncoderError> {
w.write_utc_time(&value)?;
Ok(())
})
Expand All @@ -542,7 +542,7 @@ mod tests {
let value = Asn1Time::Utc(
Asn1UtcTime::new(1999, 12, 31, 23, 59, Some(58), Some(Asn1Offset::utc_offset(2, 30).unwrap())).unwrap(),
);
let out = crate::encoder::Asn1Encoder::write(|w| -> Result<(), Asn1EncoderError> {
let out = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| -> Result<(), Asn1EncoderError> {
w.write_utc_time(&value)?;
Ok(())
})
Expand Down Expand Up @@ -605,7 +605,7 @@ mod tests {
)
.unwrap(),
);
let out = crate::encoder::Asn1Encoder::write(|w| -> Result<(), Asn1EncoderError> {
let out = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| -> Result<(), Asn1EncoderError> {
w.write_generalized_time(&value)?;
Ok(())
})
Expand Down Expand Up @@ -721,14 +721,14 @@ mod tests {
let utc = Asn1Time::utc(2024, 1, 1, 0, 0, None, None).unwrap();
let gen = Asn1Time::generalized(2024, 1, 1, 0, None, None, None, None).unwrap();

let err = crate::encoder::Asn1Encoder::write(|w| w.write_generalized_time(&utc)).unwrap_err();
let err = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| w.write_generalized_time(&utc)).unwrap_err();
assert!(matches!(
err,
Asn1EncoderError::TimeTypeMismatch { expected, actual }
if expected == crate::error::Asn1TimeType::Generalized && actual == crate::error::Asn1TimeType::Utc
));

let err = crate::encoder::Asn1Encoder::write(|w| w.write_utc_time(&gen)).unwrap_err();
let err = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| w.write_utc_time(&gen)).unwrap_err();
assert!(matches!(
err,
Asn1EncoderError::TimeTypeMismatch { expected, actual }
Expand All @@ -739,7 +739,7 @@ mod tests {
#[test]
fn write_generalized_time_without_optional_parts() {
let value = Asn1Time::generalized(2024, 6, 15, 8, None, None, None, None).unwrap();
let out = crate::encoder::Asn1Encoder::write(|w| -> Result<(), Asn1EncoderError> {
let out = crate::encoder::Asn1Encoder::write_nonzeroizing(|w| -> Result<(), Asn1EncoderError> {
w.write_generalized_time(&value)?;
Ok(())
})
Expand Down
95 changes: 62 additions & 33 deletions core-modules/asn1/src/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,24 @@
use crate::error::Asn1EncoderError;
use crate::oid::ObjectIdentifier;
use crate::tag::{Asn1Class, Asn1Form, Asn1Id, UniversalTag};
use crate::maybe_zeroing_vec::VecOfU8;

/// ASN.1 encoder for encoding data in ASN.1 format.
pub struct Asn1Encoder;

/// Scope for writing ASN.1 encoded data. (Top-level, not nested)
pub struct WriterScope {
// TODO: support zeroizing vec
buffer: Vec<u8>,
pub struct WriterScope
{
buffer: VecOfU8,
}

impl Default for WriterScope {
fn default() -> Self {
Self::new()
impl WriterScope {
pub fn new_with_nonzeroizing_buffer() -> Self {
Self { buffer: VecOfU8::new_nonzeroizing(Vec::<u8>::new()) }
}
}

impl WriterScope {
pub fn new() -> Self {
Self { buffer: Vec::new() }
pub fn new_with_zeroizing_buffer() -> Self {
Self { buffer: VecOfU8::new_zeroizing(Vec::<u8>::new()) }
}

pub fn buffer(&self) -> &[u8] {
Expand Down Expand Up @@ -143,7 +142,14 @@ impl WriterScope {
// tag
self.write_tag(id.number, id.class, id.form);
// scope
let mut scope = WriterScope::new();
let mut scope = match self.buffer {
VecOfU8::NonZeroizing(_) => {
WriterScope::new_with_nonzeroizing_buffer()
}
VecOfU8::Zeroizing(_) => {
WriterScope::new_with_zeroizing_buffer()
}
};
block(&mut scope)?;
// length + value
self.write_scope(&scope);
Expand Down Expand Up @@ -247,17 +253,40 @@ impl WriterScope {
}

impl Asn1Encoder {
/// Encodes the given block of code and returns the resulting byte array.
pub fn write<E>(block: impl FnOnce(&mut WriterScope) -> Result<(), E>) -> Result<Vec<u8>, E>
/// Encodes the given block of code and returns the resulting VecOfU8::NonZeroizing.
pub fn write_nonzeroizing<E>(
block: impl FnOnce(&mut WriterScope) -> Result<(), E>
) -> Result<VecOfU8, E>
where
E: From<Asn1EncoderError>,
{
Asn1Encoder::write(WriterScope::new_with_nonzeroizing_buffer(), block)
}

/// Encodes the given block of code and returns the resulting VecOfU8::Zeroizing.
pub fn write_zeroizing<E>(
block: impl FnOnce(&mut WriterScope) -> Result<(), E>
) -> Result<VecOfU8, E>
where
E: From<Asn1EncoderError>,
{
Asn1Encoder::write(WriterScope::new_with_zeroizing_buffer(), block)
}

/// Encodes the given block of code and returns the resulting VecOfU8.
fn write<E>(
mut scope: WriterScope,
block: impl FnOnce(&mut WriterScope) -> Result<(), E>,
) -> Result<VecOfU8, E>
where
E: From<Asn1EncoderError>,
{
let mut scope = WriterScope::new();
block(&mut scope)?;
Ok(scope.buffer)
}
}


#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -277,7 +306,7 @@ mod tests {

#[test]
fn write_multi_byte_tag_small_value() {
let result = Asn1Encoder::write(|w| {
let result = Asn1Encoder::write_nonzeroizing(|w| {
w.write_tagged_object(33u8.application_tag(), |inner| -> Result<(), Asn1EncoderError> {
inner.write_byte(0x05);
Ok(())
Expand All @@ -289,7 +318,7 @@ mod tests {

#[test]
fn write_multi_byte_tag_larger_value() {
let result = Asn1Encoder::write(|w| {
let result = Asn1Encoder::write_nonzeroizing(|w| {
w.write_tagged_object(128u32.application_tag(), |inner| -> Result<(), Asn1EncoderError> {
inner.write_byte(0x05);
Ok(())
Expand All @@ -301,7 +330,7 @@ mod tests {

#[test]
fn write_multi_byte_tag_max_single_byte() {
let result = Asn1Encoder::write::<Asn1EncoderError>(|w| {
let result = Asn1Encoder::write_nonzeroizing::<Asn1EncoderError>(|w| {
w.write_tagged_object(30u8.application_tag(), |inner| {
inner.write_byte(0x05);
Ok(())
Expand All @@ -313,7 +342,7 @@ mod tests {

#[test]
fn write_multi_byte_length() {
let result = Asn1Encoder::write::<Asn1EncoderError>(|w| {
let result = Asn1Encoder::write_nonzeroizing::<Asn1EncoderError>(|w| {
w.write_length(123_456_789u64);
Ok(())
})
Expand All @@ -323,66 +352,66 @@ mod tests {

#[test]
fn write_base128_encodes_expected() {
let mut single = WriterScope::new();
let mut single = WriterScope::new_with_nonzeroizing_buffer();
single.write_base128(0x7F);
assert_eq!(single.buffer(), &[0x7F]);

let mut multi = WriterScope::new();
let mut multi = WriterScope::new_with_nonzeroizing_buffer();
multi.write_base128(0x80);
assert_eq!(multi.buffer(), &[0x81, 0x00]);
}

#[test]
fn write_int_expected() {
let result = Asn1Encoder::write(|w| w.write_asn1_int(123_456)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_int(123_456)).unwrap();
assert_eq!(hex(&result), "02 03 01 E2 40");
}

#[test]
fn write_int_zero() {
let result = Asn1Encoder::write(|w| w.write_asn1_int(0)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_int(0)).unwrap();
assert_eq!(hex(&result), "02 01 00");
}

#[test]
fn write_int_negative() {
let result = Asn1Encoder::write(|w| w.write_asn1_int(-123)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_int(-123)).unwrap();
assert_eq!(hex(&result), "02 01 85");
}

#[test]
fn write_utf8_string_expected() {
let result = Asn1Encoder::write(|w| w.write_asn1_utf8_string("hello")).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_utf8_string("hello")).unwrap();
assert_eq!(hex(&result), "0C 05 68 65 6C 6C 6F");
}

#[test]
fn write_utf8_string_empty() {
let result = Asn1Encoder::write(|w| w.write_asn1_utf8_string("")).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_utf8_string("")).unwrap();
assert_eq!(hex(&result), "0C 00");
}

#[test]
fn write_boolean_true() {
let result = Asn1Encoder::write(|w| w.write_asn1_boolean(true)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_boolean(true)).unwrap();
assert_eq!(hex(&result), "01 01 FF");
}

#[test]
fn write_boolean_false() {
let result = Asn1Encoder::write(|w| w.write_asn1_boolean(false)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_boolean(false)).unwrap();
assert_eq!(hex(&result), "01 01 00");
}

#[test]
fn write_bit_string_invalid_unused_bits() {
let err = Asn1Encoder::write(|w| w.write_asn1_bit_string(&[0xFF], 9)).unwrap_err();
let err = Asn1Encoder::write_nonzeroizing(|w| w.write_asn1_bit_string(&[0xFF], 9)).unwrap_err();
assert!(matches!(err, Asn1EncoderError::InvalidUnusedBitCount { .. }));
}

#[test]
fn write_with_nested_tags() {
let result = Asn1Encoder::write(|w| {
let result = Asn1Encoder::write_nonzeroizing(|w| {
// Universal constructed SEQUENCE (0x10 with constructed bit)
w.write_tagged_object(0x10u8.universal_tag().constructed(), |inner| {
inner.write_asn1_int(42)?;
Expand All @@ -396,28 +425,28 @@ mod tests {
#[test]
fn write_oid_simple() {
let oid = ObjectIdentifier::parse("1.2.840.113549").unwrap();
let result = Asn1Encoder::write(|w| w.write_object_identifier(&oid)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_object_identifier(&oid)).unwrap();
assert_eq!(hex(&result), "06 06 2A 86 48 86 F7 0D");
}

#[test]
fn write_oid_single_part_beyond_40() {
let oid = ObjectIdentifier::parse("2.100.3").unwrap();
let result = Asn1Encoder::write(|w| w.write_object_identifier(&oid)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_object_identifier(&oid)).unwrap();
assert_eq!(hex(&result), "06 03 81 34 03");
}

#[test]
fn write_oid_long_identifier() {
let oid = ObjectIdentifier::parse("1.2.3.4.5.265566").unwrap();
let result = Asn1Encoder::write(|w| w.write_object_identifier(&oid)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_object_identifier(&oid)).unwrap();
assert_eq!(hex(&result), "06 07 2A 03 04 05 90 9A 5E");
}

#[test]
fn write_oid_large_first_component() {
let oid = ObjectIdentifier::parse("2.999.1").unwrap();
let result = Asn1Encoder::write(|w| w.write_object_identifier(&oid)).unwrap();
let result = Asn1Encoder::write_nonzeroizing(|w| w.write_object_identifier(&oid)).unwrap();
assert_eq!(hex(&result), "06 03 88 37 01");
}

Expand Down
1 change: 1 addition & 0 deletions core-modules/asn1/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ pub mod date_time;
pub mod decoder;
pub mod encoder;
pub mod error;
pub mod maybe_zeroing_vec;
pub mod oid;
pub mod tag;
Loading
Loading