From 424d412d56c23a51c567b0454d3dc69b74e5b7bd Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:27:17 +0100 Subject: [PATCH 1/4] der: add heapless:Vec conversions from/to OctetStringRef heapless::Vec is useful in derive(Sequence) fields in no-std environments --- der/Cargo.toml | 1 + der/src/asn1/octet_string.rs | 21 +++++ der/tests/derive.rs | 79 ------------------- der/tests/derive_heapless.rs | 145 +++++++++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 79 deletions(-) create mode 100644 der/tests/derive_heapless.rs diff --git a/der/Cargo.toml b/der/Cargo.toml index fbaf21bf4..450972297 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -25,6 +25,7 @@ flagset = { version = "0.4.6", optional = true } pem-rfc7468 = { version = "1.0.0-rc.1", optional = true, features = ["alloc"] } time = { version = "0.3.4", optional = true, default-features = false } zeroize = { version = "1.8", optional = true, default-features = false } +heapless = { version = "0.8", optional = true, default-features = false } [dev-dependencies] hex-literal = "1" diff --git a/der/src/asn1/octet_string.rs b/der/src/asn1/octet_string.rs index 76b2d1756..8813407f1 100644 --- a/der/src/asn1/octet_string.rs +++ b/der/src/asn1/octet_string.rs @@ -132,6 +132,27 @@ impl<'a, const N: usize> TryFrom> for [u8; N] { } } +#[cfg(feature = "heapless")] +impl<'a, const N: usize> TryFrom> for heapless::Vec { + type Error = Error; + + fn try_from(octet_string: OctetStringRef<'a>) -> Result { + octet_string + .as_bytes() + .try_into() + .map_err(|_| Tag::OctetString.length_error()) + } +} + +#[cfg(feature = "heapless")] +impl<'a, const N: usize> TryFrom<&'a heapless::Vec> for OctetStringRef<'a> { + type Error = Error; + + fn try_from(byte_vec: &'a heapless::Vec) -> Result { + OctetStringRef::new(byte_vec) + } +} + #[cfg(feature = "alloc")] pub use self::allocating::OctetString; diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 8198283b8..d2d677071 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -568,85 +568,6 @@ mod sequence { assert_eq!(obj, obj_decoded); } - #[derive(Sequence, Default, Eq, PartialEq, Debug)] - #[asn1(tag_mode = "IMPLICIT")] - pub struct TypeCheckArraysSequenceFieldAttributeCombinations { - #[asn1(type = "OCTET STRING", deref = "true")] - pub array_bytes: [u8; 2], - - #[asn1(type = "BIT STRING", deref = "true")] - pub array_bits: [u8; 2], - - #[asn1(type = "OCTET STRING", context_specific = "0", deref = "true")] - pub array_implicit_bytes: [u8; 2], - - #[asn1(type = "BIT STRING", context_specific = "1", deref = "true")] - pub array_implicit_bits: [u8; 2], - - #[asn1( - type = "OCTET STRING", - context_specific = "2", - tag_mode = "EXPLICIT", - deref = "true" - )] - pub array_explicit_bytes: [u8; 2], - - #[asn1( - type = "BIT STRING", - context_specific = "3", - tag_mode = "EXPLICIT", - deref = "true" - )] - pub array_explicit_bits: [u8; 2], - - #[asn1(type = "BIT STRING", context_specific = "4", optional = "true")] - pub array_optional_implicit_bits: Option<[u8; 2]>, - - #[asn1(type = "OCTET STRING", context_specific = "5", optional = "true")] - pub array_optional_implicit_bytes: Option<[u8; 2]>, - - #[asn1( - type = "BIT STRING", - context_specific = "6", - optional = "true", - tag_mode = "EXPLICIT" - )] - pub array_optional_explicit_bits: Option<[u8; 2]>, - - #[asn1( - type = "OCTET STRING", - context_specific = "7", - optional = "true", - tag_mode = "EXPLICIT" - )] - pub array_optional_explicit_bytes: Option<[u8; 2]>, - } - - #[test] - fn type_combinations_arrays_instance() { - let obj = TypeCheckArraysSequenceFieldAttributeCombinations { - array_bytes: [0xAA, 0xBB], - array_bits: [0xCC, 0xDD], - - array_implicit_bytes: [0, 1], - array_implicit_bits: [2, 3], - - array_explicit_bytes: [4, 5], - array_explicit_bits: [6, 7], - - array_optional_implicit_bits: Some([8, 9]), - array_optional_implicit_bytes: Some([10, 11]), - - array_optional_explicit_bits: Some([12, 13]), - array_optional_explicit_bytes: Some([14, 15]), - }; - - let der_encoded = obj.to_der().unwrap(); - let obj_decoded = - TypeCheckArraysSequenceFieldAttributeCombinations::from_der(&der_encoded).unwrap(); - assert_eq!(obj, obj_decoded); - } - #[derive(Sequence)] #[asn1(error = CustomError)] pub struct TypeWithCustomError { diff --git a/der/tests/derive_heapless.rs b/der/tests/derive_heapless.rs new file mode 100644 index 000000000..840675373 --- /dev/null +++ b/der/tests/derive_heapless.rs @@ -0,0 +1,145 @@ +//! Tests for custom derive support. Without alloc feature. +//! +//! # Debugging with `cargo expand` +//! +//! To expand the Rust code generated by the proc macro when debugging +//! issues related to these tests, run: +//! +//! $ cargo expand --test derive_heapless --all-features + +#![cfg(all(feature = "derive"))] + +/// Custom derive test cases for the `Sequence` macro with heapless crate, without alloc. +mod sequence { + use der::Decode; + use der::Encode; + use der::Sequence; + + #[derive(Sequence, Default, Eq, PartialEq, Debug)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct TypeCheckArraysSequenceFieldAttributeCombinations { + #[asn1(type = "OCTET STRING", deref = "true")] + pub array_bytes: [u8; 2], + + #[asn1(type = "BIT STRING", deref = "true")] + pub array_bits: [u8; 2], + + #[asn1(type = "OCTET STRING", context_specific = "0", deref = "true")] + pub array_implicit_bytes: [u8; 2], + + #[asn1(type = "BIT STRING", context_specific = "1", deref = "true")] + pub array_implicit_bits: [u8; 2], + + #[asn1( + type = "OCTET STRING", + context_specific = "2", + tag_mode = "EXPLICIT", + deref = "true" + )] + pub array_explicit_bytes: [u8; 2], + + #[asn1( + type = "BIT STRING", + context_specific = "3", + tag_mode = "EXPLICIT", + deref = "true" + )] + pub array_explicit_bits: [u8; 2], + + #[asn1(type = "BIT STRING", context_specific = "4", optional = "true")] + pub array_optional_implicit_bits: Option<[u8; 2]>, + + #[asn1(type = "OCTET STRING", context_specific = "5", optional = "true")] + pub array_optional_implicit_bytes: Option<[u8; 2]>, + + #[asn1( + type = "BIT STRING", + context_specific = "6", + optional = "true", + tag_mode = "EXPLICIT" + )] + pub array_optional_explicit_bits: Option<[u8; 2]>, + + #[asn1( + type = "OCTET STRING", + context_specific = "7", + optional = "true", + tag_mode = "EXPLICIT" + )] + pub array_optional_explicit_bytes: Option<[u8; 2]>, + } + + #[test] + fn type_combinations_arrays_instance() { + let mut buf = [0u8; 100]; + let obj = TypeCheckArraysSequenceFieldAttributeCombinations { + array_bytes: [0xAA, 0xBB], + array_bits: [0xCC, 0xDD], + + array_implicit_bytes: [0, 1], + array_implicit_bits: [2, 3], + + array_explicit_bytes: [4, 5], + array_explicit_bits: [6, 7], + + array_optional_implicit_bits: Some([8, 9]), + array_optional_implicit_bytes: Some([10, 11]), + + array_optional_explicit_bits: Some([12, 13]), + array_optional_explicit_bytes: Some([14, 15]), + }; + + let der_encoded = obj.encode_to_slice(&mut buf).unwrap(); + let obj_decoded = + TypeCheckArraysSequenceFieldAttributeCombinations::from_der(&der_encoded).unwrap(); + assert_eq!(obj, obj_decoded); + } +} +/// Custom derive test cases for the `Sequence` macro with heapless crate. +#[cfg(all(feature = "oid", feature = "heapless"))] +mod sequence_heapless_vec { + use der::Decode; + use der::Encode; + use der::Sequence; + use der::asn1::ObjectIdentifier; + use hex_literal::hex; + + /// EU Tachograph Gen 2 certificate public key + #[derive(Sequence, Debug, Clone)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct G2CertificatePublicKey { + pub domain_parameters: ObjectIdentifier, + + /// Public point is '04 xx .. xx yy .. yy' + /// + /// Maximum: 1 + 2 * 66 bytes for Nist521 + #[asn1(context_specific = "6", type = "OCTET STRING", deref = "true")] + pub public_point: heapless::Vec, + } + + static ROMANIA_PUBLIC_KEY: [u8; 80] = hex!( + " + 30 4E 06 09 2B 24 03 03 02 08 01 01 07 86 41 +04 86 23 90 68 B8 08 1F 5E 6B 49 EB 54 F7 8E 0D +FB E7 1F 85 26 15 60 73 7C 24 B0 10 24 F9 2A 02 +03 67 80 3E 17 E9 F2 6E D5 A5 4E 25 F9 B5 46 B1 +90 3C DD 76 47 DB 1D 8E E0 D9 86 D0 64 D8 3C DD +7F" + ); + + pub const BRAINPOOL_P_256_R_1: ObjectIdentifier = + ObjectIdentifier::new_unwrap("1.3.36.3.3.2.8.1.1.7"); + + #[test] + fn heapless_vec_decode_encode() { + let public_key = G2CertificatePublicKey::from_der(&ROMANIA_PUBLIC_KEY).unwrap(); + + assert_eq!(public_key.domain_parameters, BRAINPOOL_P_256_R_1); + assert_eq!(public_key.public_point.len(), 1 + 2 * (256 / 8)); + + let mut buf = [0u8; 80]; + let pk_encoded = public_key.encode_to_slice(&mut buf).unwrap(); + + assert_eq!(pk_encoded, ROMANIA_PUBLIC_KEY); + } +} From 0a8a39321eeaed577cabd5857bd2866bd70326ca Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:46:12 +0100 Subject: [PATCH 2/4] fix clippy --- der/tests/derive_heapless.rs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/der/tests/derive_heapless.rs b/der/tests/derive_heapless.rs index 840675373..17aa9c3d3 100644 --- a/der/tests/derive_heapless.rs +++ b/der/tests/derive_heapless.rs @@ -7,7 +7,9 @@ //! //! $ cargo expand --test derive_heapless --all-features -#![cfg(all(feature = "derive"))] +#![cfg(feature = "derive")] +// TODO: fix needless_question_mark in the derive crate +#![allow(clippy::needless_question_mark)] /// Custom derive test cases for the `Sequence` macro with heapless crate, without alloc. mod sequence { @@ -91,7 +93,7 @@ mod sequence { let der_encoded = obj.encode_to_slice(&mut buf).unwrap(); let obj_decoded = - TypeCheckArraysSequenceFieldAttributeCombinations::from_der(&der_encoded).unwrap(); + TypeCheckArraysSequenceFieldAttributeCombinations::from_der(der_encoded).unwrap(); assert_eq!(obj, obj_decoded); } } @@ -142,4 +144,24 @@ FB E7 1F 85 26 15 60 73 7C 24 B0 10 24 F9 2A 02 assert_eq!(pk_encoded, ROMANIA_PUBLIC_KEY); } + + #[derive(Sequence, Debug, Clone)] + pub struct HeaplessTypeCheck { + #[asn1(type = "OCTET STRING", deref = "true")] + pub octet_string_heapless: heapless::Vec, + + #[asn1(type = "OCTET STRING", deref = "true", tag_mode = "IMPLICIT")] + pub octet_string_heapless_implicit: heapless::Vec, + + #[asn1(context_specific = "0", type = "OCTET STRING", optional = "true")] + pub opt_octet_string_heapless: Option>, + + #[asn1( + context_specific = "1", + type = "OCTET STRING", + optional = "true", + tag_mode = "IMPLICIT" + )] + pub opt_octet_string_heapless_implicit: Option>, + } } From 44447c96d506563de0ff216333d6df5fff449393 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Mon, 24 Mar 2025 12:39:23 +0100 Subject: [PATCH 3/4] docs: derive_heapless --- der/tests/derive_heapless.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/der/tests/derive_heapless.rs b/der/tests/derive_heapless.rs index 17aa9c3d3..82f0616f1 100644 --- a/der/tests/derive_heapless.rs +++ b/der/tests/derive_heapless.rs @@ -11,7 +11,7 @@ // TODO: fix needless_question_mark in the derive crate #![allow(clippy::needless_question_mark)] -/// Custom derive test cases for the `Sequence` macro with heapless crate, without alloc. +/// Custom derive test cases for the `Sequence` macro, without alloc. mod sequence { use der::Decode; use der::Encode; From 66eea156ddedeec23358d835a029f8c15e7c9755 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Tue, 25 Mar 2025 08:49:22 +0100 Subject: [PATCH 4/4] rename tests to derive_no_alloc.rs --- der/tests/{derive_heapless.rs => derive_no_alloc.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename der/tests/{derive_heapless.rs => derive_no_alloc.rs} (100%) diff --git a/der/tests/derive_heapless.rs b/der/tests/derive_no_alloc.rs similarity index 100% rename from der/tests/derive_heapless.rs rename to der/tests/derive_no_alloc.rs