GithubHelp home page GithubHelp logo

asn1-rs's Introduction

License: MIT Apache License 2.0 docs.rs crates.io Download numbers Github CI Minimum rustc version

BER/DER Parsers/Encoders

A set of parsers/encoders for Basic Encoding Rules (BER [X.690]) and Distinguished Encoding Rules(DER [X.690]) formats, implemented with the nom parser combinator framework.

It is written in pure Rust, fast, and makes extensive use of zero-copy. A lot of care is taken to ensure security and safety of this crate, including design (recursion limit, defensive programming), tests, and fuzzing. It also aims to be panic-free.

This crate is a rewrite of der-parser to propose a more data-oriented API, and add generalized support for serialization.

Many ideas were borrowed from the crypto/utils/der crate (like the Any/TryFrom/FromDer mechanism), adapted and merged into a generalized BER/DER crate. Credits (and many thanks) go to Tony Arcieri for writing the original crate.

BER/DER parsers

BER stands for Basic Encoding Rules, and is defined in [X.690]. It defines a set of rules to encode and decode ASN.1 [X.680] objects in binary.

[X.690] also defines Distinguished Encoding Rules (DER), which is BER with added rules to ensure canonical and unequivocal binary representation of objects.

The choice of which one to use is usually guided by the speficication of the data format based on BER or DER: for example, X.509 uses DER as encoding representation.

The main traits for parsing are the [FromBer] and [FromDer] traits. These traits provide methods to parse binary input, and return either the remaining (unparsed) bytes and the parsed object, or an error.

The parsers follow the interface from nom, and the [ParseResult] object is a specialized version of nom::IResult. This means that most nom combinators (map, many0, etc.) can be used in combination to objects and methods from this crate. Reading the nom documentation may help understanding how to write and combine parsers and use the output.

Minimum Supported Rust Version: 1.63.0

Recipes

See [doc::recipes] and [doc::derive] for more examples and recipes.

See [doc::debug] for advice and tools to debug parsers.

Examples

Parse 2 BER integers:

use asn1_rs::{Integer, FromBer};

let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
              0x02, 0x03, 0x01, 0x00, 0x00,
];

let (rem, obj1) = Integer::from_ber(&bytes).expect("parsing failed");
let (rem, obj2) = Integer::from_ber(&bytes).expect("parsing failed");

assert_eq!(obj1, Integer::from_u32(65537));

In the above example, the generic [Integer] type is used. This type can contain integers of any size, but do not provide a simple API to manipulate the numbers.

In most cases, the integer either has a limit, or is expected to fit into a primitive type. To get a simple value, just use the from_ber/from_der methods on the primitive types:

use asn1_rs::FromBer;

let bytes = [ 0x02, 0x03, 0x01, 0x00, 0x01,
              0x02, 0x03, 0x01, 0x00, 0x00,
];

let (rem, obj1) = u32::from_ber(&bytes).expect("parsing failed");
let (rem, obj2) = u32::from_ber(&rem).expect("parsing failed");

assert_eq!(obj1, 65537);
assert_eq!(obj2, 65536);

If the parsing succeeds, but the integer cannot fit into the expected type, the method will return an IntegerTooLarge error.

BER/DER encoders

BER/DER encoding is symmetrical to decoding, using the traits ToBer and [ToDer] traits. These traits provide methods to write encoded content to objects with the io::Write trait, or return an allocated Vec<u8> with the encoded data. If the serialization fails, an error is returned.

Examples

Writing 2 BER integers:

use asn1_rs::{Integer, ToDer};

let mut writer = Vec::new();

let obj1 = Integer::from_u32(65537);
let obj2 = Integer::from_u32(65536);

let _ = obj1.write_der(&mut writer).expect("serialization failed");
let _ = obj2.write_der(&mut writer).expect("serialization failed");

let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
               0x02, 0x03, 0x01, 0x00, 0x00,
];
assert_eq!(&writer, bytes);

Similarly to FromBer/FromDer, serialization methods are also implemented for primitive types:

