diff --git a/der/src/asn1/application.rs b/der/src/asn1/application.rs index dd0585ab4..452fc55d4 100644 --- a/der/src/asn1/application.rs +++ b/der/src/asn1/application.rs @@ -19,3 +19,30 @@ pub type ApplicationExplicitRef<'a, const TAG: u16, T> = /// Application class, reference, IMPLICIT pub type ApplicationImplicitRef<'a, const TAG: u16, T> = CustomClassImplicitRef<'a, TAG, T, CLASS_APPLICATION>; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use crate::{ + asn1::{context_specific::ContextSpecificExplicit, OctetStringRef}, + Decode, Encode, + }; + use hex_literal::hex; + + #[test] + fn round_trip() { + const EXAMPLE_BYTES: &[u8] = &hex!( + "A2 06" + "04 04" + "01020304" + ); + + let field = + ContextSpecificExplicit::<2, OctetStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); + assert_eq!(field.value, OctetStringRef::new(&[1, 2, 3, 4]).unwrap()); + + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, EXAMPLE_BYTES); + } +} diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index 50267ce8a..09320449a 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -22,28 +22,6 @@ pub type ContextSpecificExplicitRef<'a, const TAG: u16, T> = pub type ContextSpecificImplicitRef<'a, const TAG: u16, T> = CustomClassImplicitRef<'a, TAG, T, CLASS_CONTEXT_SPECIFIC>; -// pub fn decode_implicit<'a, R: Reader<'a>, T: Tagged + DecodeValue<'a>>( -// number: TagNumber, -// reader: &mut R, -// ) -> Result, T::Error> { -// match AnyCustomClassImplicit::decode_skipping(Class::ContextSpecific, number, reader) { -// Ok(Some(custom)) => Ok(Some(custom.value)), -// Ok(None) => Ok(None), -// Err(err) => Err(err), -// } -// } - -// pub fn decode_explicit<'a, R: Reader<'a>, T: Decode<'a>>( -// number: TagNumber, -// reader: &mut R, -// ) -> Result, T::Error> { -// match AnyCustomClassExplicit::decode_skipping(Class::ContextSpecific, number, reader) { -// Ok(Some(custom)) => Ok(Some(custom.value)), -// Ok(None) => Ok(None), -// Err(err) => Err(err), -// } -// } - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { diff --git a/der/src/asn1/private.rs b/der/src/asn1/private.rs index de9345526..648e92de1 100644 --- a/der/src/asn1/private.rs +++ b/der/src/asn1/private.rs @@ -80,7 +80,6 @@ mod tests { use crate::{asn1::BitStringRef, Decode, Encode, SliceReader}; use hex_literal::hex; - // Public key data from `pkcs8` crate's `ed25519-pkcs8-v2.der` const EXAMPLE_BYTES: &[u8] = &hex!("E123032100A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); diff --git a/der/src/tag.rs b/der/src/tag.rs index d750f7c73..c76f5d426 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -377,6 +377,7 @@ fn parse_parts<'a, R: Reader<'a>>(first_byte: u8, reader: &mut R) -> Result<(boo Err(Error::new(ErrorKind::TagNumberInvalid, reader.position())) } +/// Length of encoded tag depends only on number it encodes fn tag_length(tag_number: u16) -> Length { if tag_number <= 30 { Length::ONE @@ -389,6 +390,21 @@ fn tag_length(tag_number: u16) -> Length { } } +fn tag_class_number_bytes( + class: Class, + tag_number: TagNumber, + constructed: bool, + buf: &mut [u8; Tag::MAX_SIZE], +) -> &[u8] { + let mut first_byte = class as u8; + if constructed { + first_byte |= CONSTRUCTED_FLAG; + } + let num = tag_number.value(); + tag_number_bytes(first_byte, num, buf) +} + +/// Tag contains class bits, constructed flag and number #[allow(clippy::cast_possible_truncation)] fn tag_number_bytes(first_byte: u8, num: u16, buf: &mut [u8; Tag::MAX_SIZE]) -> &[u8] { if num <= 30 { @@ -418,14 +434,9 @@ impl Encode for Tag { } fn encode(&self, writer: &mut impl Writer) -> Result<()> { - let mut first_byte = self.class() as u8; - if self.is_constructed() { - first_byte |= CONSTRUCTED_FLAG; - } - let num = self.number().value(); - let mut buf = [0u8; Tag::MAX_SIZE]; - let tag_bytes = tag_number_bytes(first_byte, num, &mut buf); + let tag_bytes = + tag_class_number_bytes(self.class(), self.number(), self.is_constructed(), &mut buf); writer.write(tag_bytes) } } @@ -499,7 +510,10 @@ impl fmt::Display for Tag { impl fmt::Debug for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Tag(0x{:02x}: {})", self.number().value(), self) + let mut buf = [0u8; Tag::MAX_SIZE]; + let tag_bytes = + tag_class_number_bytes(self.class(), self.number(), self.is_constructed(), &mut buf); + write!(f, "Tag({:02X?}: {})", tag_bytes, self) } } diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 1c0828677..30dca887c 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -130,213 +130,6 @@ mod choice { #[asn1(context_specific = "2", type = "UTF8String")] Utf8String(String), } - // #[derive(Debug, Eq, PartialEq)] - // pub enum ImplicitChoice<'a> { - // BitString(BitStringRef<'a>), - // Time(GeneralizedTime), - // Utf8String(String), - // } - - // impl<'a> ::der::Choice<'a> for ImplicitChoice<'a> { - // fn can_decode(tag: ::der::Tag) -> bool { - // match tag { - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(0u16), - // } - // | ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(1u16), - // } - // | ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(2u16), - // } => true, - // _ => false, - // } - // } - // } - // impl<'a> ::der::Decode<'a> for ImplicitChoice<'a> { - // type Error = ::der::Error; - // fn decode>(reader: &mut R) -> ::der::Result { - // use der::Reader as _; - // match ::der::Tag::peek(reader)? { - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(0u16), - // } => { - // Ok( - // Self::BitString( - // ::der::asn1::ContextSpecificImplicit::< - // 0u16, - // ::der::asn1::BitStringRef, - // >::decode_skipping(reader)? - // .ok_or_else(|| { - // ::der::Tag::ContextSpecific { - // number: ::der::TagNumber(0u16), - // constructed: false, - // } - // .value_error() - // })? - // .value - // .try_into()?, - // ), - // ) - // } - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(1u16), - // } => { - // Ok( - // Self::Time( - // ::der::asn1::ContextSpecificImplicit::< - // 1u16, - // ::der::asn1::GeneralizedTime, - // >::decode_skipping(reader)? - // .ok_or_else(|| { - // ::der::Tag::ContextSpecific { - // number: ::der::TagNumber(1u16), - // constructed: false, - // } - // .value_error() - // })? - // .value - // .try_into()?, - // ), - // ) - // } - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(2u16), - // } => { - // Ok( - // Self::Utf8String( - // ::der::asn1::ContextSpecificImplicit::< - // 2u16, - // ::der::asn1::Utf8StringRef, - // >::decode_skipping(reader)? - // .ok_or_else(|| { - // ::der::Tag::ContextSpecific { - // number: ::der::TagNumber(2u16), - // constructed: false, - // } - // .value_error() - // })? - // .value - // .try_into()?, - // ), - // ) - // } - // actual => { - // Err( - // der::ErrorKind::TagUnexpected { - // expected: None, - // actual, - // } - // .into(), - // ) - // } - // } - // } - // } - // impl<'a> ::der::EncodeValue for ImplicitChoice<'a> { - // fn encode_value( - // &self, - // encoder: &mut impl ::der::Writer, - // ) -> ::der::Result<()> { - // match self { - // Self::BitString(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 0u16, - // ::der::asn1::BitStringRef, - // > { - // value: &variant.try_into()?, - // } - // .encode_value(encoder) - // } - // Self::Time(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 1u16, - // ::der::asn1::GeneralizedTime, - // > { - // value: &variant.try_into()?, - // } - // .encode_value(encoder) - // } - // Self::Utf8String(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 2u16, - // ::der::asn1::Utf8StringRef, - // > { - - // value: &variant.try_into()?, - // } - // .encode_value(encoder) - // } - // } - // } - // fn value_len(&self) -> ::der::Result<::der::Length> { - // match self { - // Self::BitString(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 0u16, - // ::der::asn1::BitStringRef, - // > { - // value: variant, - // } - // .value_len() - // } - // Self::Time(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 1u16, - // ::der::asn1::GeneralizedTime, - // > { - // value: variant, - // } - // .value_len() - // } - // Self::Utf8String(variant) => { - // ::der::asn1::ContextSpecificImplicitRef::< - // '_, - // 2u16, - // ::der::asn1::Utf8StringRef, - // > { - // value: &variant.try_into()?, - // } - // .value_len() - // } - // } - // } - // } - // impl<'a> ::der::Tagged for ImplicitChoice<'a> { - // fn tag(&self) -> ::der::Tag { - // match self { - // Self::BitString(_) => { - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(0u16), - // } - // } - // Self::Time(_) => { - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(1u16), - // } - // } - // Self::Utf8String(_) => { - // ::der::Tag::ContextSpecific { - // constructed: false, - // number: ::der::TagNumber(2u16), - // } - // } - // } - // } - // } impl<'a> ImplicitChoice<'a> { pub fn bit_string(&self) -> Option> { @@ -721,6 +514,138 @@ mod sequence { } } +#[cfg(all(feature = "derive", feature = "oid"))] +mod sequence_application { + use const_oid::ObjectIdentifier; + use der::{asn1::ApplicationImplicit, Decode, Encode, Sequence}; + use hex_literal::hex; + + const TACHO_CERT_DER: &[u8] = &hex!( + "7F 21 81 C8" // Application 33 + + "7F 4E 81 81" // Appliction 78 + + "5F 29" // Application 41 + "01 00" + "42 08" // Application 2 + "FD 45 43 20 01 FF FF 01" + "5F 4C 07" // Application 76 + "FF 53 4D 52 44 54 0E" + "7F 49 4D" // Application 73 + "06 08 2A 86 48 CE 3D 03 01 07 86 41 04 + 30 E8 EE D8 05 1D FB 8F 05 BF 4E 34 90 B8 A0 1C + 83 21 37 4E 99 41 67 70 64 28 23 A2 C9 E1 21 16 + D9 27 46 45 94 DD CB CC 79 42 B5 F3 EE 1A A3 AB + A2 5C E1 6B 20 92 00 F0 09 70 D9 CF 83 0A 33 4B" + + + "5F 20 08" // Application 32 + "17 47 52 20 02 FF FF 01" + "5F 25 04" // Application 37 + "62 A3 B0 D0" + "5F 24 04" // Application 36 + "6F F6 49 50" + "5F 37 40" // Application 55 + "6D 3E FD 97 + BE 83 EC 65 5F 51 4D 8C 47 60 DB FD 9B A2 D1 5D + 3C 1A 21 93 CE D7 EA F2 A2 0D 89 CC 4A 4F 0C 4B + E5 3F A3 F9 0F 20 B5 74 67 26 DB 19 9E FF DE 0B + D0 B9 2C B9 D1 5A E2 18 08 6C F0 E2" + ); + + /// EU Tachograph certificate + pub type TachographCertificate<'a> = ApplicationImplicit<33, TachographCertificateInner<'a>>; + + /// EU Tachograph certificate inner sequence + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct TachographCertificateInner<'a> { + /// constructed + #[asn1(application = "78")] + pub body: TachographCertificateBody<'a>, + + /// primitive + #[asn1(application = "55", type = "OCTET STRING")] + pub signature: &'a [u8], + } + + /// EU Tachograph certificate body + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + + pub struct TachographCertificateBody<'a> { + /// primitive + #[asn1(application = "41", type = "OCTET STRING")] + pub profile_identifier: &'a [u8], + + /// primitive + #[asn1(application = "2", type = "OCTET STRING")] + pub authority_reference: &'a [u8], + + /// primitive + #[asn1(application = "76", type = "OCTET STRING")] + pub holder_authorisation: &'a [u8], + + /// constructed + #[asn1(application = "73")] + pub public_key: CertificatePublicKey<'a>, + + /// primitive + #[asn1(application = "32", type = "OCTET STRING")] + pub holder_reference: &'a [u8], + + /// primitive + #[asn1(application = "37", type = "OCTET STRING")] + pub effective_date: &'a [u8], + + /// primitive + #[asn1(application = "36", type = "OCTET STRING")] + pub expiration_date: &'a [u8], + } + + /// EU Tachograph certificate public key + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + + pub struct CertificatePublicKey<'a> { + pub domain_parameters: ObjectIdentifier, + + #[asn1(context_specific = "6", type = "OCTET STRING")] + pub public_point: &'a [u8], + } + #[test] + fn decode_tacho_application_tags() { + let tacho_cert = TachographCertificate::from_der(TACHO_CERT_DER).unwrap(); + + let sig = tacho_cert.value.signature; + assert_eq!(&sig[..2], hex!("6D 3E")); + assert_eq!(tacho_cert.value.body.profile_identifier, &[0x00]); + assert_eq!( + tacho_cert.value.body.authority_reference, + hex!("FD 45 43 20 01 FF FF 01") + ); + assert_eq!( + tacho_cert.value.body.holder_authorisation, + hex!("FF 53 4D 52 44 54 0E") + ); + assert_eq!( + tacho_cert.value.body.public_key.domain_parameters, + ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") + ); + assert_eq!( + &tacho_cert.value.body.public_key.public_point[..4], + hex!("04 30 E8 EE") + ); + const GREECE: &[u8] = b"GR "; + assert_eq!(&tacho_cert.value.body.holder_reference[1..4], GREECE); + + // Re-encode + let mut buf = [0u8; 256]; + let encoded = tacho_cert.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, TACHO_CERT_DER); + } +} + mod infer_default { //! When another crate might define a PartialEq for another type, the use of //! `default="Default::default"` in the der derivation will not provide enough