Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OID macro! #181

Merged
merged 12 commits into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
matrix:
RUST:
# MSRV
- VERSION: "1.41.0"
- VERSION: "1.47.0"
FLAGS: "--no-default-features --features std,derive"
- VERSION: stable
FLAGS: ""
Expand Down
53 changes: 53 additions & 0 deletions asn1_derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
extern crate proc_macro;

use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
Expand Down Expand Up @@ -439,3 +440,55 @@ fn generate_enum_write_block(name: &syn::Ident, data: &syn::DataEnum) -> proc_ma
}
}
}

// TODO: Duplicate of this function in src/object_identifier.rs, can we
// de-dupe?
fn _write_base128_int(data: &mut Vec<u8>, n: u32) {
if n == 0 {
data.push(0);
return;
}

let mut l = 0;
let mut i = n;
while i > 0 {
l += 1;
i >>= 7;
}

for i in (0..l).rev() {
let mut o = (n >> (i * 7)) as u8;
o &= 0x7f;
if i != 0 {
o |= 0x80;
}
data.push(o);
}
}

#[proc_macro]
pub fn oid(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let p_arcs = Punctuated::<syn::LitInt, syn::Token![,]>::parse_terminated
.parse(item)
.unwrap();
let mut arcs = p_arcs.iter();

let mut der_encoded = vec![];
let first = arcs.next().unwrap().base10_parse::<u32>().unwrap();
let second = arcs.next().unwrap().base10_parse::<u32>().unwrap();
_write_base128_int(&mut der_encoded, 40 * first + second);
for arc in arcs {
_write_base128_int(&mut der_encoded, arc.base10_parse().unwrap());
}

let der_len = der_encoded.len();
// TODO: is there a way to use the `MAX_OID_LENGTH` constant here?
assert!(der_len <= 63);
der_encoded.resize(63, 0);
let der_lit = syn::LitByteStr::new(&der_encoded, proc_macro2::Span::call_site());
let expanded = quote::quote! {
asn1::ObjectIdentifier::from_der_unchecked(*#der_lit, #der_len as u8)
};

proc_macro::TokenStream::from(expanded)
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub use crate::types::{Explicit, Implicit};
pub use crate::writer::{write, write_single, Writer};

#[cfg(feature = "derive")]
pub use asn1_derive::{Asn1Read, Asn1Write};
pub use asn1_derive::{oid, Asn1Read, Asn1Write};

/// Decodes an `OPTIONAL` ASN.1 value which has a `DEFAULT`. Generaly called
/// immediately after [`Parser::read_element`].
Expand Down
103 changes: 74 additions & 29 deletions src/object_identifier.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
use alloc::borrow::Cow;
use crate::parser::{ParseError, ParseErrorKind, ParseResult};
use alloc::fmt;
use alloc::vec;
use alloc::vec::Vec;

const MAX_OID_LENGTH: usize = 63;

/// Represents an ASN.1 `OBJECT IDENTIFIER`. ObjectIdentifiers are opaque, the only thing may be
/// done with them is test if they are equal to another `ObjectIdentifier`. The generally
/// recommended practice for handling them is to create some `ObjectIdentifier` constants with
/// `ObjectIdentifier::from_string` and then compare ObjectIdentifiers you get from parsing to
/// `asn1::oid!()` and then compare ObjectIdentifiers you get from parsing to
/// those.
///
/// `asn1::oid!()` takes a series of arcs, for example: `asn1::oid!(1, 2, 3)`.
///
/// rust-asn1 stores `ObjectIdentifier`s in a fixed-size buffer, therefore
/// they are limited to OID values whose DER encoding fits into that buffer.
/// This buffer is sufficiently large to fit all known publically known OIDs,
/// so this should not effect most people.
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct ObjectIdentifier<'a> {
// Store the OID as DER encoded. This means we can 0-copy on parse.
der_encoded: Cow<'a, [u8]>,
pub struct ObjectIdentifier {
// Store the OID as DER encoded.
der_encoded: [u8; MAX_OID_LENGTH],
der_encoded_len: u8,
}

fn _read_base128_int<I: Iterator<Item = u8>>(mut reader: I) -> Option<u32> {
fn _read_base128_int<I: Iterator<Item = u8>>(mut reader: I) -> ParseResult<u32> {
let mut ret = 0u32;
for _ in 0..4 {
let b = reader.next()?;
let b = reader
.next()
.ok_or_else(|| ParseError::new(ParseErrorKind::InvalidValue))?;
ret <<= 7;
ret |= u32::from(b & 0x7f);
if b & 0x80 == 0 {
return Some(ret);
return Ok(ret);
}
}
None
Err(ParseError::new(ParseErrorKind::InvalidValue))
}

fn _write_base128_int(data: &mut Vec<u8>, n: u32) {
fn _write_base128_int(data: &mut [u8], data_len: &mut usize, n: u32) -> Option<()> {
if n == 0 {
data.push(0);
return;
if *data_len >= data.len() {
return None;
}
data[*data_len] = 0;
*data_len += 1;
return Some(());
}

let mut l = 0;
Expand All @@ -46,13 +60,19 @@ fn _write_base128_int(data: &mut Vec<u8>, n: u32) {
if i != 0 {
o |= 0x80;
}
data.push(o);
if *data_len >= data.len() {
return None;
}
data[*data_len] = o;
*data_len += 1;
}

Some(())
}

impl<'a> ObjectIdentifier<'a> {
impl ObjectIdentifier {
/// Parses an OID from a dotted string, e.g. `"1.2.840.113549"`.
pub fn from_string(oid: &str) -> Option<ObjectIdentifier<'a>> {
pub fn from_string(oid: &str) -> Option<ObjectIdentifier> {
let mut parts = oid.split('.');

let first = parts.next()?.parse::<u32>().ok()?;
Expand All @@ -61,42 +81,61 @@ impl<'a> ObjectIdentifier<'a> {
return None;
}

let mut der_data = vec![];
_write_base128_int(&mut der_data, 40 * first + second);
let mut der_data = [0; MAX_OID_LENGTH];
let mut der_data_len = 0;
_write_base128_int(&mut der_data, &mut der_data_len, 40 * first + second)?;
for part in parts {
_write_base128_int(&mut der_data, part.parse::<u32>().ok()?);
_write_base128_int(&mut der_data, &mut der_data_len, part.parse::<u32>().ok()?)?;
}
Some(ObjectIdentifier {
der_encoded: Cow::Owned(der_data),
der_encoded: der_data,
der_encoded_len: der_data_len as u8,
})
}

/// Creates an `ObjectIdentifier` from its DER representation. This does
/// not perform any allocations or copies.
pub fn from_der(data: &'a [u8]) -> Option<ObjectIdentifier<'a>> {
pub fn from_der(data: &[u8]) -> ParseResult<ObjectIdentifier> {
if data.is_empty() {
return None;
return Err(ParseError::new(ParseErrorKind::InvalidValue));
} else if data.len() > MAX_OID_LENGTH {
return Err(ParseError::new(ParseErrorKind::OidTooLong));
}
let mut cursor = data.iter().copied();
while cursor.len() > 0 {
_read_base128_int(&mut cursor)?;
}

Some(ObjectIdentifier {
der_encoded: Cow::Borrowed(data),
let mut storage = [0; MAX_OID_LENGTH];
storage[..data.len()].copy_from_slice(data);

Ok(ObjectIdentifier {
der_encoded: storage,
der_encoded_len: data.len() as u8,
})
}

/// Creates an `ObjectIdentifier` from its DER representation. Does not
/// check that the DER is valid. Intended only for use from the `oid!()`
/// macro. Do not use yourself!
#[doc(hidden)]
pub const fn from_der_unchecked(data: [u8; MAX_OID_LENGTH], data_len: u8) -> ObjectIdentifier {
ObjectIdentifier {
der_encoded: data,
der_encoded_len: data_len,
}
}

pub(crate) fn as_der(&self) -> &[u8] {
&self.der_encoded
&self.der_encoded[..self.der_encoded_len as usize]
}
}

impl fmt::Display for ObjectIdentifier<'_> {
impl fmt::Display for ObjectIdentifier {
/// Converts an `ObjectIdentifier` to a dotted string, e.g.
/// "1.2.840.113549".
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut cursor = self.der_encoded.iter().copied();
let mut cursor = self.as_der().iter().copied();

let first = _read_base128_int(&mut cursor).unwrap();
if first < 80 {
Expand All @@ -116,7 +155,7 @@ impl fmt::Display for ObjectIdentifier<'_> {

#[cfg(test)]
mod tests {
use crate::ObjectIdentifier;
use crate::{ObjectIdentifier, ParseError, ParseErrorKind};

#[test]
fn test_object_identifier_from_string() {
Expand All @@ -131,6 +170,7 @@ mod tests {
".2.5",
"2..5",
"2.5.",
"1.3.6.1.4.1.1248.1.1.2.1.3.21.69.112.115.111.110.32.83.116.121.108.117.115.32.80.114.111.32.52.57.48.48.123.124412.31.213321.123.110.32.83.116.121.108.117.115.32.80.114.111.32.52.57.48.48.123.124412.31.213321.123",
] {
assert_eq!(ObjectIdentifier::from_string(val), None);
}
Expand All @@ -147,6 +187,11 @@ mod tests {
}
}

#[test]
fn test_from_der() {
assert_eq!(ObjectIdentifier::from_der(b"\x06\x40\x2b\x06\x01\x04\x01\x89\x60\x01\x01\x02\x01\x03\x15\x45\x70\x73\x6f\x6e\x20\x53\x74\x79\x6c\x75\x73\x20\x50\x72\x6f\x20\x34\x39\x30\x30\x7b\x87\xcb\x7c\x1f\x8d\x82\x49\x7b\x2b\x06\x01\x04\x01\x89\x60\x01\x01\x02\x01\x03\x15\x45\x70\x73\x6f\x6e\x20"), Err(ParseError::new(ParseErrorKind::OidTooLong)));
}

#[test]
fn test_to_string() {
for val in &[
Expand Down
9 changes: 8 additions & 1 deletion src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pub enum ParseErrorKind {
InvalidSetOrdering,
/// An OPTIONAL DEFAULT was written with a default value.
EncodedDefault,
/// OID value is longer than the maximum size rust-asn1 can store. This is
/// a limitation of rust-asn1.
OidTooLong,
}

#[derive(Debug, PartialEq)]
Expand Down Expand Up @@ -112,6 +115,10 @@ impl fmt::Display for ParseError {
ParseErrorKind::ExtraData => write!(f, "extra data"),
ParseErrorKind::InvalidSetOrdering => write!(f, "SET value was ordered incorrectly"),
ParseErrorKind::EncodedDefault => write!(f, "DEFAULT value was explicitly encoded"),
ParseErrorKind::OidTooLong => write!(
f,
"OBJECT IDENTIFIER was too large to be stored in rust-asn1's buffer"
),
}
}
}
Expand Down Expand Up @@ -688,7 +695,7 @@ mod tests {

#[test]
fn test_parse_object_identifier() {
assert_parses::<ObjectIdentifier<'_>>(&[
assert_parses::<ObjectIdentifier>(&[
(
Ok(ObjectIdentifier::from_string("2.5").unwrap()),
b"\x06\x01\x55",
Expand Down
7 changes: 3 additions & 4 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,14 +634,13 @@ impl<'a> SimpleAsn1Writable<'a> for BigInt<'a> {
}
}

impl<'a> SimpleAsn1Readable<'a> for ObjectIdentifier<'a> {
impl<'a> SimpleAsn1Readable<'a> for ObjectIdentifier {
const TAG: u8 = 0x06;
fn parse_data(data: &'a [u8]) -> ParseResult<ObjectIdentifier<'a>> {
fn parse_data(data: &'a [u8]) -> ParseResult<ObjectIdentifier> {
ObjectIdentifier::from_der(data)
.ok_or_else(|| ParseError::new(ParseErrorKind::InvalidValue))
}
}
impl<'a> SimpleAsn1Writable<'a> for ObjectIdentifier<'a> {
impl<'a> SimpleAsn1Writable<'a> for ObjectIdentifier {
const TAG: u8 = 0x06;
fn write_data(&self, dest: &mut Vec<u8>) {
dest.extend_from_slice(self.as_der());
Expand Down
23 changes: 23 additions & 0 deletions tests/oid_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![cfg(feature = "derive")]

#[test]
fn test_oid_value() {
assert_eq!(
asn1::oid!(1, 2, 3, 4),
asn1::ObjectIdentifier::from_string("1.2.3.4").unwrap()
);
}

#[test]
fn test_match_statement() {
const OID1: asn1::ObjectIdentifier = asn1::oid!(1, 2, 3, 4);
const OID2: asn1::ObjectIdentifier = asn1::oid!(1, 2, 3, 5);

let oid = asn1::ObjectIdentifier::from_string("1.2.3.4").unwrap();
let result = match oid {
OID1 => "abc",
OID2 => "def",
_ => "ghi",
};
assert_eq!(result, "abc");
}