use asn1_rs::ToDer;

let mut writer = Vec::new();

let _ = 65537.write_der(&mut writer).expect("serialization failed");
let _ = 65536.write_der(&mut writer).expect("serialization failed");

let bytes = &[ 0x02, 0x03, 0x01, 0x00, 0x01,
               0x02, 0x03, 0x01, 0x00, 0x00,
];
assert_eq!(&writer, bytes);

If the parsing succeeds, but the integer cannot fit into the expected type, the method will return an IntegerTooLarge error.

Changes

See CHANGELOG.md.

References

  • [X.680] Abstract Syntax Notation One (ASN.1): Specification of basic notation.
  • [X.690] ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER).

Changes

See CHANGELOG.md, and UPGRADING.md for instructions for upgrading major versions.

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

asn1-rs's People

Contributors

chifflier avatar cpu avatar dequbed avatar ibeckermayer avatar kleinweby avatar la10736 avatar pc-anssi avatar sergiobenitez avatar wojnilowicz avatar yestyle avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

asn1-rs's Issues

Please release asn1-rs with syn 2

I would like to remove syn 1 from my dependency tree (since it's slow to build). As far as I can tell, the upgrade isn't even breaking (at least given how I use asn1-rs/der-parser/x509-parser).

RUSTSEC-2020-0071: Potential segfault in the time crate

Potential segfault in the time crate

Details
Package time
Version 0.1.44
URL time-rs/time#293
Date 2020-11-18
Patched versions >=0.2.23
Unaffected versions =0.2.0,=0.2.1,=0.2.2,=0.2.3,=0.2.4,=0.2.5,=0.2.6

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

The affected functions from time 0.2.7 through 0.2.22 are:

  • time::UtcOffset::local_offset_at
  • time::UtcOffset::try_local_offset_at
  • time::UtcOffset::current_local_offset
  • time::UtcOffset::try_current_local_offset
  • time::OffsetDateTime::now_local
  • time::OffsetDateTime::try_now_local

The affected functions in time 0.1 (all versions) are:

  • at
  • at_utc

Non-Unix targets (including Windows and wasm) are unaffected.

Patches

Pending a proper fix, the internal method that determines the local offset has been modified to always return None on the affected operating systems. This has the effect of returning an Err on the try_* methods and UTC on the non-try_* methods.

Users and library authors with time in their dependency tree should perform cargo update, which will pull in the updated, unaffected code.

Users of time 0.1 do not have a patch and should upgrade to an unaffected version: time 0.2.23 or greater or the 0.3. series.

Workarounds

No workarounds are known.

References

time-rs/time#293

See advisory page for additional details.

Serialized tag of `SetOf` is not correct

Hi there,

I was trying to use this crate to serialize a SetOf data and found that the serialized tag of SetOf is 0x30, but the correct one should be 0x31 if I understand it correctly.

The issue can be demonstrated by a simple example:

let it = [2, 3, 4].iter();
let set = SetOf::from_iter(it);
let mut der = Vec::new();
set.write_der(&mut der).unwrap();

The content of der is:

30 09 02 01 02 02 01 03 02 01 04

which I think should be:

31 09 02 01 02 02 01 03 02 01 04

My wild guess is that the tag for Vec is always Tag::Sequence.

Could you please advise if this is an issue or not? Thanks.

`Option<T>` with `#[derive(DerSequence)]` fails in certain scenarios

impl<'a, T> FromDer<'a> for Option<T> has a bug where deserialization of Option<T> in a struct annotated with #[derive(DerSequence)] will fail in with DerConstraintFailed(..) in certain cases.

Example of test failure where test() here will fail with Error(DerConstraintFailed(InvalidBoolean)) from TestBool::from_der(expected).unwrap() as bool/Boolean has a strict DER constraint check on the first byte of the following field:

#[derive(DerSequence, Debug, PartialEq)]
struct TestBool {
    a: u16,
    b: Option<bool>,
    c: u32,
}

#[test]
fn test() {
    let x = TestBool {
        a: 0x1234,
        b: None,
        c: 0x5678,
    };

    let mut buffer = vec![];
    let _ = x.write_der(&mut buffer).unwrap();

    let expected = &[48, 8, 2, 2, 18, 52, 2, 2, 86, 120];
    assert_eq!(buffer, expected);

    let (_, val) = TestBool::from_der(expected).unwrap();
    assert_eq!(val, x);
}

#[test]
fn test2() {
    let x = TestBool {
        a: 0x1234,
        b: Some(true),
        c: 0x5678,
    };

    let mut buffer = vec![];
    let _ = x.write_der(&mut buffer).unwrap();

    let expected = &[48, 11, 2, 2, 18, 52, 1, 1, 255, 2, 2, 86, 120];
    assert_eq!(buffer, expected);

    let (_, val) = TestBool::from_der(expected).unwrap();
    assert_eq!(val, x);
}

A similar example where the contained type has more relaxed constraints (u32 instead of bool) works:

#[derive(DerSequence, Debug, PartialEq)]
struct TestInt {
    a: u16,
    b: Option<u32>,
    c: bool,
}

#[test]
fn test3() {
    let x = TestInt {
        a: 0x1234,
        b: None,
        c: true,
    };

    let mut buffer = vec![];
    let _ = x.write_der(&mut buffer).unwrap();

    let expected = &[48, 7, 2, 2, 18, 52, 1, 1, 255];
    assert_eq!(buffer, expected);

    let (_, val) = TestInt::from_der(expected).unwrap();
    assert_eq!(val, x);
}

#[test]
fn test4() {
    let x = TestInt {
        a: 0x1234,
        b: Some(0x5678),
        c: true,
    };

    let mut buffer = vec![];
    let _ = x.write_der(&mut buffer).unwrap();

    let expected = &[48, 11, 2, 2, 18, 52, 2, 2, 86, 120, 1, 1, 255];
    assert_eq!(buffer, expected);

    let (_, val) = TestInt::from_der(expected).unwrap();
    assert_eq!(val, x);
}

This could be fixed by accounting for Error::DerConstraintFailed in impl<'a, T> FromDer<'a> for Option<T>, but perhaps there are other edge cases to consider as well?

impl<'a, T> FromDer<'a> for Option<T>
where
    T: FromDer<'a>,
{
    fn from_der(bytes: &'a [u8]) -> ParseResult<Self> {
        if bytes.is_empty() {
            return Ok((bytes, None));
        }
        match T::from_der(bytes) {
            Ok((rem, t)) => Ok((rem, Some(t))),
            Err(nom::Err::Error(Error::UnexpectedTag { .. })) => Ok((bytes, None)),
            Err(nom::Err::Error(Error::DerConstraintFailed(..))) => Ok((bytes, None)),
            Err(e) => Err(e),
        }
    }
}

Make Oid::from a const fn

Currently, if I want to compare parsed OIDs against expected constants, the best I can do with the current API is the following:

const OID_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.1";
const OID_EC_PUBLIC_KEY: &str = "1.2.840.10045.2.1";

fn foo(oid: Oid) {
    match oid.to_string().as_str() {
        OID_RSA_ENCRYPTION => { .. },
        OID_EC_PUBLIC_KEY => { .. },
    }
}

If Oid::from was a const fn, then we could define the constants using something like:

const OID_RSA_ENCRYPTION: Oid = Oid::from(&[1, 2, 840, 113549, 1, 1, 1]);
const OID_EC_PUBLIC_KEY: Oid = Oid::from(&[1, 2, 840, 10045, 2, 1]);

fn foo(oid: Oid) {
    match oid {
        OID_RSA_ENCRYPTION => { .. },
        OID_EC_PUBLIC_KEY => { .. },
    }
}

This probably requires rusticata/der-parser#17.

Make `ToDer` trait potentially more convenient by keeping state from `to_der_len` for `write_der_content`

This is just a abstract idea I had because I'm currently writing a serializer using this crate:

The pattern I hit right now is that to be able to calculate the final encoded length for write_der_header I have to wrap all tagged fields and resolve things like their lengths, recursively.

But I have to throw away that state directly afterwards, only to reconstruct it again in the directly following call to write_der_content.

It's not a big overhead by any means, but I was wondering if I could remove it regardless.

Is that something that'd you be interested in as well and/or would want to work on together?

An idea I had is to make ToDer have an associated type that stores what is in essence a weak normalized form. The basic idea is that the WNF can be encoded into bytes directly without conditionals and without having to allocate/wrap any fields.

That solution would however make ToDer not object-safe until GATs for trait objects land. Not sure if that is a significant usability problem though.

(Fun fact: The last time I had this issue in asnom I actually inverted encoding, writing out the bodies of fields first and the headers afterwards, making heavy use of scatter/gather-IO. But that approach was absurdly complex, hard to use, and not all that efficient in most cases anyway.)

`oid!` macro now fails if given anything besides a single expr

The oid! macro in der-parser v6 accepted any number of tokens, which allowed me to construct an invocation to it in my own macro where I fed it a series of literals and periods. Trying this in v8 fails with a macro error, which appears to be due to the asn1-rs version of the macro expecting a single $items:expr arg instead of something like $($item:tt)+. This breaks my code as I construct a whole tree of oids using my own macro.

A simple reproduction looks like

// Code sample provided under the MIT license
macro_rules! foo {
  ($a:literal $b:literal $c:literal) => {
    der_parser::oid!($a.$b.$c)
  };
}

fn main() {
  dbg!(foo!(1 2 3));
}

This works just fine with der-parser v6 but fails with der-parser v8

error: unexpected token: `2`
 --> foo.rs:4:22
  |
4 |  der_parser::oid!($a.$b.$c)
  |                      ^^
...
9 |  dbg!(foo!(1 2 3));
  |       ----------- in this macro invocation
  |
  = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)

