Coverage Report

Created: 2025-07-23 07:29

/rust/registry/src/index.crates.io-6f17d22bba15001f/der-parser-6.0.1/src/oid.rs
Line
Count
Source (jump to first uncovered line)
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
90
0
fn encode_relative(ids: &'_ [u64]) -> impl Iterator<Item = u8> + '_ {
91
0
    ids.iter()
92
0
        .map(|id| {
93
0
            let bit_count = 64 - id.leading_zeros();
94
0
            let octets_needed = ((bit_count + 6) / 7).max(1);
95
0
            (0..octets_needed).map(move |i| {
96
0
                let flag = if i == octets_needed - 1 { 0 } else { 1 << 7 };
97
0
                ((id >> (7 * (octets_needed - 1 - i))) & 0b111_1111) as u8 | flag
98
0
            })
99
0
        })
100
0
        .flatten()
101
0
}
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.
106
31.6k
    pub const fn new(asn1: Cow<'a, [u8]>) -> Oid {
107
31.6k
        Oid {
108
31.6k
            asn1,
109
31.6k
            relative: false,
110
31.6k
        }
111
31.6k
    }
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.
115
7.23k
    pub const fn new_relative(asn1: Cow<'a, [u8]>) -> Oid {
116
7.23k
        Oid {
117
7.23k
            asn1,
118
7.23k
            relative: true,
119
7.23k
        }
120
7.23k
    }
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)]
126
0
    pub fn from<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError> {
127
0
        if s.len() < 2 {
128
0
            if s.len() == 1 && s[0] == 0 {
129
0
                return Ok(Oid {
130
0
                    asn1: Cow::Borrowed(&[0]),
131
0
                    relative: false,
132
0
                });
133
0
            }
134
0
            return Err(ParseError::TooShort);
135
0
        }
136
0
        if s[0] >= 7 || s[1] >= 40 {
137
0
            return Err(ParseError::FirstComponentsTooLarge);
138
0
        }
139
0
        let asn1_encoded: Vec<u8> = [(s[0] * 40 + s[1]) as u8]
140
0
            .iter()
141
0
            .map(|&x| x)
142
0
            .chain(encode_relative(&s[2..]))
143
0
            .collect();
144
0
        Ok(Oid {
145
0
            asn1: Cow::from(asn1_encoded),
146
0
            relative: false,
147
0
        })
148
0
    }
149
150
    /// Build a relative OID from an array of object identifier components.
151
0
    pub fn from_relative<'b>(s: &'b [u64]) -> Result<Oid<'static>, ParseError> {
152
0
        if s.is_empty() {
153
0
            return Err(ParseError::TooShort);
154
0
        }
155
0
        let asn1_encoded: Vec<u8> = encode_relative(s).collect();
156
0
        Ok(Oid {
157
0
            asn1: Cow::from(asn1_encoded),
158
0
            relative: true,
159
0
        })
160
0
    }
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.
168
0
    pub fn to_owned(&self) -> Oid<'static> {
169
0
        Oid {
170
0
            asn1: Cow::from(self.asn1.to_vec()),
171
0
            relative: self.relative,
172
0
        }
173
0
    }
174
175
    /// Get the encoded oid without the header.
176
0
    pub fn bytes(&self) -> &[u8] {
177
0
        self.asn1.as_ref()
178
0
    }
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")]
183
    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.
195
6.92k
    pub fn to_id_string(&self) -> String {
196
6.92k
        if let Some(arcs) = self.iter() {
197
120k
            let ints: Vec<String> = arcs.map(|i| i.to_string()).collect();
198
5.14k
            ints.join(".")
199
        } else {
200
1.78k
            let mut ret = String::with_capacity(self.asn1.len() * 3);
201
104k
            for (i, o) in self.asn1.iter().enumerate() {
202
104k
                ret.push_str(&format!("{:02x}", o));
203
104k
                if i + 1 != self.asn1.len() {
204
102k
                    ret.push(' ');
205
102k
                }
206
            }
207
1.78k
            ret
208
        }
209
6.92k
    }
210
211
    /// Return an iterator over the sub-identifiers (arcs).
