diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 734d88d..b51d19b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,7 +66,7 @@ jobs: matrix: RUST: # MSRV - - VERSION: "1.41.0" + - VERSION: "1.47.0" FLAGS: "--no-default-features --features std,derive" - VERSION: stable FLAGS: "" diff --git a/asn1_derive/src/lib.rs b/asn1_derive/src/lib.rs index d56a4d8..7fd3375 100644 --- a/asn1_derive/src/lib.rs +++ b/asn1_derive/src/lib.rs @@ -1,5 +1,6 @@ extern crate proc_macro; +use syn::parse::Parser; use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::token::Comma; @@ -439,3 +440,55 @@ fn generate_enum_write_block(name: &syn::Ident, data: &syn::DataEnum) -> proc_ma } } } + +// TODO: Duplicate of this function in src/object_identifier.rs, can we +// de-dupe? +fn _write_base128_int(data: &mut Vec, n: u32) { + if n == 0 { + data.push(0); + return; + } + + let mut l = 0; + let mut i = n; + while i > 0 { + l += 1; + i >>= 7; + } + + for i in (0..l).rev() { + let mut o = (n >> (i * 7)) as u8; + o &= 0x7f; + if i != 0 { + o |= 0x80; + } + data.push(o); + } +} + +#[proc_macro] +pub fn oid(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let p_arcs = Punctuated::::parse_terminated + .parse(item) + .unwrap(); + let mut arcs = p_arcs.iter(); + + let mut der_encoded = vec![]; + let first = arcs.next().unwrap().base10_parse::().unwrap(); + let second = arcs.next().unwrap().base10_parse::().unwrap(); + _write_base128_int(&mut der_encoded, 40 * first + second); + for arc in arcs { + _write_base128_int(&mut der_encoded, arc.base10_parse().unwrap()); + } + + let der_len = der_encoded.len(); + // TODO: is there a way to use the `MAX_OID_LENGTH` constant here? + assert!(der_len <= 63); + der_encoded.resize(63, 0); + let der_lit = syn::LitByteStr::new(&der_encoded, proc_macro2::Span::call_site()); + let expanded = quote::quote! { + asn1::ObjectIdentifier::from_der_unchecked(*#der_lit, #der_len as u8) + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/src/lib.rs b/src/lib.rs index bb055aa..0c4a5d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ pub use crate::types::{Explicit, Implicit}; pub use crate::writer::{write, write_single, Writer}; #[cfg(feature = "derive")] -pub use asn1_derive::{Asn1Read, Asn1Write}; +pub use asn1_derive::{oid, Asn1Read, Asn1Write}; /// Decodes an `OPTIONAL` ASN.1 value which has a `DEFAULT`. Generaly called /// immediately after [`Parser::read_element`]. diff --git a/src/object_identifier.rs b/src/object_identifier.rs index 50a1df5..caf3586 100644 --- a/src/object_identifier.rs +++ b/src/object_identifier.rs @@ -1,36 +1,50 @@ -use alloc::borrow::Cow; +use crate::parser::{ParseError, ParseErrorKind, ParseResult}; use alloc::fmt; -use alloc::vec; -use alloc::vec::Vec; + +const MAX_OID_LENGTH: usize = 63; /// Represents an ASN.1 `OBJECT IDENTIFIER`. ObjectIdentifiers are opaque, the only thing may be /// done with them is test if they are equal to another `ObjectIdentifier`. The generally /// recommended practice for handling them is to create some `ObjectIdentifier` constants with -/// `ObjectIdentifier::from_string` and then compare ObjectIdentifiers you get from parsing to +/// `asn1::oid!()` and then compare ObjectIdentifiers you get from parsing to /// those. +/// +/// `asn1::oid!()` takes a series of arcs, for example: `asn1::oid!(1, 2, 3)`. +/// +/// rust-asn1 stores `ObjectIdentifier`s in a fixed-size buffer, therefore +/// they are limited to OID values whose DER encoding fits into that buffer. +/// This buffer is sufficiently large to fit all known publically known OIDs, +/// so this should not effect most people. #[derive(Debug, PartialEq, Eq, Clone, Hash)] -pub struct ObjectIdentifier<'a> { - // Store the OID as DER encoded. This means we can 0-copy on parse. - der_encoded: Cow<'a, [u8]>, +pub struct ObjectIdentifier { + // Store the OID as DER encoded. + der_encoded: [u8; MAX_OID_LENGTH], + der_encoded_len: u8, } -fn _read_base128_int>(mut reader: I) -> Option { +fn _read_base128_int>(mut reader: I) -> ParseResult { let mut ret = 0u32; for _ in 0..4 { - let b = reader.next()?; + let b = reader + .next() + .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidValue))?; ret <<= 7; ret |= u32::from(b & 0x7f); if b & 0x80 == 0 { - return Some(ret); + return Ok(ret); } } - None + Err(ParseError::new(ParseErrorKind::InvalidValue)) } -fn _write_base128_int(data: &mut Vec, n: u32) { +fn _write_base128_int(data: &mut [u8], data_len: &mut usize, n: u32) -> Option<()> { if n == 0 { - data.push(0); - return; + if *data_len >= data.len() { + return None; + } + data[*data_len] = 0; + *data_len += 1; + return Some(()); } let mut l = 0; @@ -46,13 +60,19 @@ fn _write_base128_int(data: &mut Vec, n: u32) { if i != 0 { o |= 0x80; } - data.push(o); + if *data_len >= data.len() { + return None; + } + data[*data_len] = o; + *data_len += 1; } + + Some(()) } -impl<'a> ObjectIdentifier<'a> { +impl ObjectIdentifier { /// Parses an OID from a dotted string, e.g. `"1.2.840.113549"`. - pub fn from_string(oid: &str) -> Option> { + pub fn from_string(oid: &str) -> Option { let mut parts = oid.split('.'); let first = parts.next()?.parse::().ok()?; @@ -61,42 +81,61 @@ impl<'a> ObjectIdentifier<'a> { return None; } - let mut der_data = vec![]; - _write_base128_int(&mut der_data, 40 * first + second); + let mut der_data = [0; MAX_OID_LENGTH]; + let mut der_data_len = 0; + _write_base128_int(&mut der_data, &mut der_data_len, 40 * first + second)?; for part in parts { - _write_base128_int(&mut der_data, part.parse::().ok()?); + _write_base128_int(&mut der_data, &mut der_data_len, part.parse::().ok()?)?; } Some(ObjectIdentifier { - der_encoded: Cow::Owned(der_data), + der_encoded: der_data, + der_encoded_len: der_data_len as u8, }) } /// Creates an `ObjectIdentifier` from its DER representation. This does /// not perform any allocations or copies. - pub fn from_der(data: &'a [u8]) -> Option> { + pub fn from_der(data: &[u8]) -> ParseResult { if data.is_empty() { - return None; + return Err(ParseError::new(ParseErrorKind::InvalidValue)); + } else if data.len() > MAX_OID_LENGTH { + return Err(ParseError::new(ParseErrorKind::OidTooLong)); } let mut cursor = data.iter().copied(); while cursor.len() > 0 { _read_base128_int(&mut cursor)?; } - Some(ObjectIdentifier { - der_encoded: Cow::Borrowed(data), + let mut storage = [0; MAX_OID_LENGTH]; + storage[..data.len()].copy_from_slice(data); + + Ok(ObjectIdentifier { + der_encoded: storage, + der_encoded_len: data.len() as u8, }) } + /// Creates an `ObjectIdentifier` from its DER representation. Does not + /// check that the DER is valid. Intended only for use from the `oid!()` + /// macro. Do not use yourself! + #[doc(hidden)] + pub const fn from_der_unchecked(data: [u8; MAX_OID_LENGTH], data_len: u8) -> ObjectIdentifier { + ObjectIdentifier { + der_encoded: data, + der_encoded_len: data_len, + } + } + pub(crate) fn as_der(&self) -> &[u8] { - &self.der_encoded + &self.der_encoded[..self.der_encoded_len as usize] } } -impl fmt::Display for ObjectIdentifier<'_> { +impl fmt::Display for ObjectIdentifier { /// Converts an `ObjectIdentifier` to a dotted string, e.g. /// "1.2.840.113549". fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut cursor = self.der_encoded.iter().copied(); + let mut cursor = self.as_der().iter().copied(); let first = _read_base128_int(&mut cursor).unwrap(); if first < 80 { @@ -116,7 +155,7 @@ impl fmt::Display for ObjectIdentifier<'_> { #[cfg(test)] mod tests { - use crate::ObjectIdentifier; + use crate::{ObjectIdentifier, ParseError, ParseErrorKind}; #[test] fn test_object_identifier_from_string() { @@ -131,6 +170,7 @@ mod tests { ".2.5", "2..5", "2.5.", + "1.3.6.1.4.1.1248.1.1.2.1.3.21.69.112.115.111.110.32.83.116.121.108.117.115.32.80.114.111.32.52.57.48.48.123.124412.31.213321.123.110.32.83.116.121.108.117.115.32.80.114.111.32.52.57.48.48.123.124412.31.213321.123", ] { assert_eq!(ObjectIdentifier::from_string(val), None); } @@ -147,6 +187,11 @@ mod tests { } } + #[test] + fn test_from_der() { + assert_eq!(ObjectIdentifier::from_der(b"\x06\x40\x2b\x06\x01\x04\x01\x89\x60\x01\x01\x02\x01\x03\x15\x45\x70\x73\x6f\x6e\x20\x53\x74\x79\x6c\x75\x73\x20\x50\x72\x6f\x20\x34\x39\x30\x30\x7b\x87\xcb\x7c\x1f\x8d\x82\x49\x7b\x2b\x06\x01\x04\x01\x89\x60\x01\x01\x02\x01\x03\x15\x45\x70\x73\x6f\x6e\x20"), Err(ParseError::new(ParseErrorKind::OidTooLong))); + } + #[test] fn test_to_string() { for val in &[ diff --git a/src/parser.rs b/src/parser.rs index ad54b63..c0f9fc4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -18,6 +18,9 @@ pub enum ParseErrorKind { InvalidSetOrdering, /// An OPTIONAL DEFAULT was written with a default value. EncodedDefault, + /// OID value is longer than the maximum size rust-asn1 can store. This is + /// a limitation of rust-asn1. + OidTooLong, } #[derive(Debug, PartialEq)] @@ -112,6 +115,10 @@ impl fmt::Display for ParseError { ParseErrorKind::ExtraData => write!(f, "extra data"), ParseErrorKind::InvalidSetOrdering => write!(f, "SET value was ordered incorrectly"), ParseErrorKind::EncodedDefault => write!(f, "DEFAULT value was explicitly encoded"), + ParseErrorKind::OidTooLong => write!( + f, + "OBJECT IDENTIFIER was too large to be stored in rust-asn1's buffer" + ), } } } @@ -688,7 +695,7 @@ mod tests { #[test] fn test_parse_object_identifier() { - assert_parses::>(&[ + assert_parses::(&[ ( Ok(ObjectIdentifier::from_string("2.5").unwrap()), b"\x06\x01\x55", diff --git a/src/types.rs b/src/types.rs index aec31a4..7ac6ab6 100644 --- a/src/types.rs +++ b/src/types.rs @@ -634,14 +634,13 @@ impl<'a> SimpleAsn1Writable<'a> for BigInt<'a> { } } -impl<'a> SimpleAsn1Readable<'a> for ObjectIdentifier<'a> { +impl<'a> SimpleAsn1Readable<'a> for ObjectIdentifier { const TAG: u8 = 0x06; - fn parse_data(data: &'a [u8]) -> ParseResult> { + fn parse_data(data: &'a [u8]) -> ParseResult { ObjectIdentifier::from_der(data) - .ok_or_else(|| ParseError::new(ParseErrorKind::InvalidValue)) } } -impl<'a> SimpleAsn1Writable<'a> for ObjectIdentifier<'a> { +impl<'a> SimpleAsn1Writable<'a> for ObjectIdentifier { const TAG: u8 = 0x06; fn write_data(&self, dest: &mut Vec) { dest.extend_from_slice(self.as_der()); diff --git a/tests/oid_tests.rs b/tests/oid_tests.rs new file mode 100644 index 0000000..e23bf29 --- /dev/null +++ b/tests/oid_tests.rs @@ -0,0 +1,23 @@ +#![cfg(feature = "derive")] + +#[test] +fn test_oid_value() { + assert_eq!( + asn1::oid!(1, 2, 3, 4), + asn1::ObjectIdentifier::from_string("1.2.3.4").unwrap() + ); +} + +#[test] +fn test_match_statement() { + const OID1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 3, 4); + const OID2: asn1::ObjectIdentifier = asn1::oid!(1, 2, 3, 5); + + let oid = asn1::ObjectIdentifier::from_string("1.2.3.4").unwrap(); + let result = match oid { + OID1 => "abc", + OID2 => "def", + _ => "ghi", + }; + assert_eq!(result, "abc"); +}