error: no rules expected the token `2`
   --> foo.rs:4:22
    |
4   |  der_parser::oid!($a.$b.$c)
    |                      ^^ no rules expected this token in macro call
...
9   |  dbg!(foo!(1 2 3));
    |       ----------- in this macro invocation
    |
note: while trying to match meta-variable `$items:expr`
   --> /Users/lily/.cargo/registry/src/github.com-1ecc6299db9ec823/asn1-rs-0.5.2/src/asn1_types/oid.rs:462:6
    |
462 |     ($items:expr) => {
    |      ^^^^^^^^^^^
    = note: this error originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)

Large Tags are not encoded properly

Hi, I have an issue regarding the encoding of large tags, particularly those that exceed the value of 0x80, where after serialization and deserialization, the tags don't match to the original.

The following code can be used to show this:

extern crate asn1_rs;
use asn1_rs::{Any, Error, FromDer, Implicit, Integer, Tag, ToDer};

fn main() {
   let tmp = Any::from_tag_and_data(Tag::from(0x41424344), &Integer::from(1).to_der_vec().unwrap())
        .to_der_vec()
        .unwrap();

    let expect = Tag::from(0x41424344);
    let actual = Any::from_der(&tmp).unwrap().1.tag();
    
    assert_eq!(expect, actual, "expected tag {expect}, found tag {actual}");
}