212
    #[cfg(feature = "bigint")]
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`.
226
6.92k
    pub fn iter(
227
6.92k
        &'_ self,
228
6.92k
    ) -> Option<impl Iterator<Item = u64> + FusedIterator + ExactSizeIterator + '_> {
229
        // Check that every arc fits into u64
230
6.92k
        let bytes = if self.relative {
231
0
            &self.asn1
232
6.92k
        } else if self.asn1.is_empty() {
233
0
            &[]
234
        } else {
235
6.92k
            &self.asn1[1..]
236
        };
237
6.92k
        let max_bits = bytes
238
6.92k
            .iter()
239
227k
            .fold((0usize, 0usize), |(max, cur), c| {
240
227k
                let is_end = (c >> 7) == 0u8;
241
227k
                if is_end {
242
175k
                    (max.max(cur + 7), 0)
243
                } else {
244
52.3k
                    (max, cur + 7)
245
                }
246
227k
            })
247
6.92k
            .0;
248
6.92k
        if max_bits > 64 {
249
1.78k
            return None;
250
5.14k
        }
251
5.14k
252
5.14k
        Some(SubIdentifierIterator {
253
5.14k
            oid: self,
254
5.14k
            pos: 0,
255
5.14k
            first: false,
256
5.14k
            n: core::marker::PhantomData,
257
5.14k
        })
258
6.92k
    }
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
274
125k
    fn next(&mut self) -> Option<Self::Item> {
275
        use num_traits::identities::Zero;
276
277
125k
        if self.pos == self.oid.asn1.len() {
278
4.78k
            return None;
279
120k
        }
280
120k
        if !self.oid.relative {
281
120k
            if !self.first {
282
5.14k
                debug_assert!(self.pos == 0);
283
5.14k
                self.first = true;
284
5.14k
                return Some((self.oid.asn1[0] / 40).into());
285
115k
            } else if self.pos == 0 {
286
5.14k
                self.pos += 1;
287
5.14k
                if self.oid.asn1[0] == 0 && self.oid.asn1.len() == 1 {
288
355
                    return None;
289
4.78k
                }
290
4.78k
                return Some((self.oid.asn1[0] % 40).into());
291
110k
            }
292
0
        }
293
        // decode objet sub-identifier according to the asn.1 standard
294
110k
        let mut res = <N as Zero>::zero();
295
125k
        for o in self.oid.asn1[self.pos..].iter() {
296
125k
            self.pos += 1;
297
125k
            res = (res << 7) + (o & 0b111_1111).into();
298
125k
            let flag = o >> 7;
299
125k
            if flag == 0u8 {
300
110k
                break;
301
14.9k
            }
302
        }
303
110k
        Some(res)
304
125k
    }
305
}
306
307
impl<'a, N: Repr> FusedIterator for SubIdentifierIterator<'a, N> {}
308
309
impl<'a, N: Repr> ExactSizeIterator for SubIdentifierIterator<'a, N> {
310
0
    fn len(&self) -> usize {
311
0
        if self.oid.relative {
312
0
            self.oid.asn1.iter().filter(|o| (*o >> 7) == 0u8).count()
313
0
        } else if self.oid.asn1.len() == 0 {
314
0
            0
315
0
        } else if self.oid.asn1.len() == 1 {
316
0
            if self.oid.asn1[0] == 0 {
317
0
                1
318
            } else {
319
0
                2
320
            }
321
        } else {
322
0
            2 + self.oid.asn1[2..]
323
0
                .iter()
324
0
                .filter(|o| (*o >> 7) == 0u8)
325
0
                .count()
326
        }
327
0
    }
328
329
    #[cfg(feature = "exact_size_is_empty")]
330
    fn is_empty(&self) -> bool {
331
        self.oid.asn1.is_empty()
332
    }
333
}
334
335
impl<'a> fmt::Display for Oid<'a> {
336
6.92k
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
337
6.92k
        if self.relative {
338
0
            f.write_str("rel. ")?;
339
6.92k
        }
340
6.92k
        f.write_str(&self.to_id_string())
341
6.92k
    }
342
}
343
344
impl<'a> fmt::Debug for Oid<'a> {
345
0
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
346
0
        f.write_str("OID(")?;
347
0
        <Oid as fmt::Display>::fmt(self, f)?;
348
0
        f.write_str(")")
349
0
    }
350
}
351
352
impl<'a> FromStr for Oid<'a> {
353
    type Err = ParseError;
354
355
0
    fn from_str(s: &str) -> Result<Self, Self::Err> {
356
0
        let v: Result<Vec<_>, _> = s.split('.').map(|c| c.parse::<u64>()).collect();
357
0
        v.map_err(|_| ParseError::ParseIntError)
358
0
            .and_then(|v| Oid::from(&v))
359
0
    }
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]
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]
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]
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]
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]
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
}