Skip to content

Commit

Permalink
const-oid: fix off-by-one error parsing large BER arcs (#84)
Browse files Browse the repository at this point in the history
Arcs are currently modeled as `u32`. When encoded as base 128, they can
be up to 5 bytes, since the 4 preceeding bytes provide only 7-bits of
the final integer value.
  • Loading branch information
tarcieri committed Oct 12, 2021
1 parent a06d563 commit 415a426
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 36 deletions.
36 changes: 21 additions & 15 deletions const-oid/src/arcs.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
//! Arcs are integer values which exist within an OID's hierarchy.

use crate::{Error, ObjectIdentifier, Result};
use core::convert::TryFrom;
use core::{convert::TryFrom, mem};

/// Type used to represent an "arc" (i.e. integer identifier value)
/// Type used to represent an "arc" (i.e. integer identifier value).
pub type Arc = u32;

/// Maximum value of the first arc in an OID
pub(crate) const FIRST_ARC_MAX: Arc = 2;
/// Maximum value of the first arc in an OID.
pub(crate) const ARC_MAX_FIRST: Arc = 2;

/// Maximum value of the second arc in an OID
pub(crate) const SECOND_ARC_MAX: Arc = 39;
/// Maximum value of the second arc in an OID.
pub(crate) const ARC_MAX_SECOND: Arc = 39;

/// Maximum number of bytes supported in an arc.
pub(crate) const ARC_MAX_BYTES: usize = mem::size_of::<Arc>();

/// Maximum value of the last byte in an arc.
pub(crate) const ARC_MAX_LAST_OCTET: u8 = 0b11110000; // Max bytes of leading 1-bits

/// [`Iterator`] over arcs (a.k.a. nodes) in an [`ObjectIdentifier`].
///
Expand Down Expand Up @@ -55,8 +61,8 @@ impl<'a> Iterator for Arcs<'a> {
match self.oid.as_bytes().get(offset + arc_bytes).cloned() {
Some(byte) => {
arc_bytes += 1;
assert!(
arc_bytes < 4 || byte & 0b11110000 == 0,
debug_assert!(
arc_bytes < ARC_MAX_BYTES || byte & ARC_MAX_LAST_OCTET == 0,
"OID arc overflowed"
);
result = result << 7 | (byte & 0b1111111) as Arc;
Expand All @@ -67,7 +73,7 @@ impl<'a> Iterator for Arcs<'a> {
}
}
None => {
assert_eq!(arc_bytes, 0, "truncated OID");
debug_assert_eq!(arc_bytes, 0, "truncated OID");
return None;
}
}
Expand All @@ -88,31 +94,31 @@ impl RootArcs {
/// Create [`RootArcs`] from the first and second arc values represented
/// as `Arc` integers.
pub(crate) fn new(first_arc: Arc, second_arc: Arc) -> Result<Self> {
if first_arc > FIRST_ARC_MAX || second_arc > SECOND_ARC_MAX {
if first_arc > ARC_MAX_FIRST || second_arc > ARC_MAX_SECOND {
return Err(Error);
}

let byte = (first_arc * (SECOND_ARC_MAX + 1)) as u8 + second_arc as u8;
let byte = (first_arc * (ARC_MAX_SECOND + 1)) as u8 + second_arc as u8;
Ok(Self(byte))
}

/// Get the value of the first arc
pub(crate) fn first_arc(self) -> Arc {
self.0 as Arc / (SECOND_ARC_MAX + 1)
self.0 as Arc / (ARC_MAX_SECOND + 1)
}

/// Get the value of the second arc
pub(crate) fn second_arc(self) -> Arc {
self.0 as Arc % (SECOND_ARC_MAX + 1)
self.0 as Arc % (ARC_MAX_SECOND + 1)
}
}

impl TryFrom<u8> for RootArcs {
type Error = Error;

fn try_from(octet: u8) -> Result<Self> {
let first = octet as Arc / (SECOND_ARC_MAX + 1);
let second = octet as Arc % (SECOND_ARC_MAX + 1);
let first = octet as Arc / (ARC_MAX_SECOND + 1);
let second = octet as Arc % (ARC_MAX_SECOND + 1);
let result = Self::new(first, second)?;
debug_assert_eq!(octet, result.0);
Ok(result)
Expand Down
8 changes: 4 additions & 4 deletions const-oid/src/encoder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! OID encoder with `const` support.

use crate::{
arcs::{FIRST_ARC_MAX, SECOND_ARC_MAX},
arcs::{ARC_MAX_FIRST, ARC_MAX_SECOND},
Arc, Error, ObjectIdentifier, Result,
};

Expand Down Expand Up @@ -43,14 +43,14 @@ impl Encoder {
pub(crate) const fn encode(mut self, arc: Arc) -> Self {
match self.state {
State::Initial => {
const_assert!(arc <= FIRST_ARC_MAX, "invalid first arc (must be 0-2)");
const_assert!(arc <= ARC_MAX_FIRST, "invalid first arc (must be 0-2)");
self.state = State::FirstArc(arc);
self
}
State::FirstArc(first_arc) => {
const_assert!(arc <= SECOND_ARC_MAX, "invalid second arc (must be 0-39)");
const_assert!(arc <= ARC_MAX_SECOND, "invalid second arc (must be 0-39)");
self.state = State::Body;
self.bytes[0] = (first_arc * (SECOND_ARC_MAX + 1)) as u8 + arc as u8;
self.bytes[0] = (first_arc * (ARC_MAX_SECOND + 1)) as u8 + arc as u8;
self.cursor = 1;
self
}
Expand Down
8 changes: 4 additions & 4 deletions const-oid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ pub use crate::{
error::{Error, Result},
};

use crate::arcs::RootArcs;
use crate::arcs::{RootArcs, ARC_MAX_BYTES, ARC_MAX_LAST_OCTET};
use core::{convert::TryFrom, fmt, str::FromStr};

/// Object identifier (OID).
Expand Down Expand Up @@ -179,13 +179,13 @@ impl ObjectIdentifier {
while arc_offset < len {
match ber_bytes.get(arc_offset + arc_bytes).cloned() {
Some(byte) => {
arc_bytes += 1;

if arc_bytes == 4 && byte & 0b11110000 != 0 {
if (arc_bytes == ARC_MAX_BYTES) && (byte & ARC_MAX_LAST_OCTET != 0) {
// Overflowed `Arc` (u32)
return Err(Error);
}

arc_bytes += 1;

if byte & 0b10000000 == 0 {
arc_offset += arc_bytes;
arc_bytes = 0;
Expand Down
33 changes: 20 additions & 13 deletions const-oid/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,20 @@ use const_oid::ObjectIdentifier;
use hex_literal::hex;
use std::string::ToString;

/// Example OID value with a root arc of `1`
const EXAMPLE_OID_1: ObjectIdentifier = ObjectIdentifier::new("1.2.840.10045.2.1");
/// Example OID value with a root arc of `0` (and large arc).
const EXAMPLE_OID_0_STRING: &str = "0.9.2342.19200300.100.1.1";
const EXAMPLE_OID_0_BER: &[u8] = &hex!("0992268993F22C640101");
const EXAMPLE_OID_0: ObjectIdentifier = ObjectIdentifier::new(EXAMPLE_OID_0_STRING);

/// Example OID value with a root arc of `2`
const EXAMPLE_OID_2: ObjectIdentifier = ObjectIdentifier::new("2.16.840.1.101.3.4.1.42");

/// Example OID 1 encoded as ASN.1 BER/DER
const EXAMPLE_OID_1_BER: &[u8] = &hex!("2A8648CE3D0201");

/// Example OID 2 encoded as ASN.1 BER/DER
const EXAMPLE_OID_2_BER: &[u8] = &hex!("60864801650304012A");

/// Example OID 1 as a string
/// Example OID value with a root arc of `1`.
const EXAMPLE_OID_1_STRING: &str = "1.2.840.10045.2.1";
const EXAMPLE_OID_1_BER: &[u8] = &hex!("2A8648CE3D0201");
const EXAMPLE_OID_1: ObjectIdentifier = ObjectIdentifier::new(EXAMPLE_OID_1_STRING);

/// Example OID 2 as a string
/// Example OID value with a root arc of `2`.
const EXAMPLE_OID_2_STRING: &str = "2.16.840.1.101.3.4.1.42";
const EXAMPLE_OID_2_BER: &[u8] = &hex!("60864801650304012A");
const EXAMPLE_OID_2: ObjectIdentifier = ObjectIdentifier::new(EXAMPLE_OID_2_STRING);

#[test]
fn display() {
Expand All @@ -33,6 +30,11 @@ fn display() {

#[test]
fn from_bytes() {
let oid0 = ObjectIdentifier::from_bytes(EXAMPLE_OID_0_BER).unwrap();
assert_eq!(oid0.arc(0).unwrap(), 0);
assert_eq!(oid0.arc(1).unwrap(), 9);
assert_eq!(oid0, EXAMPLE_OID_0);

let oid1 = ObjectIdentifier::from_bytes(EXAMPLE_OID_1_BER).unwrap();
assert_eq!(oid1.arc(0).unwrap(), 1);
assert_eq!(oid1.arc(1).unwrap(), 2);
Expand All @@ -53,6 +55,11 @@ fn from_bytes() {

#[test]
fn from_str() {
let oid0 = EXAMPLE_OID_0_STRING.parse::<ObjectIdentifier>().unwrap();
assert_eq!(oid0.arc(0).unwrap(), 0);
assert_eq!(oid0.arc(1).unwrap(), 9);
assert_eq!(oid0, EXAMPLE_OID_0);

let oid1 = EXAMPLE_OID_1_STRING.parse::<ObjectIdentifier>().unwrap();
assert_eq!(oid1.arc(0).unwrap(), 1);
assert_eq!(oid1.arc(1).unwrap(), 2);
Expand Down

0 comments on commit 415a426

Please sign in to comment.