The above code results in the following output upon execution:

thread 'main' panicked at src/main.rs:12:5:
assertion `left == right` failed: expected tag Tag(1094861636 / 0x41424344), found tag Tag(1086473476 / 0x40c24504)
  left: Tag(1094861636)
 right: Tag(1086473476)
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

As I understand it, the tags are to remain the same during both the serialization and deserialization steps. If not, is there a correct way to store the tags so that they can be encoded onto the proper value?

Thanks in advance!

ToBer is missing?

The main readme hints at the existence of a ToBer trait:

BER/DER encoding is symmetrical to decoding, using the traits ToBer and [ToDer] traits. 

However I can't find anything about this in the code. Am I just missing something obvious?

ber is parsed using der

When passed in a ber with an Infinite length content, it produces a der error on Infinite length (Error(DerConstraintFailed(IndefiniteLength))). This shouldn't have happened because ber has no constrains on length.

The problem is expected to be located here. I fixed to Header::from_ber and the problem is resolved.

let (i2, header2) = Header::from_der(i)?;

I believe this is a typo and should be fixed.

P/S: The ber I tried to parse is from an Apple signature chain in any signed binary, and I assume it to be correct (checked using https://lapo.it/asn1js/). Using the rusticata/der-parser parse_ber function.

Consume embedded SEQUENCE in a clean way

I have a BER schema which looks like the following :

SEQUENCE {
    typeA SEQUENCE {...}
    typeB SEQUENCE {...}
}

I try to retrieve fields that are located in typeA SEQUENCE, how can I inspect it in a way to deal with errors, i.e. is it possible to do something like the following :

let (rem, result) = Sequence::from_ber_and_then(input, |i| {
     let (rem2, a) = Sequence::from_ber_and_then(input, |j| {
          let (rem2, a) = u32::from_der(input)?;
          ....
         Ok(...)
})?;

Is it the right to do that ? I'm not able to find the correct code to write in order to run that.

Thanks in advance

Add a pretty-printer for `Any`

Add a pretty-printer (like PrettyBer in der-parser) for Any.

This is already implemented as an example, dump-der.rs.

If a pretty-printer is embedded, it will either be simpler than the example (will not decode OID etc.), or should be moved to another crate to avoid cyclic dependencies

RUSTSEC-2020-0159: Potential segfault in `localtime_r` invocations

Potential segfault in localtime_r invocations

Details
Package chrono
Version 0.4.19
URL chronotope/chrono#499
Date 2020-11-10

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

Workarounds

No workarounds are known.

References

See advisory page for additional details.

`BerSequence` and `DerSequence` derive macros produce code that can panic

The code generated by both BerSequence and DerSequence for the TryFrom<Any> trait uses the finish function for error convertion. The issue with finish is that it can panic when the parser's result is Err(Err::Incomplete(_)). This is problematic when parsing ASN.1 structures that are corrupted.

I've found this issue while trying to use the x509-parser crate for parsing X509 certificates in Windows PE files. The files are sometimes corrupt, and make the program panic.

Consider the AlgorithmIdentifier structure:

#[derive(Clone, Debug, PartialEq, DerSequence)]
#[error(X509Error)]
pub struct AlgorithmIdentifier<'a> {
    #[map_err(|_| X509Error::InvalidAlgorithmIdentifier)]
    pub algorithm: Oid<'a>,
    #[optional]
    pub parameters: Option<Any<'a>>,
}

The code generated by DerSequence for the try_from function looks like:

fn try_from(any: Any<'ber>) -> asn1_rs::Result<Self, X509Error> {
    use asn1_rs::nom::*;
    any.tag().assert_eq(Self::TAG)?;
    let i = any.data;
    let (i, algorithm) = FromBer::from_ber(i).finish().map_err(|_| X509Error::InvalidAlgorithmIdentifier)?;
    let (i, parameters) = FromBer::from_ber(i).finish()?;
    let _ = i;
    Ok(Self {
        algorithm,
        parameters,
    })
}

Notice the use of finish after parsing both the algorithm and parameters fields. The documentation for finish says:

warning: if the result is Err(Err::Incomplete(_)), this method will panic.

“complete” parsers: It will not be an issue, Incomplete is never used
“streaming” parsers: Incomplete will be returned if there’s not enough data for the parser to decide, and you should gather more data before parsing again. Once the parser returns either Ok(), Err(Err::Error()) or Err(Err::Failure(_)), you can get out of the parsing loop and call finish() on the parser’s result

So, it looks like "streaming" parsing is being used, but I'm not sure if that's the case, or why.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.