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