• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Object ID (OID) representations.
2 //!
3 //! The parser does not copy oids when parsing. The [Oid struct](struct.Oid.html)
4 //! only has a reference to the DER encoded form of the oid.
5 //!
6 //! # The `der_parser::oid!` macro
7 //!
8 //! Since the DER encoded oids are not very readable we provide a
9 //! procedural macro `oid!`. The macro can be used the following ways:
10 //!
11 //! - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>`
12 //! - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>`
13 //! - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array.
14 //!
15 //! # Comparing oids
16 //!
17 //! Comparing a parsed oid to a static oid is probably the most common
18 //! thing done with oids in your code. The `oid!` macro can be used in expression positions for
19 //! this purpose. For example
20 //! ```
21 //! use der_parser::{oid, oid::Oid};
22 //!
23 //! # let some_oid: Oid<'static> = oid!(1.2.456);
24 //! const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456);
25 //! assert_eq!(some_oid, SOME_STATIC_OID)
26 //! ```
27 //! To get a relative Oid use `oid!(rel 1.2)`.
28 //!
29 //! Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727))
30 //! and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434))
31 //! the `oid` macro can not directly be used in patterns, also not through constants.
32 //! You can do this, though:
33 //! ```
34 //! # use der_parser::{oid, oid::Oid};
35 //! # let some_oid: Oid<'static> = oid!(1.2.456);
36 //! const SOME_OID: Oid<'static> = oid!(1.2.456);
37 //! if some_oid == SOME_OID || some_oid == oid!(1.2.456) {
38 //!     println!("match");
39 //! }
40 //!
41 //! // Alternatively, compare the DER encoded form directly:
42 //! const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456);
43 //! match some_oid.bytes() {
44 //!     SOME_OID_RAW => println!("match"),
45 //!     _ => panic!("no match"),
46 //! }
47 //! ```
48 //! *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An
49 //! extra check might be necessary.
50 use alloc::borrow::Cow;
51 use alloc::fmt;
52 use alloc::str::FromStr;
53 use alloc::string::{String, ToString};
54 use alloc::vec::Vec;
55 use core::convert::From;
56 use core::iter::{ExactSizeIterator, FusedIterator, Iterator};
57 use core::ops::Shl;
58 
59 #[cfg(feature = "bigint")]
60 use num_bigint::BigUint;
61 use num_traits::Num;
62 
63 #[cfg(not(feature = "std"))]
64 use alloc::format;
65 
66 #[derive(Debug)]
67 pub enum ParseError {
68     TooShort,
69     /// Signalizes that the first or second component is too large.
70     /// The first must be within the range 0 to 6 (inclusive).
71     /// The second component must be less than 40.
72     FirstComponentsTooLarge,
73     ParseIntError,
74 }
75 
76 /// Object ID (OID) representation which can be relative or non-relative.
77 /// An example for an oid in string representation is "1.2.840.113549.1.1.5".
78 ///
79 /// For non-relative oids restrictions apply to the first two components.
80 ///
81 /// This library contains a procedural macro `oid` which can be used to
82 /// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)`
83 /// for relative oids. See the [module documentation](index.html) for more information.
84 #[derive(Hash, PartialEq, Eq, Clone)]
85 pub struct Oid<'a> {
86     asn1: Cow<'a, [u8]>,
87     pub relative: bool,
88 }
89 
encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_90 fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ {
91     ids.iter()
92         .map(|id| {
93             let bit_count = 64 - id.leading_zeros();
94             let octets_needed = ((bit_count + 6) / 7).max(1);
95             (0..octets_needed).map(move |i| {
96                 let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 };
97                 ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag
98             })
99         })
100         .flatten()
101 }
102 
103 impl<'a> Oid<'a> {
104     /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
105     /// for other ways to create oids.
new(asn1: Cow<'a, [u8]>) -> Oid106     pub const fn new(asn1: Cow<'a, [u8]>) -> Oid {
107         Oid {
108             asn1,
109             relative: false,
110         }
111     }
112 
113     /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
114     /// for other ways to create relative oids.
new_relative(asn1: Cow<'a, [u8]>) -> Oid115     pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid {
116         Oid {
117             asn1,
118             relative: true,
119         }
120     }
121 
122     /// Build an OID from an array of object identifier components.
123     /// This method allocates memory on the heap.
124     // we do not use .copied() for compatibility with 1.34
125     #[allow(clippy::map_clone)]
from<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError>126     pub fn from<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError> {
127         if s.len() < 2 {
128             if s.len() == 1 && s[0] == 0 {
129                 return Ok(Oid {
130                     asn1: Cow::Borrowed(&[0]),
131                     relative: false,
132                 });
133             }
134             return Err(ParseError::TooShort);
135         }
136         if s[0] >= 7 || s[1] >= 40 {
137             return Err(ParseError::FirstComponentsTooLarge);
138         }
139         let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8]
140             .iter()
141             .map(|&x| x)
142             .chain(encode_relative(&s[2..]))
143             .collect();
144         Ok(Oid {
145             asn1: Cow::from(asn1_encoded),
146             relative: false,
147         })
148     }
149 
150     /// Build a relative OID from an array of object identifier components.
from_relative<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError>151     pub fn from_relative<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError> {
152         if s.is_empty() {
153             return Err(ParseError::TooShort);
154         }
155         let asn1_encoded: Vec<u8> = encode_relative(s).collect();
156         Ok(Oid {
157             asn1: Cow::from(asn1_encoded),
158             relative: true,
159         })
160     }
161 
162     /// Create a deep copy of the oid.
163     ///
164     /// This method allocates data on the heap. The returned oid
165     /// can be used without keeping the ASN.1 representation around.
166     ///
167     /// Cloning the returned oid does again allocate data.
to_owned(&self) -> Oid<'static>168     pub fn to_owned(&self) -> Oid<'static> {
169         Oid {
170             asn1: Cow::from(self.asn1.to_vec()),
171             relative: self.relative,
172         }
173     }
174 
175     /// Get the encoded oid without the header.
bytes(&self) -> &[u8]176     pub fn bytes(&self) -> &[u8] {
177         self.asn1.as_ref()
178     }
179 
180     /// Convert the OID to a string representation.
181     /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5"
182     #[cfg(feature = "bigint")]
to_id_string(&self) -> String183     pub fn to_id_string(&self) -> String {
184         let ints: Vec<String> = self.iter_bigint().map(|i| i.to_string()).collect();
185         ints.join(".")
186     }
187 
188     #[cfg(not(feature = "bigint"))]
189     /// Convert the OID to a string representation.
190     ///
191     /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5"
192     /// is returned, otherwise a hex representation.
193     ///
194     /// See also the "bigint" feature of this crate.
to_id_string(&self) -> String195     pub fn to_id_string(&self) -> String {
196         if let Some(arcs) = self.iter() {
197             let ints: Vec<String> = arcs.map(|i| i.to_string()).collect();
198             ints.join(".")
199         } else {
200             let mut ret = String::with_capacity(self.asn1.len() * 3);
201             for (i, o) in self.asn1.iter().enumerate() {
202                 ret.push_str(&format!("{:02x}", o));
203                 if i + 1 != self.asn1.len() {
204                     ret.push(' ');
205                 }
206             }
207             ret
208         }
209     }
210 
211     /// Return an iterator over the sub-identifiers (arcs).
212     #[cfg(feature = "bigint")]
iter_bigint( &'_ self, ) -> impl Iterator<Item = BigUint> + FusedIterator + ExactSizeIterator + '_213     pub fn iter_bigint(
214         &'_ self,
215     ) -> impl Iterator<Item = BigUint> + FusedIterator + ExactSizeIterator + '_ {
216         SubIdentifierIterator {
217             oid: self,
218             pos: 0,
219             first: false,
220             n: core::marker::PhantomData,
221         }
222     }
223 
224     /// Return an iterator over the sub-identifiers (arcs).
225     /// Returns `None` if at least one arc does not fit into `u64`.
iter( &'_ self, ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_>226     pub fn iter(
227         &'_ self,
228     ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_> {
229         // Check that every arc fits into u64
230         let bytes = if self.relative {
231             &self.asn1
232         } else if self.asn1.is_empty() {
233             &[]
234         } else {
235             &self.asn1[1..]
236         };
237         let max_bits = bytes
238             .iter()
239             .fold((0usize, 0usize), |(max, cur), c| {
240                 let is_end = (c >> 7) == 0u8;
241                 if is_end {
242                     (max.max(cur + 7), 0)
243                 } else {
244                     (max, cur + 7)
245                 }
246             })
247             .0;
248         if max_bits > 64 {
249             return None;
250         }
251 
252         Some(SubIdentifierIterator {
253             oid: self,
254             pos: 0,
255             first: false,
256             n: core::marker::PhantomData,
257         })
258     }
259 }
260 
261 trait Repr: Num + Shl<usize, Output = Self> + From<u8> {}
262 impl<N> Repr for N where N: Num + Shl<usize, Output = N> + From<u8> {}
263 
264 struct SubIdentifierIterator<'a, N: Repr> {
265     oid: &'a Oid<'a>,
266     pos: usize,
267     first: bool,
268     n: core::marker::PhantomData<&'a N>,
269 }
270 
271 impl<'a, N: Repr> Iterator for SubIdentifierIterator<'a, N> {
272     type Item = N;
273 
next(&mut self) -> Option<Self::Item>274     fn next(&mut self) -> Option<Self::Item> {
275         use num_traits::identities::Zero;
276 
277         if self.pos == self.oid.asn1.len() {
278             return None;
279         }
280         if !self.oid.relative {
281             if !self.first {
282                 debug_assert!(self.pos == 0);
283                 self.first = true;
284                 return Some((self.oid.asn1[0] / 40).into());
285             } else if self.pos == 0 {
286                 self.pos += 1;
287                 if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 {
288                     return None;
289                 }
290                 return Some((self.oid.asn1[0] % 40).into());
291             }
292         }
293         // decode objet sub-identifier according to the asn.1 standard
294         let mut res = <N as Zero>::zero();
295         for o in self.oid.asn1[self.pos..].iter() {
296             self.pos += 1;
297             res = (res << 7) + (o & 0b111_1111).into();
298             let flag = o >> 7;
299             if flag == 0u8 {
300                 break;
301             }
302         }
303         Some(res)
304     }
305 }
306 
307 impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {}
308 
309 impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> {
len(&self) -> usize310     fn len(&self) -> usize {
311         if self.oid.relative {
312             self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count()
313         } else if self.oid.asn1.len() == 0 {
314             0
315         } else if self.oid.asn1.len() == 1 {
316             if self.oid.asn1[0] == 0 {
317                 1
318             } else {
319                 2
320             }
321         } else {
322             2 + self.oid.asn1[2..]
323                 .iter()
324                 .filter(|o| (*o >> 7) == 0u8)
325                 .count()
326         }
327     }
328 
329     #[cfg(feature = "exact_size_is_empty")]
is_empty(&self) -> bool330     fn is_empty(&self) -> bool {
331         self.oid.asn1.is_empty()
332     }
333 }
334 
335 impl<'a> fmt::Display for Oid<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result336     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
337         if self.relative {
338             f.write_str("rel. ")?;
339         }
340         f.write_str(&self.to_id_string())
341     }
342 }
343 
344 impl<'a> fmt::Debug for Oid<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result345     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346         f.write_str("OID(")?;
347         <Oid as fmt::Display>::fmt(self, f)?;
348         f.write_str(")")
349     }
350 }
351 
352 impl<'a> FromStr for Oid<'a> {
353     type Err = ParseError;
354 
from_str(s: &str) -> Result<Self, Self::Err>355     fn from_str(s: &str) -> Result<Self, Self::Err> {
356         let v: Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect();
357         v.map_err(|_| ParseError::ParseIntError)
358             .and_then(|v| Oid::from(&v))
359     }
360 }
361 
362 #[cfg(test)]
363 mod tests {
364     use crate::oid::Oid;
365     use std::borrow::Cow;
366     use std::borrow::ToOwned;
367     use std::str::FromStr;
368 
369     #[test]
test_oid_fmt()370     fn test_oid_fmt() {
371         let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 5]).unwrap();
372         assert_eq!(format!("{}", oid), "1.2.840.113549.1.1.5".to_owned());
373         assert_eq!(format!("{:?}", oid), "OID(1.2.840.113549.1.1.5)".to_owned());
374 
375         let oid = Oid::from_relative(&[840, 113_549, 1, 1, 5]).unwrap();
376         let byte_ref = [0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 5];
377         assert_eq!(byte_ref.as_ref(), oid.asn1.as_ref());
378         assert_eq!(format!("{}", oid), "rel. 840.113549.1.1.5".to_owned());
379         assert_eq!(
380             format!("{:?}", oid),
381             "OID(rel. 840.113549.1.1.5)".to_owned()
382         );
383     }
384 
385     #[test]
test_iter_len()386     fn test_iter_len() {
387         #[cfg(feature = "bigint")]
388         {
389             assert_eq!(Oid::new(Cow::Borrowed(&[])).iter_bigint().len(), 0);
390             assert_eq!(Oid::from(&[0]).unwrap().iter_bigint().len(), 1);
391             assert_eq!(Oid::from(&[1, 2]).unwrap().iter_bigint().len(), 2);
392             assert_eq!(
393                 Oid::from(&[1, 29, 459, 342]).unwrap().iter_bigint().len(),
394                 4
395             );
396             assert_eq!(
397                 Oid::from_relative(&[459, 342]).unwrap().iter_bigint().len(),
398                 2
399             );
400         }
401         {
402             assert_eq!(Oid::new(Cow::Borrowed(&[])).iter().unwrap().len(), 0);
403             assert_eq!(Oid::from(&[0]).unwrap().iter().unwrap().len(), 1);
404             assert_eq!(Oid::from(&[1, 2]).unwrap().iter().unwrap().len(), 2);
405             assert_eq!(
406                 Oid::from(&[1, 29, 459, 342]).unwrap().iter().unwrap().len(),
407                 4
408             );
409             assert_eq!(
410                 Oid::from_relative(&[459, 342])
411                     .unwrap()
412                     .iter()
413                     .unwrap()
414                     .len(),
415                 2
416             );
417         }
418     }
419 
420     #[test]
test_oid_from_str()421     fn test_oid_from_str() {
422         let oid_ref = Oid::from(&[1, 2, 840, 113_549, 1, 1, 5]).unwrap();
423         let byte_ref = [42, 0x86, 0x48, 0x86, 0xf7, 0x0d, 1, 1, 5];
424         let oid = Oid::from_str("1.2.840.113549.1.1.5").unwrap();
425         assert_eq!(byte_ref.as_ref(), oid.asn1.as_ref());
426         assert_eq!(oid_ref, oid);
427     }
428 
429     /// This test case will test an OID beginning with two zero
430     /// subidentifiers (literally: "itu-t recommendation"), as
431     /// used for example in the TCAP (Q.773) specification.
432 
433     #[test]
test_itu_t_rec_oid()434     fn test_itu_t_rec_oid() {
435         let oid = Oid::from(&[0, 0, 17, 773, 1, 1, 1]).unwrap();
436         assert_eq!(format!("{}", oid), "0.0.17.773.1.1.1".to_owned());
437         assert_eq!(format!("{:?}", oid), "OID(0.0.17.773.1.1.1)".to_owned());
438     }
439 
440     #[test]
test_zero_oid()441     fn test_zero_oid() {
442         #[cfg(feature = "bigint")]
443         {
444             use num_bigint::BigUint;
445             use num_traits::FromPrimitive;
446             use std::vec::Vec;
447 
448             let oid_raw = Oid::new(Cow::Borrowed(&[0]));
449             let ids: Vec<BigUint> = oid_raw.iter_bigint().collect();
450             assert_eq!(vec![BigUint::from_u8(0).unwrap()], ids);
451             assert_eq!(oid_raw.iter_bigint().len(), 1);
452         }
453         {
454             use std::vec::Vec;
455             let oid_raw = Oid::new(Cow::Borrowed(&[0]));
456             let ids: Vec<u64> = oid_raw.iter().unwrap().collect();
457             assert_eq!(vec![0], ids);
458             assert_eq!(oid_raw.iter().unwrap().len(), 1);
459         }
460         let oid_from = Oid::from(&[0]).unwrap();
461         assert_eq!(oid_from.asn1.as_ref(), &[0]);
462     }
463 }
464