asn1_rs/asn1_types/
oid.rs

1use crate::*;
2use alloc::borrow::Cow;
3#[cfg(not(feature = "std"))]
4use alloc::format;
5#[cfg(not(feature = "std"))]
6use alloc::string::{String, ToString};
7#[cfg(not(feature = "std"))]
8use alloc::vec::Vec;
9use core::{
10    convert::TryFrom, fmt, iter::FusedIterator, marker::PhantomData, ops::Shl, str::FromStr,
11};
12use displaydoc::Display;
13use num_traits::Num;
14use thiserror::Error;
15
16/// An error for OID parsing functions.
17#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Error)]
18pub enum OidParseError {
19    /// Encoded data length too short
20    TooShort,
21    /** Signalizes that the first or second component is too large.
22     * The first must be within the range 0 to 6 (inclusive).
23     * The second component must be less than 40.
24     */
25    FirstComponentsTooLarge,
26    /// a
27    ParseIntError,
28}
29
30/// Object ID (OID) representation which can be relative or non-relative.
31///
32/// An example for an OID in string representation is `"1.2.840.113549.1.1.5"`.
33///
34/// For non-relative OIDs restrictions apply to the first two components.
35///
36/// This library contains a procedural macro `oid` which can be used to
37/// create oids. For example `oid!(1.2.44.233)` or `oid!(rel 44.233)`
38/// for relative oids. See the [module documentation](index.html) for more information.
39#[derive(Hash, PartialEq, Eq, Clone)]
40pub struct Oid<'a> {
41    asn1: Cow<'a, [u8]>,
42    relative: bool,
43}
44
45impl<'a> TryFrom<Any<'a>> for Oid<'a> {
46    type Error = Error;
47
48    fn try_from(any: Any<'a>) -> Result<Self> {
49        TryFrom::try_from(&any)
50    }
51}
52
53impl<'a, 'b> TryFrom<&'b Any<'a>> for Oid<'a> {
54    type Error = Error;
55
56    fn try_from(any: &'b Any<'a>) -> Result<Self> {
57        // check that any.data.last().unwrap() >> 7 == 0u8
58        let asn1 = Cow::Borrowed(any.data);
59        Ok(Oid::new(asn1))
60    }
61}
62
63impl CheckDerConstraints for Oid<'_> {
64    fn check_constraints(any: &Any) -> Result<()> {
65        any.header.assert_primitive()?;
66        any.header.length.assert_definite()?;
67        Ok(())
68    }
69}
70
71impl DerAutoDerive for Oid<'_> {}
72
73impl Tagged for Oid<'_> {
74    const TAG: Tag = Tag::Oid;
75}
76
77#[cfg(feature = "std")]
78impl ToDer for Oid<'_> {
79    fn to_der_len(&self) -> Result<usize> {
80        // OID/REL-OID tag will not change header size, so we don't care here
81        let header = Header::new(
82            Class::Universal,
83            false,
84            Self::TAG,
85            Length::Definite(self.asn1.len()),
86        );
87        Ok(header.to_der_len()? + self.asn1.len())
88    }
89
90    fn write_der_header(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
91        let tag = if self.relative {
92            Tag::RelativeOid
93        } else {
94            Tag::Oid
95        };
96        let header = Header::new(
97            Class::Universal,
98            false,
99            tag,
100            Length::Definite(self.asn1.len()),
101        );
102        header.write_der_header(writer)
103    }
104
105    fn write_der_content(&self, writer: &mut dyn std::io::Write) -> SerializeResult<usize> {
106        writer.write(&self.asn1).map_err(Into::into)
107    }
108}
109
110fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ {
111    ids.iter().flat_map(|id| {
112        let bit_count = 64 - id.leading_zeros();
113        let octets_needed = ((bit_count + 6) / 7).max(1);
114        (0..octets_needed).map(move |i| {
115            let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 };
116            ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag
117        })
118    })
119}
120
121impl<'a> Oid<'a> {
122    /// Create an OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
123    /// for other ways to create oids.
124    pub const fn new(asn1: Cow<'a, [u8]>) -> Oid<'a> {
125        Oid {
126            asn1,
127            relative: false,
128        }
129    }
130
131    /// Create a relative OID from the ASN.1 DER encoded form. See the [module documentation](index.html)
132    /// for other ways to create relative oids.
133    pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid<'a> {
134        Oid {
135            asn1,
136            relative: true,
137        }
138    }
139
140    /// Build an OID from an array of object identifier components.
141    /// This method allocates memory on the heap.
142    pub fn from(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
143        if s.len() < 2 {
144            if s.len() == 1 && s[0] == 0 {
145                return Ok(Oid {
146                    asn1: Cow::Borrowed(&[0]),
147                    relative: false,
148                });
149            }
150            return Err(OidParseError::TooShort);
151        }
152        if s[0] >= 7 || s[1] >= 40 {
153            return Err(OidParseError::FirstComponentsTooLarge);
154        }
155        let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8]
156            .iter()
157            .copied()
158            .chain(encode_relative(&s[2..]))
159            .collect();
160        Ok(Oid {
161            asn1: Cow::from(asn1_encoded),
162            relative: false,
163        })
164    }
165
166    /// Build a relative OID from an array of object identifier components.
167    pub fn from_relative(s: &[u64]) -> core::result::Result<Oid<'static>, OidParseError> {
168        if s.is_empty() {
169            return Err(OidParseError::TooShort);
170        }
171        let asn1_encoded: Vec<u8> = encode_relative(s).collect();
172        Ok(Oid {
173            asn1: Cow::from(asn1_encoded),
174            relative: true,
175        })
176    }
177
178    /// Create a deep copy of the oid.
179    ///
180    /// This method allocates data on the heap. The returned oid
181    /// can be used without keeping the ASN.1 representation around.
182    ///
183    /// Cloning the returned oid does again allocate data.
184    pub fn to_owned(&self) -> Oid<'static> {
185        Oid {
186            asn1: Cow::from(self.asn1.to_vec()),
187            relative: self.relative,
188        }
189    }
190
191    /// Get the encoded oid without the header.
192    #[inline]
193    pub fn as_bytes(&self) -> &[u8] {
194        self.asn1.as_ref()
195    }
196
197    /// Get the encoded oid without the header.
198    #[deprecated(since = "0.2.0", note = "Use `as_bytes` instead")]
199    #[inline]
200    pub fn bytes(&self) -> &[u8] {
201        self.as_bytes()
202    }
203
204    /// Get the bytes representation of the encoded oid
205    pub fn into_cow(self) -> Cow<'a, [u8]> {
206        self.asn1
207    }
208
209    /// Convert the OID to a string representation.
210    /// The string contains the IDs separated by dots, for ex: "1.2.840.113549.1.1.5"
211    #[cfg(feature = "bigint")]
212    pub fn to_id_string(&self) -> String {
213        let ints: Vec<String> = self.iter_bigint().map(|i| i.to_string()).collect();
214        ints.join(".")
215    }
216
217    #[cfg(not(feature = "bigint"))]
218    /// Convert the OID to a string representation.
219    ///
220    /// If every arc fits into a u64 a string like "1.2.840.113549.1.1.5"
221    /// is returned, otherwise a hex representation.
222    ///
223    /// See also the "bigint" feature of this crate.
224    pub fn to_id_string(&self) -> String {
225        if let Some(arcs) = self.iter() {
226            let ints: Vec<String> = arcs.map(|i| i.to_string()).collect();
227            ints.join(".")
228        } else {
229            let mut ret = String::with_capacity(self.asn1.len() * 3);
230            for (i, o) in self.asn1.iter().enumerate() {
231                ret.push_str(&format!("{:02x}", o));
232                if i + 1 != self.asn1.len() {
233                    ret.push(' ');
234                }
235            }
236            ret
237        }
238    }
239
240    /// Return an iterator over the sub-identifiers (arcs).
241    #[cfg(feature = "bigint")]
242    pub fn iter_bigint(&'_ self) -> impl FusedIterator<Item = BigUint> + ExactSizeIterator + '_ {
243        SubIdentifierIterator {
244            oid: self,
245            pos: 0,
246            first: false,
247            n: PhantomData,
248        }
249    }
250
251    /// Return an iterator over the sub-identifiers (arcs).
252    /// Returns `None` if at least one arc does not fit into `u64`.
253    pub fn iter(&'_ self) -> Option<impl FusedIterator<Item = u64> + ExactSizeIterator + '_> {
254        // Check that every arc fits into u64
255        let bytes = if self.relative {
256            &self.asn1
257        } else if self.asn1.is_empty() {
258            &[]
259        } else {
260            &self.asn1[1..]
261        };
262        let max_bits = bytes
263            .iter()
264            .fold((0usize, 0usize), |(max, cur), c| {
265                let is_end = (c >> 7) == 0u8;
266                if is_end {
267                    (max.max(cur + 7), 0)
268                } else {
269                    (max, cur + 7)
270                }
271            })
272            .0;
273        if max_bits > 64 {
274            return None;
275        }
276
277        Some(SubIdentifierIterator {
278            oid: self,
279            pos: 0,
280            first: false,
281            n: PhantomData,
282        })
283    }
284
285    pub fn from_ber_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
286        let (rem, any) = Any::from_ber(bytes)?;
287        any.header.assert_primitive()?;
288        any.header.assert_tag(Tag::RelativeOid)?;
289        let asn1 = Cow::Borrowed(any.data);
290        Ok((rem, Oid::new_relative(asn1)))
291    }
292
293    pub fn from_der_relative(bytes: &'a [u8]) -> ParseResult<'a, Self> {
294        let (rem, any) = Any::from_der(bytes)?;
295        any.header.assert_tag(Tag::RelativeOid)?;
296        Self::check_constraints(&any)?;
297        let asn1 = Cow::Borrowed(any.data);
298        Ok((rem, Oid::new_relative(asn1)))
299    }
300
301    /// Returns true if `needle` is a prefix of the OID.
302    pub fn starts_with(&self, needle: &Oid) -> bool {
303        self.asn1.len() >= needle.asn1.len() && self.asn1.starts_with(needle.as_bytes())
304    }
305}
306
307trait Repr: Num + Shl<usize, Output = Self> + From<u8> {}
308impl<N> Repr for N where N: Num + Shl<usize, Output = N> + From<u8> {}
309
310struct SubIdentifierIterator<'a, N: Repr> {
311    oid: &'a Oid<'a>,
312    pos: usize,
313    first: bool,
314    n: PhantomData<&'a N>,
315}
316
317impl<N: Repr> Iterator for SubIdentifierIterator<'_, N> {
318    type Item = N;
319
320    fn next(&mut self) -> Option<Self::Item> {
321        use num_traits::identities::Zero;
322
323        if self.pos == self.oid.asn1.len() {
324            return None;
325        }
326        if !self.oid.relative {
327            if !self.first {
328                debug_assert!(self.pos == 0);
329                self.first = true;
330                return Some((self.oid.asn1[0] / 40).into());
331            } else if self.pos == 0 {
332                self.pos += 1;
333                if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 {
334                    return None;
335                }
336                return Some((self.oid.asn1[0] % 40).into());
337            }
338        }
339        // decode objet sub-identifier according to the asn.1 standard
340        let mut res = <N as Zero>::zero();
341        for o in self.oid.asn1[self.pos..].iter() {
342            self.pos += 1;
343            res = (res << 7) + (o & 0b111_1111).into();
344            let flag = o >> 7;
345            if flag == 0u8 {
346                break;
347            }
348        }
349        Some(res)
350    }
351}
352
353impl<N: Repr> FusedIterator for SubIdentifierIterator<'_, N> {}
354
355impl<N: Repr> ExactSizeIterator for SubIdentifierIterator<'_, N> {
356    fn len(&self) -> usize {
357        if self.oid.relative {
358            self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count()
359        } else if self.oid.asn1.is_empty() {
360            0
361        } else if self.oid.asn1.len() == 1 {
362            if self.oid.asn1[0] == 0 {
363                1
364            } else {
365                2
366            }
367        } else {
368            2 + self.oid.asn1[2..]
369                .iter()
370                .filter(|o| (*o >> 7) == 0u8)
371                .count()
372        }
373    }
374}
375
376impl fmt::Display for Oid<'_> {
377    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
378        if self.relative {
379            f.write_str("rel. ")?;
380        }
381        f.write_str(&self.to_id_string())
382    }
383}
384
385impl fmt::Debug for Oid<'_> {
386    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387        f.write_str("OID(")?;
388        <Oid as fmt::Display>::fmt(self, f)?;
389        f.write_str(")")
390    }
391}
392
393impl FromStr for Oid<'_> {
394    type Err = OidParseError;
395
396    fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
397        let v: core::result::Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect();
398        v.map_err(|_| OidParseError::ParseIntError)
399            .and_then(|v| Oid::from(&v))
400    }
401}
402
403/// Helper macro to declare integers at compile-time
404///
405/// Since the DER encoded oids are not very readable we provide a
406/// procedural macro `oid!`. The macro can be used the following ways:
407///
408/// - `oid!(1.4.42.23)`: Create a const expression for the corresponding `Oid<'static>`
409/// - `oid!(rel 42.23)`: Create a const expression for the corresponding relative `Oid<'static>`
410/// - `oid!(raw 1.4.42.23)`/`oid!(raw rel 42.23)`: Obtain the DER encoded form as a byte array.
411///
412/// # Comparing oids
413///
414/// Comparing a parsed oid to a static oid is probably the most common
415/// thing done with oids in your code. The `oid!` macro can be used in expression positions for
416/// this purpose. For example
417/// ```
418/// use asn1_rs::{oid, Oid};
419///
420/// # let some_oid: Oid<'static> = oid!(1.2.456);
421/// const SOME_STATIC_OID: Oid<'static> = oid!(1.2.456);
422/// assert_eq!(some_oid, SOME_STATIC_OID)
423/// ```
424/// To get a relative Oid use `oid!(rel 1.2)`.
425///
426/// Because of limitations for procedural macros ([rust issue](https://github.com/rust-lang/rust/issues/54727))
427/// and constants used in patterns ([rust issue](https://github.com/rust-lang/rust/issues/31434))
428/// the `oid` macro can not directly be used in patterns, also not through constants.
429/// You can do this, though:
430/// ```
431/// # use asn1_rs::{oid, Oid};
432/// # let some_oid: Oid<'static> = oid!(1.2.456);
433/// const SOME_OID: Oid<'static> = oid!(1.2.456);
434/// if some_oid == SOME_OID || some_oid == oid!(1.2.456) {
435///     println!("match");
436/// }
437///
438/// // Alternatively, compare the DER encoded form directly:
439/// const SOME_OID_RAW: &[u8] = &oid!(raw 1.2.456);
440/// match some_oid.as_bytes() {
441///     SOME_OID_RAW => println!("match"),
442///     _ => panic!("no match"),
443/// }
444/// ```
445/// *Attention*, be aware that the latter version might not handle the case of a relative oid correctly. An
446/// extra check might be necessary.
447#[macro_export]
448macro_rules! oid {
449    (raw $( $item:literal ).*) => {
450        $crate::exports::asn1_rs_impl::encode_oid!( $( $item ).* )
451    };
452    (raw $items:expr) => {
453        $crate::exports::asn1_rs_impl::encode_oid!($items)
454    };
455    (rel $($item:literal ).*) => {
456        $crate::Oid::new_relative($crate::exports::borrow::Cow::Borrowed(
457            &$crate::exports::asn1_rs_impl::encode_oid!(rel $( $item ).*),
458        ))
459    };
460    ($($item:literal ).*) => {
461        $crate::Oid::new($crate::exports::borrow::Cow::Borrowed(
462            &$crate::oid!(raw $( $item ).*),
463        ))
464    };
465}
466
467#[cfg(all(test, feature = "std"))]
468mod tests {
469    use crate::{FromDer, Oid, ToDer};
470    use hex_literal::hex;
471
472    #[test]
473    fn declare_oid() {
474        let oid = super::oid! {1.2.840.113549.1};
475        assert_eq!(oid.to_string(), "1.2.840.113549.1");
476    }
477
478    const OID_RSA_ENCRYPTION: &[u8] = &oid! {raw 1.2.840.113549.1.1.1};
479    const OID_EC_PUBLIC_KEY: &[u8] = &oid! {raw 1.2.840.10045.2.1};
480    #[allow(clippy::match_like_matches_macro)]
481    fn compare_oid(oid: &Oid) -> bool {
482        match oid.as_bytes() {
483            OID_RSA_ENCRYPTION => true,
484            OID_EC_PUBLIC_KEY => true,
485            _ => false,
486        }
487    }
488
489    #[test]
490    fn test_compare_oid() {
491        let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
492        assert_eq!(oid, oid! {1.2.840.113549.1.1.1});
493        let oid = Oid::from(&[1, 2, 840, 113_549, 1, 1, 1]).unwrap();
494        assert!(compare_oid(&oid));
495    }
496
497    #[test]
498    fn oid_to_der() {
499        let oid = super::oid! {1.2.840.113549.1};
500        assert_eq!(oid.to_der_len(), Ok(9));
501        let v = oid.to_der_vec().expect("could not serialize");
502        assert_eq!(&v, &hex! {"06 07 2a 86 48 86 f7 0d 01"});
503        let (_, oid2) = Oid::from_der(&v).expect("could not re-parse");
504        assert_eq!(&oid, &oid2);
505    }
506
507    #[test]
508    fn oid_starts_with() {
509        const OID_RSA_ENCRYPTION: Oid = oid! {1.2.840.113549.1.1.1};
510        const OID_EC_PUBLIC_KEY: Oid = oid! {1.2.840.10045.2.1};
511        let oid = super::oid! {1.2.840.113549.1};
512        assert!(OID_RSA_ENCRYPTION.starts_with(&oid));
513        assert!(!OID_EC_PUBLIC_KEY.starts_with(&oid));
514    }
515
516    #[test]
517    fn oid_macro_parameters() {
518        // Code inspired from https://github.com/rusticata/der-parser/issues/68
519        macro_rules! foo {
520            ($a:literal $b:literal $c:literal) => {
521                super::oid!($a.$b.$c)
522            };
523        }
524
525        let oid = foo!(1 2 3);
526        assert_eq!(oid, oid! {1.2.3});
527    }
528}