//! Object ID (OID) representations. //! //! The parser does not copy oids when parsing. The [Oid struct](struct.Oid.html) //! only has a reference to the DER encoded form of the oid. //! //! # The `der_parser::oid!` macro //! //! Since the DER encoded oids are not very readable we provide a //! procedural macro `oid!`. The macro can be used the following ways: //! //! - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>` //! - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>` //! - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array. //! //! # Comparing oids //! //! Comparing a parsed oid to a static oid is probably the most common //! thing done with oids in your code. The `oid!` macro can be used in expression positions for //! this purpose. For example //! ``` //! use der_parser::{oid, oid::Oid}; //! //! # let some_oid: Oid<'static> = oid!(1.2.456); //! const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456); //! assert_eq!(some_oid, SOME_STATIC_OID) //! ``` //! To get a relative Oid use `oid!(rel 1.2)`. //! //! Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727)) //! and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434)) //! the `oid` macro can not directly be used in patterns, also not through constants. //! You can do this, though: //! ``` //! # use der_parser::{oid, oid::Oid}; //! # let some_oid: Oid<'static> = oid!(1.2.456); //! const SOME_OID: Oid<'static> = oid!(1.2.456); //! if some_oid == SOME_OID || some_oid == oid!(1.2.456) { //! println!("match"); //! } //! //! // Alternatively, compare the DER encoded form directly: //! const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456); //! match some_oid.bytes() { //! SOME_OID_RAW => println!("match"), //! _ => panic!("no match"), //! } //! ``` //! *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An //! extra check might be necessary. use alloc::borrow::Cow; use alloc::fmt; use alloc::str::FromStr; use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::convert::From; use core::iter::{ExactSizeIterator, FusedIterator, Iterator}; use core::ops::Shl; #[cfg(feature = "bigint")] use num_bigint::BigUint; use num_traits::Num; #[cfg(not(feature = "std"))] use alloc::format; #[derive(Debug)] pub enum ParseError { TooShort, /// Signalizes that the first or second component is too large. /// The first must be within the range 0 to 6 (inclusive). /// The second component must be less than 40. FirstComponentsTooLarge, ParseIntError, } /// Object ID (OID) representation which can be relative or non-relative. /// An example for an oid in string representation is "1.2.840.113549.1.1.5". /// /// For non-relative oids restrictions apply to the first two components. /// /// This library contains a procedural macro `oid` which can be used to /// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)` /// for relative oids. See the [module documentation](index.html) for more information. #[derive(Hash, PartialEq, Eq, Clone)] pub struct Oid<'a> { asn1: Cow<'a, [u8]>, pub relative: bool, } fn encode_relative(ids: &'_ [u64]) -> impl Iterator + '_ { ids.iter() .map(|id| { let bit_count = 64 - id.leading_zeros(); let octets_needed = ((bit_count + 6) / 7).max(1); (0..octets_needed).map(move |i| { let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 }; ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag }) }) .flatten() } impl<'a> Oid<'a> { /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html) /// for other ways to create oids. pub const fn new(asn1: Cow<'a, [u8]>) -> Oid { Oid { asn1, relative: false, } } /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html) /// for other ways to create relative oids. pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid { Oid { asn1, relative: true, } } /// Build an OID from an array of object identifier components. /// This method allocates memory on the heap. // we do not use .copied() for compatibility with 1.34 #[allow(clippy::map_clone)] pub fn from<'b>(s: &'b [u64]) -> Result, ParseError> { if s.len() < 2 { if s.len() == 1 && s[0] == 0 { return Ok(Oid { asn1: Cow::Borrowed(&[0]), relative: false, }); } return Err(ParseError::TooShort); } if s[0] >= 7 || s[1] >= 40 { return Err(ParseError::FirstComponentsTooLarge); } let asn1_encoded: Vec = [(s[0] * 40 + s[1]) as u8] .iter() .map(|&x| x) .chain(encode_relative(&s[2..])) .collect(); Ok(Oid { asn1: Cow::from(asn1_encoded), relative: false, }) } /// Build a relative OID from an array of object identifier components. pub fn from_relative<'b>(s: &'b [u64]) -> Result, ParseError> { if s.is_empty() { return Err(ParseError::TooShort); } let asn1_encoded: Vec = encode_relative(s).collect(); Ok(Oid { asn1: Cow::from(asn1_encoded), relative: true, }) } /// Create a deep copy of the oid. /// /// This method allocates data on the heap. The returned oid /// can be used without keeping the ASN.1 representation around. /// /// Cloning the returned oid does again allocate data. pub fn to_owned(&self) -> Oid<'static> { Oid { asn1: Cow::from(self.asn1.to_vec()), relative: self.relative, } } /// Get the encoded oid without the header. pub fn bytes(&self) -> &[u8] { self.asn1.as_ref() } /// Convert the OID to a string representation. /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5" #[cfg(feature = "bigint")] pub fn to_id_string(&self) -> String { let ints: Vec = self.iter_bigint().map(|i| i.to_string()).collect(); ints.join(".") } #[cfg(not(feature = "bigint"))] /// Convert the OID to a string representation. /// /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5" /// is returned, otherwise a hex representation. /// /// See also the "bigint" feature of this crate. pub fn to_id_string(&self) -> String { if let Some(arcs) = self.iter() { let ints: Vec = arcs.map(|i| i.to_string()).collect(); ints.join(".") } else { let mut ret = String::with_capacity(self.asn1.len() * 3); for (i, o) in self.asn1.iter().enumerate() { ret.push_str(&format!("{:02x}", o)); if i + 1 != self.asn1.len() { ret.push(' '); } } ret } } /// Return an iterator over the sub-identifiers (arcs). #[cfg(feature = "bigint")] pub fn iter_bigint( &'_ self, ) -> impl Iterator + FusedIterator + ExactSizeIterator + '_ { SubIdentifierIterator { oid: self, pos: 0, first: false, n: core::marker::PhantomData, } } /// Return an iterator over the sub-identifiers (arcs). /// Returns `None` if at least one arc does not fit into `u64`. pub fn iter( &'_ self, ) -> Option + FusedIterator + ExactSizeIterator + '_> { // Check that every arc fits into u64 let bytes = if self.relative { &self.asn1 } else if self.asn1.is_empty() { &[] } else { &self.asn1[1..] }; let max_bits = bytes .iter() .fold((0usize, 0usize), |(max, cur), c| { let is_end = (c >> 7) == 0u8; if is_end { (max.max(cur + 7), 0) } else { (max, cur + 7) } }) .0; if max_bits > 64 { return None; } Some(SubIdentifierIterator { oid: self, pos: 0, first: false, n: core::marker::PhantomData, }) } } trait Repr: Num + Shl + From {} impl Repr for N where N: Num + Shl + From {} struct SubIdentifierIterator<'a, N: Repr> { oid: &'a Oid<'a>, pos: usize, first: bool, n: core::marker::PhantomData<&'a N>, } impl<'a, N: Repr> Iterator for SubIdentifierIterator<'a, N> { type Item = N; fn next(&mut self) -> Option { use num_traits::identities::Zero; if self.pos == self.oid.asn1.len() { return None; } if !self.oid.relative { if !self.first { debug_assert!(self.pos == 0); self.first = true; return Some((self.oid.asn1[0] / 40).into()); } else if self.pos == 0 { self.pos += 1; if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 { return None; } return Some((self.oid.asn1[0] % 40).into()); } } // decode objet sub-identifier according to the asn.1 standard let mut res = ::zero(); for o in self.oid.asn1[self.pos..].iter() { self.pos += 1; res = (res << 7) + (o & 0b111_1111).into(); let flag = o >> 7; if flag == 0u8 { break; } } Some(res) } } impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {} impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> { fn len(&self) -> usize { if self.oid.relative { self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count() } else if self.oid.asn1.len() == 0 { 0 } else if self.oid.asn1.len() == 1 { if self.oid.asn1[0] == 0 { 1 } else { 2 } } else { 2 + self.oid.asn1[2..] .iter() .filter(|o| (*o >> 7) == 0u8) .count() } } #[cfg(feature = "exact_size_is_empty")] fn is_empty(&self) -> bool { self.oid.asn1.is_empty() } } impl<'a> fmt::Display for Oid<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.relative { f.write_str("rel. ")?; } f.write_str(&self.to_id_string()) } } impl<'a> fmt::Debug for Oid<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("OID(")?; ::fmt(self, f)?; f.write_str(")") } } impl<'a> FromStr for Oid<'a> { type Err = ParseError; fn from_str(s: &str) -> Result { let v: Result, _> = s.split('.').map(|c| c.parse::()).collect(); v.map_err(|_| ParseError::ParseIntError) .and_then(|v| Oid::from(&v)) } } #[cfg(test)] mod tests { use crate::oid::Oid; use std::borrow::Cow; use std::borrow::ToOwned; use std::str::FromStr; #[test] fn test_oid_fmt() { let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 5]).unwrap(); assert_eq!(format!("{}", oid), "1.2.840.113549.1.1.5".to_owned()); assert_eq!(format!("{:?}", oid), "OID(1.2.840.113549.1.1.5)".to_owned()); let oid = Oid::from_relative(&[840, 113_549, 1, 1, 5]).unwrap(); let byte_ref = [0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 5]; assert_eq!(byte_ref.as_ref(), oid.asn1.as_ref()); assert_eq!(format!("{}", oid), "rel. 840.113549.1.1.5".to_owned()); assert_eq!( format!("{:?}", oid), "OID(rel. 840.113549.1.1.5)".to_owned() ); } #[test] fn test_iter_len() { #[cfg(feature = "bigint")] { assert_eq!(Oid::new(Cow::Borrowed(&[])).iter_bigint().len(), 0); assert_eq!(Oid::from(&[0]).unwrap().iter_bigint().len(), 1); assert_eq!(Oid::from(&[1, 2]).unwrap().iter_bigint().len(), 2); assert_eq!( Oid::from(&[1, 29, 459, 342]).unwrap().iter_bigint().len(), 4 ); assert_eq!( Oid::from_relative(&[459, 342]).unwrap().iter_bigint().len(), 2 ); } { assert_eq!(Oid::new(Cow::Borrowed(&[])).iter().unwrap().len(), 0); assert_eq!(Oid::from(&[0]).unwrap().iter().unwrap().len(), 1); assert_eq!(Oid::from(&[1, 2]).unwrap().iter().unwrap().len(), 2); assert_eq!( Oid::from(&[1, 29, 459, 342]).unwrap().iter().unwrap().len(), 4 ); assert_eq!( Oid::from_relative(&[459, 342]) .unwrap() .iter() .unwrap() .len(), 2 ); } } #[test] fn test_oid_from_str() { let oid_ref = Oid::from(&[1, 2, 840, 113_549, 1, 1, 5]).unwrap(); let byte_ref = [42, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 5]; let oid = Oid::from_str("1.2.840.113549.1.1.5").unwrap(); assert_eq!(byte_ref.as_ref(), oid.asn1.as_ref()); assert_eq!(oid_ref, oid); } /// This test case will test an OID beginning with two zero /// subidentifiers (literally: "itu-t recommendation"), as /// used for example in the TCAP (Q.773) specification. #[test] fn test_itu_t_rec_oid() { let oid = Oid::from(&[0, 0, 17, 773, 1, 1, 1]).unwrap(); assert_eq!(format!("{}", oid), "0.0.17.773.1.1.1".to_owned()); assert_eq!(format!("{:?}", oid), "OID(0.0.17.773.1.1.1)".to_owned()); } #[test] fn test_zero_oid() { #[cfg(feature = "bigint")] { use num_bigint::BigUint; use num_traits::FromPrimitive; use std::vec::Vec; let oid_raw = Oid::new(Cow::Borrowed(&[0])); let ids: Vec = oid_raw.iter_bigint().collect(); assert_eq!(vec![BigUint::from_u8(0).unwrap()], ids); assert_eq!(oid_raw.iter_bigint().len(), 1); } { use std::vec::Vec; let oid_raw = Oid::new(Cow::Borrowed(&[0])); let ids: Vec = oid_raw.iter().unwrap().collect(); assert_eq!(vec![0], ids); assert_eq!(oid_raw.iter().unwrap().len(), 1); } let oid_from = Oid::from(&[0]).unwrap(); assert_eq!(oid_from.asn1.as_ref(), &[0]); } }