librasn / rasn Goto Github PK
View Code? Open in Web Editor NEWA Safe #[no_std] ASN.1 Codec Framework
License: Other
A Safe #[no_std] ASN.1 Codec Framework
License: Other
Terrific library, and I'm trying to use it to implement a rust SNMP library. However, I've run into an issue that I believe isn't handled correctly. I'm no expert with BER encodings, so may be mistaken.
Per X.690 (2008):
8.14.4 If implicit tagging was used in the definition of the type, then:
a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise;
and
b) the contents octets shall be the same as the contents octets of the base encoding.
To me this would imply that if I implicitly tagged a sequence, the 'constructed' bit of the tag should be set (carried over from the base encoding). And indeed if I try this in a test case I get the following:
#[test]
fn implicit_tagged_constructed() {
use crate::{tag::Class, types::Implicit, AsnType, Tag};
struct C0;
impl AsnType for C0 {
const TAG: Tag = Tag::new(Class::Context, 0);
}
let value = <Implicit<C0, _>>::new(vec![1, 2]);
let data = &[0xA0, 6, 2, 1, 1, 2, 1, 2][..];
assert_eq!(data, &*crate::ber::encode(&value).unwrap());
// thread 'ber::tests::implicit_tagged_constructed' panicked at 'assertion failed: `(left == right)`
// left: `[160, 6, 2, 1, 1, 2, 1, 2]`,
//. right: `[128, 6, 2, 1, 1, 2, 1, 2]`'
}
Happy to submit a PR if you agree this is non-conforming to the spec.
Instead of directly reexporting types such as BigInt as Integer
and Bytes as OctetString
, I think it makes a lot more sense to newtype these and provide a more stable interface so that the implementations can be changed without affecting the public API. For instance, being able to switch from using num-bigint
to ibig
for parsing variable-sized integers could provide a significant speed increase, avoiding unnecessary allocations for small integers. However, changing the Integer
alias to IBig
would be a breaking change. Reexporting types also makes the docs quite a bit more confusing in my opinion, as the documentation for the types ends up using a completely, totally different type name, for example when I go to view the docs for OctetString
, I'm met with the following docs:
A cheaply cloneable and sliceable chunk of contiguous memory.
Bytes
is an efficient container for storing and operating on contiguous slices of memory. It is intended for use primarily in networking code, but could have applications elsewhere as well.
Bytes
values facilitate zero-copy network programming by allowing multipleBytes
objects to point to the same underlying memory.
Which is a bit jarring when the type signature is named something completely different IMO
In the Kerberos Spec (RFC 4120) - there are a few instances of OPTIONAL fields that cannot be empty, eg. padata
:
KDC-REQ ::= SEQUENCE {
-- NOTE: first tag is [1], not [0]
pvno [1] INTEGER (5) ,
msg-type [2] INTEGER (10 -- AS -- | 12 -- TGS --),
padata [3] SEQUENCE OF PA-DATA OPTIONAL
-- NOTE: not empty --,
req-body [4] KDC-REQ-BODY
}
In the rasn implementation, it looks like OPTIONAL
fields with a None
value are added with zero-length instead of being omitted.
Eg, if we look at the encoding of:
KDC-REQ ::= SEQUENCE {
pvno [1] INTEGER = 5,
msg-type [2] INTEGER = 10,
padata [3] SEQUENCE OF PA-DATA = None,
req-body [4] KDC-REQ-BODY = ...
}
We get 30, 81, DF, A1, 03, 02, 01, 05, A2, 03, 02, 01, 0A, A3, 00, A4 ...
(bear in mind that I've changed the context-specific tags in rasn-kerberos to explicit as per conversation in #69). Breaking that down for clarity:
30, 81, DF = KDC-REQ tag & length,
A1, 03, 02, 01, 05 = pvno,
A2, 03, 02, 01, 0A = msg-type,
A3, 00 = padata (SHOULD BE OMITTED),
A4 ... = req-body
Additionally, it seems like the Windows kerberos implementation doesn't like empty OPTIONAL
fields even if they're not marked as such in the RFC eg. some of the time fields.
from [4] KerberosTime OPTIONAL,
till [5] KerberosTime,
rtime [6] KerberosTime OPTIONAL,
Is there functionality within rasn to support this scenario? Let me know if there are any other details that would be helpful here.
The compiler should be able to be used as a standalone executable to compile ASN.1 to a variety of languages (currently just Rust). Ideally we'd have a much more fully featured CLI for generating code. This issue requires some research and finding prior art to see if there's anything rasn should adopt.
When you have a Implicit string like below, it's not possible to access the value by itself because value is private.
Implicit { _tag: PhantomData, value: "TestString" }
error[E0616]: field `value` of struct `Implicit` is private
| println!("{:?}", obj.value);
| ^^^^^ private field
use rasn::{types::*, *};
#[test]
fn choice() {
#[derive(AsnType, Clone, Debug, Encode, Decode, PartialEq)]
struct ChoiceField {
choice: VecChoice,
}
#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq)]
#[rasn(choice)]
enum VecChoice {
#[rasn(tag(1))]
Bar(Vec<bool>),
#[rasn(tag(2))]
Foo(Vec<OctetString>),
}
let bar = ChoiceField { choice: VecChoice::Bar(vec![true]) };
let foo = ChoiceField { choice: VecChoice::Foo(vec![OctetString::from(vec![1, 2, 3, 4, 5])]) };
assert_eq!(foo, ber::decode(&ber::encode(&foo).unwrap()).unwrap());
assert_eq!(bar, ber::decode(&ber::encode(&bar).unwrap()).unwrap());
}
errors with thread 'choice' panicked at 'called Result::unwrap()
on an Err
value: Custom { msg: "CHOICE-style enums do not allow implicit tagging." }', tests/derive.rs:67:52
if impl Decode manually for ChoiceField:
use rasn::{types::*, *};
#[derive(AsnType, Clone, Debug, Encode, PartialEq)]
struct ChoiceField {
choice: VecChoice,
}
impl Decode for ChoiceField {
fn decode_with_tag<D: rasn::Decoder>(decoder: &mut D, tag: Tag) -> Result<Self, D::Error> {
let mut decoder = decoder.decode_sequence(tag)?;
Ok(Self {
choice: <_>::decode(&mut decoder)?,
})
}
}
#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq)]
#[rasn(choice)]
enum VecChoice {
#[rasn(tag(1))]
Bar(Vec<bool>),
#[rasn(tag(2))]
Foo(Vec<OctetString>),
}
let bar = ChoiceField { choice: VecChoice::Bar(vec![true]) };
let foo = ChoiceField { choice: VecChoice::Foo(vec![OctetString::from(vec![1, 2, 3, 4, 5])]) };
assert_eq!(foo, ber::decode(&ber::encode(&foo).unwrap()).unwrap());
assert_eq!(bar, ber::decode(&ber::encode(&bar).unwrap()).unwrap());
then it errors with thread 'choice' panicked at 'called Result::unwrap()
on an Err
value: Custom { msg: "CHOICE-style enums do not allow implicit tagging." }', tests/derive.rs:66:52
So what's the proper way to deal with it? Thanks!
Section 3 of the Version 2 of the Protocol Operations for the Simple Network Management Protocol (SNMP) RFC specifies IpAddress
as the following:
IpAddress ::= [APPLICATION 0] IMPLICIT OCTET STRING (SIZE (4))
which only allows 4-byte IPv4 addresses, whereas rasn_snmp::v2::IpAddress
allows for arbitrarily sized, malformed IPs:
#[test]
fn bad_ip_address() {
let ip = IpAddress {
0: OctetString::from_static(&[255, 255, 255]),
};
assert_eq!(
rasn::ber::decode::<IpAddress>(&rasn::ber::encode(&ip).unwrap()).unwrap(),
ip
);
}
Also unrelated, but I think the type alias for rasn_smi::v2::Integer32
is incorrect as well, its currently:
pub type Integer32 = u32;
but I think it should be
pub type Integer32 = i32;
since the unsigned 32-bit integer type is already covered by Gauge
and by type alias Unsigned32
BACnet uses ASN.1 to encode it's datastructures. It also defines an encoding that is similar but not the same as the standard BER encoding.
The following are the main difference I identified between the BACnet Encoding and BER:
|------|------|------|--------|------|------|------|------|
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|------|------|------|--------|------|------|------|------|
| Tag Number |Class |Length/Value/Type |
|------|------|------|--------|------|------|------|------|
0 = Null
1 = Boolean
2 = Unsigned Integer
3 = Signed Integer (2's complement notation)
4 = Real (ANSI/IEEE-754 floating point)
5 = Double (ANSI/IEEE-754 double precision floating point)
6 = Octet String
7 = Character String
8 = Bit String
9 = Enumerated
10 = Date
11 = Time
12 = BACnetObjectIdentifier
These are used instead of the universal ones.
I see the following possible ways of implemementing this:
Currently I'm playing around with the code to see if option 1 or 2 is a better fit.
@XAMPPRocky I would like to get your opinion which option you think is preferable? Specifically I would like to know if you see the BACnet encoding as part of this repo, or if I should go with a separate one.
error[E0658]: arbitrary expressions in key-value attributes are unstable
#![doc = include_str!("../README.md")]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Believe this is due to the changes in 4e69c11
Hello,
I'm trying to implement very ASN.1 schema from RFC7030:
CsrAttrs ::= SEQUENCE SIZE (0..MAX) OF AttrOrOID
AttrOrOID ::= CHOICE (oid OBJECT IDENTIFIER, attribute Attribute }
Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE {
type ATTRIBUTE.&id({IOSet}),
values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) }
I created rust code which tries to encode/decode it:
#[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
struct CsrAttrs {
attr_or_oid: Vec<AttrOrOid>,
}
#[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
#[rasn(choice)]
enum AttrOrOid {
OID(rasn::types::ObjectIdentifier),
ATTRIBUTE(Attribute),
}
#[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
struct Attribute {
r#type: rasn::types::ObjectIdentifier,
values: rasn::types::SetOf<rasn::types::Open>,
}
#[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
struct AttributeValue(Vec<u8>);
fn main() {}
However, I got compilation error, as it looks like the SetOf
is not fully implemented:
error[E0277]: the trait bound `BTreeSet<Open>: Decode` is not satisfied
--> src/main.rs:23:25
|
23 | #[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
| ^^^^^^^^^^^^ the trait `Decode` is not implemented for `BTreeSet<Open>`
|
= note: required by `decode_with_tag`
= note: this error originates in the derive macro `rasn::Decode` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0599]: no method named `encode` found for struct `BTreeSet` in the current scope
--> src/main.rs:23:39
|
23 | #[derive(rasn::AsnType, rasn::Decode, rasn::Encode, Debug, PartialEq, Clone)]
| ^^^^^^^^^^^^ method not found in `BTreeSet<Open>`
|
= note: this error originates in the derive macro `rasn::Encode` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 2 previous errors
I don't think it's currently possible to encode Bitstrings ending in 0x00
. I think that is due to this code https://github.com/XAMPPRocky/rasn/blob/1ff67d472faa3f0448d1b6ca31cd8c1b149ff072/src/ber/enc.rs#L259
microsoft msix and appx packages contain a CodeIntegrity.cat
and AppxSignature.p7x
file, which while undocumented seems to be cms encoded SignedData. however it seems like it's not quite spec compliant and maybe has an extra field or something.
thread 'test_msix_p7x' panicked at 'called `Result::unwrap()` on an `Err` value: FieldError { name: "SignedData.encap_content_info", error: "Field `EncapsulatedContentInfo.content`: Unexpected extra data found: length `264` bytes" }', standards/cms/tests/test_cms.rs:44:62
thread 'test_msix_cat' panicked at 'called `Result::unwrap()` on an `Err` value: FieldError { name: "SignedData.encap_content_info", error: "Field `EncapsulatedContentInfo.content`: Unexpected extra data found: length `1303` bytes" }', standards/cms/tests/test_cms.rs:58:62
replacing content with Any works.
Hello!
In my project, I faced with some suspicious behaviour SetOf decoding.
I have correct cms structure, and I'm trying to get SignerInfo.signed_attrs as bytes.
CMS Structure decoded correctly
My code for decode:
#[derive(AsnType, Decode, Encode)]
#[derive(Debug)]
struct StbStruct {
oid: ObjectIdentifier,
#[rasn(tag(0))]
data: SequenceOf<SignedData>,
}
fn verify_cms(structure: &SignedData) -> bool {
let data = structure.encap_content_info.content.as_ref().to_vec();
let certset = structure.certificates.as_ref().unwrap();
let mut key: Vec<u8> = vec![];
for cert in certset {
if let CertificateChoices::Certificate(c) = cert {
key = c.tbs_certificate.subject_public_key_info.subject_public_key.clone().into_vec();
}
}
let signersset = &structure.signer_infos;
for signer in signersset {
let mut signed_attrs = signer.signed_attrs.clone().unwrap();
let mut signed_attrs_der = rasn::der::encode(&signed_attrs).unwrap();
println!("{:02X?}", signed_attrs_der);
}
// correct setof from lapoit
let mut signed_attrs_der = vec![
0x31, 0x69, 0x30, 0x18, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x03, 0x31, 0x0B, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01,
0x30, 0x1C, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x05, 0x31, 0x0F, 0x17, 0x0D, 0x32, 0x32, 0x30, 0x33, 0x31, 0x31, 0x31, 0x31, 0x32, 0x38, 0x30, 0x34, 0x5A,
0x30, 0x2F, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x04, 0x31, 0x22, 0x04, 0x20, 0x8D, 0xA7, 0x88, 0xFB, 0xD0, 0xC5, 0x7D, 0x46, 0x9B, 0x39, 0x20, 0x7B, 0x68, 0x95, 0xF0, 0x05, 0x77, 0xCB, 0x78, 0x77, 0xA1, 0x43, 0x72, 0xC2, 0x8C, 0xDF, 0x6D, 0x9B, 0x9C, 0x49, 0xE1, 0x25
];
println!("{:02X?}", signed_attrs_der);
true
}
fn main() {
let cms_bytes = base64::decode("MIIFlQYJK...").unwrap();
let d: StbStruct = rasn::der::decode(&cms_bytes).unwrap();
let signeddata = d.data.first().unwrap();
println!("{:?}", verify_cms(&signeddata));
}
And from my decode i am getting sorted set:
[31, 69, 30, 18, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 03, 31, 0B, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 07, 01, 30, 2F, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 04, 31, 22, 04, 20, 8D, A7, 88, FB, D0, C5, 7D, 46, 9B, 39, 20, 7B, 68, 95, F0, 05, 77, CB, 78, 77, A1, 43, 72, C2, 8C, DF, 6D, 9B, 9C, 49, E1, 25, 30, 1C, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 05, 31, 0F, 17, 0D, 32, 32, 30, 33, 31, 31, 31, 31, 32, 38, 30, 34, 5A]
So, difference is:
My:
[31, 69, 30, 18, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 03, 31, 0B, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 07, 01, 30, 2F, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 04, 31, 22, 04, 20, 8D, A7, 88, FB, D0, C5, 7D, 46, 9B, 39, 20, 7B, 68, 95, F0, 05, 77, CB, 78, 77, A1, 43, 72, C2, 8C, DF, 6D, 9B, 9C, 49, E1, 25, 30, 1C, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 05, 31, 0F, 17, 0D, 32, 32, 30, 33, 31, 31, 31, 31, 32, 38, 30, 34, 5A]
"MWkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAvBgkqhkiG9w0BCQQxIgQgjaeI+9DFfUabOSB7aJXwBXfLeHehQ3LCjN9tm5xJ4SUwHAYJKoZIhvcNAQkFMQ8XDTIyMDMxMTExMjgwNFo="
Correct:
[31, 69, 30, 18, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 03, 31, 0B, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 07, 01, 30, 1C, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 05, 31, 0F, 17, 0D, 32, 32, 30, 33, 31, 31, 31, 31, 32, 38, 30, 34, 5A, 30, 2F, 06, 09, 2A, 86, 48, 86, F7, 0D, 01, 09, 04, 31, 22, 04, 20, 8D, A7, 88, FB, D0, C5, 7D, 46, 9B, 39, 20, 7B, 68, 95, F0, 05, 77, CB, 78, 77, A1, 43, 72, C2, 8C, DF, 6D, 9B, 9C, 49, E1, 25]
"MWkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjIwMzExMTEyODA0WjAvBgkqhkiG9w0BCQQxIgQgjaeI+9DFfUabOSB7aJXwBXfLeHehQ3LCjN9tm5xJ4SU="
So there is a question, is there any way to get der structure without sorted values?
Correct der:
With the following example struct
#[derive(AsnType, Decode, Encode, Debug, PartialEq, Clone)]
pub struct PaddingError {
#[rasn(tag(0))]
pub normal: rasn::types::Integer,
#[rasn(tag(1))]
pub bitstring_1: rasn::types::BitString,
#[rasn(tag(2))]
pub bitstring_2: rasn::types::BitString,
}
let expected = hex::decode("3016800101810305e100820c03ee0800000400000001ed18").unwrap();
let actual: Vec<u8> = rasn::der::encode(&PaddingError{
normal: 1.into(),
bitstring_1: BitString::from_vec([0xe1, 0x00].to_vec()),
bitstring_2: BitString::from_vec([0xee,0x08,0x00,0x00,0x04,0x00,0x00,0x00,0x01,0xed,0x18].to_vec()),
}).unwrap();
assert_eq!(actual, expected);
We would expect the resulting bytes to look like
[48, 22, 128, 1, 1, 129, 3, 5, 225, 0, 130, 12, 3, 238, 8, 0, 0, 4, 0, 0, 0, 1, 237, 24]
Instead what we get from our encode is
[48, 21, 128, 1, 1, 129, 2, 0, 225, 130, 12, 0, 238, 8, 0, 0, 4, 0, 0, 0, 1, 237, 24]
Unit test that shows error
Error : Custom { msg: "Encoding field of type `OptionalChoice`: CHOICE-style enums do not allow implicit tagging. If using the derive macro on a sequence with a choice field ensure it is marked with `#[rasn(choice)]`."
use rasn::{types::*, Decode, Encode};
#[derive(AsnType, Decode, Encode, Debug, PartialEq, Clone)]
pub struct OptionTest {
#[rasn(tag(0))]
pub regular: rasn::types::Integer,
#[rasn(tag(1))]
pub optional: Option<OptionalChoice>,
}
#[derive(AsnType, Decode, Encode, Debug, PartialEq, Clone)]
#[rasn(choice)]
pub enum OptionalChoice {
#[rasn(tag(0))]
Test(rasn::types::Integer),
}
#[test]
fn it_works() {
let data = OptionTest {
regular: 0.into(),
optional: Some(OptionalChoice::Test(0.into())),
};
let bin = rasn::ber::encode(&data).unwrap();
assert_eq!(data, rasn::ber::decode(&bin).unwrap());
}
Similar to #101 ideally we’d have a public codegen API that allows the compiler to generate languages other than Rust, and we’d have an API for generating ASN.1 notation back from Rust code so that compiler can be integrated with the derive macros to enable generating ASN.1 modules from derived code.
Most of the top-level rasn-ldap structs are declared with non-exhaustive attribute which prevents them from being constructed outside of the rasn-ldap crate.
How is the LdapMessage
supposed to be constructed for encoding?
Hey, amazing crate!
Any update on when this crate will support constraints?
I'm implementing the IEEE 11073 standard for medical device communication in Rust. It encodes its data types in ASN.1 BER and DER. My implementation targets embedded environments so it would really save me memory and storage if I can constraint data types to u8/16/32 as per the IEEE 11073 recommendations.
I have some data which is supposed to be a ContentInfo (received from AWS KMS), but the following code fails:
let data: Vec<u8> = vec![ 48, 128, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 3, 160, 128, 48, 128, 2, 1, 2, 49, 130, 1, 107, 48, 130, 1, 103, 2, 1, 2, 128, 32, 212, 17, 115, 195, 21, 153, 39, 196, 140, 239, 85, 90, 110, 51, 220, 56, 29, 10, 60, 34, 101, 42, 71, 97, 142, 255, 112, 248, 99, 5, 201, 148, 48, 60, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 7, 48, 47, 160, 15, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 5, 0, 161, 28, 48, 26, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 8, 48, 13, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 5, 0, 4, 130, 1, 0, 17, 238, 11, 93, 44, 255, 28, 111, 215, 103, 19, 197, 237, 193, 145, 116, 70, 103, 208, 137, 8, 71, 74, 138, 141, 169, 169, 238, 40, 84, 122, 21, 216, 49, 63, 90, 105, 66, 69, 178, 78, 28, 66, 236, 23, 81, 214, 78, 0, 210, 182, 109, 105, 36, 16, 214, 250, 246, 112, 244, 33, 164, 75, 118, 120, 18, 29, 71, 174, 23, 38, 95, 37, 244, 10, 75, 229, 177, 41, 169, 74, 68, 183, 204, 187, 113, 244, 225, 65, 222, 187, 86, 224, 102, 147, 85, 248, 213, 228, 192, 93, 127, 5, 184, 123, 196, 165, 110, 16, 248, 25, 242, 194, 38, 156, 219, 100, 43, 167, 72, 184, 66, 34, 225, 225, 216, 104, 156, 247, 26, 150, 12, 151, 202, 76, 173, 179, 197, 118, 217, 109, 245, 52, 171, 99, 177, 28, 138, 137, 206, 246, 175, 166, 254, 119, 86, 66, 141, 227, 120, 66, 76, 120, 93, 186, 204, 38, 91, 48, 34, 107, 23, 191, 92, 251, 246, 248, 112, 227, 238, 122, 165, 22, 18, 94, 232, 208, 209, 129, 1, 6, 28, 235, 238, 198, 204, 221, 234, 17, 111, 190, 163, 148, 215, 140, 71, 132, 48, 37, 169, 178, 25, 196, 20, 9, 240, 39, 137, 22, 40, 71, 155, 200, 59, 244, 156, 220, 212, 154, 94, 211, 132, 198, 195, 245, 49, 89, 152, 238, 178, 200, 226, 193, 145, 28, 175, 31, 179, 137, 30, 168, 96, 210, 208, 54, 181, 48, 128, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 29, 6, 9, 96, 134, 72, 1, 101, 3, 4, 1, 42, 4, 16, 171, 84, 103, 126, 243, 106, 225, 202, 106, 15, 104, 146, 100, 201, 219, 97, 160, 128, 4, 48, 75, 109, 64, 64, 47, 137, 225, 83, 92, 88, 253, 78, 185, 232, 53, 253, 41, 237, 32, 149, 221, 227, 20, 25, 156, 80, 137, 130, 53, 140, 158, 244, 110, 86, 121, 214, 90, 195, 21, 63, 0, 86, 150, 198, 189, 2, 120, 239, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
let _content_info: rasn_cms::ContentInfo = rasn::ber::decode(&*data).unwrap();
with the error:
panicked at 'called `Result::unwrap()` on an `Err` value: FieldError { name: "ContentInfo.content", error: "Error in Parser: Parsing Failure: Parsing Error: Error { input: [2, 1, 2, 49, 130, 1, 107, ... , 0, 0], code: Tag }" }'
However, if I define a new struct:
#[derive(rasn::AsnType, Clone, Debug, rasn::Decode, rasn::Encode, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MyContentInfo {
pub content_type: rasn_cms::ContentType,
#[rasn(tag(explicit(0)))]
pub content: rasn_cms::EnvelopedData,
}
then I can decode to that fine. The only change is changing the type of content
from Any
to EnvelopedData
.
Currently the syntax in the PKIX crate is based on the 1998 syntax, because that was easier to implement, however it would be preferable if we could move to a similar abstraction of modules as in RFC 5912 while maintaining the same level of flexibility. Ideally more of constraints can also be implemented as part of the refactor.
OID for SHA1 looks wrong:
ISO_MEMBER_BODY_US_RSADSI_PKCS1_MD5_RSA => 1, 2, 840, 113549, 1, 1, 4;
ISO_MEMBER_BODY_US_RSADSI_PKCS1_SHA1_RSA => 1, 2, 840, 113549, 1, 1, 4;
SHA1 with RSA should be 1.2.840.113549.1.1.5.
I've updated the rasn dependency to 0.6 and found out that the standards are still at 0.5 making them incompatible with the parent crate.
I mean if I mix rasn 0.6 and rasn-ldap 0.5 in the Cargo.toml the project doesn't compile.
This is a non-trivial amount of work and I would not at all mind if it was declared out-of-scope, but it would be nice for rasn to be a full-fledged ASN.1 compiler. Some protocol definitions (cough 3GPP) are huge and writing models by hand would be very error-prone.
As discussed in #94, the V2 module should be updated to better match https://datatracker.ietf.org/doc/html/rfc3416#section-3
Hi.
I am trying to follow the RFC5280 section for CRL distribution point certificate extension
GeneralName ::= CHOICE {
otherName [0] AnotherName,
rfc822Name [1] IA5String,
dNSName [2] IA5String,
x400Address [3] ORAddress,
directoryName [4] Name,
ediPartyName [5] EDIPartyName,
uniformResourceIdentifier [6] IA5String,
iPAddress [7] OCTET STRING,
registeredID [8] OBJECT IDENTIFIER }
CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
DistributionPoint ::= SEQUENCE {
distributionPoint [0] DistributionPointName OPTIONAL,
reasons [1] ReasonFlags OPTIONAL,
cRLIssuer [2] GeneralNames OPTIONAL }
DistributionPointName ::= CHOICE {
fullName [0] GeneralNames,
nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
I've only implemented a basic part of it and trying to parse the extension field from a well-known CA certificate
Here's my code: https://gist.github.com/Albibek/bdfaa3464ac7b8f009865b3426164a68
I'm using the main branch of the library since Option does not work in 0.3 at all giving a macro error.
I see two problems:
It is still probably me using the library wrong but can also be it's some problems with the library.
Thanks in advance.
Hi, a friend just linked your 0.3.0 announcement and I'm super excited to see SNMP stuff get a crate! :D However, reading through the docs and then the code, I'm assuming some fields were forgotten to be made pub
? e.g. https://github.com/XAMPPRocky/rasn/blob/fb424b886f15d2d3943fffbb72ae0d2fccdf4e7b/standards/snmp/src/v1.rs#L52-L58
There's no way to construct or inspect any Pdu
since all fields are private and there's no constructor or accessor methods. Seems like the other structs have similar issues. Haven't looked at the other new crates yet so not sure if its the case there either.
Hi, a friend recently made me aware of the const-oid
crate which implements const fn new
on the type to allow compile-time parsing and construction of the OID from the textual representation, which I think would be quite useful to have re-exported & the Decode
and Encode
traits implemented for. As far as I can see, the only limitation is that it has a fixed-size internal representation which limits the lengths of the OID values (in bytes), but I'm not sure how much of an issue this would be in practice (it seems most, if not all, of the OIDs that I work with at my job are well below the current limit of 23 bytes for the OID, at least). I would imagine this would likely replace the current ConstOid
type in rasn
, or at least be available as an opt-in feature. Thoughts?
Eventually I'd like to move the compiler to using chumsky/ariadne crates for parsing, in order to provide better error messages when parsing fails.
nom-supreme
, nom based, so we'd use the same dependency in the codec as the parser.As a way to know when the compiler is ready for release, we should replace some of the handwritten implementations in this crate with compiler generated implementations. This is also a good way of finding what needs to be done next, as if it can't compile these standards then there's something that needs to be fixed.
Hi, I'm working on building a SNMP client and as an optimization to prevent allocating memory on every send/receive, I want to internally store a Vec
in my client, however there doesn't appear to be a way to provide the Encoder
a &mut Vec<u8>
or &mut [u8]
to encode into, which would be really useful!
E.g. at /src/ber/de.rs:60:70
I get the error:
--> /home/sivizius/.cargo/registry/src/github.com-1ecc6299db9ec823/rasn-0.2.0/src/ber/de.rs:60:70
|
60 | self::parser::parse_identifier_octet(self.input).map_err(error::map_nom_err)?;
| ^^^^^^^^^^^^^^^^^^ expected signature of `fn(nom::Err<nom::error::Error<&[u8]>>) -> _`
|
::: /home/sivizius/.cargo/registry/src/github.com-1ecc6299db9ec823/rasn-0.2.0/src/ber/de/error.rs:20:1
|
20 | pub(crate) fn map_nom_err(error: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Error {
| ----------------------------------------------------------------------------------- found signature of `for<'r> fn(nom::Err<(&'r [u8], nom::error::ErrorKind)>) -> _`
similar errors at other places, where .map_err(error::map_nom_err)?
is used.
Hello! I'm just starting to use this project and it's everything I want in a DER encoder/decoder (especially the derive macros, which are incredibly helpful). One thing I'm wondering if is there's a built-in way to have something like this:
#[derive(Debug, AsnType, Encode, Decode)]
struct SignatureWrapper(Bytes);
#[derive(Debug, AsnType, Encode, Decode)]
#[rasn(choice)]
enum Signature {
#[rasn(tag(0))]
Ed25519(SignatureWrapper),
}
And encode it as if SignatureWrapper
was just a Bytes
. Right now, it seems to wrap it. Is there a #[rasn(...)]
attribute that can do this, or would this be something I'd need to open a PR for? Thank you!
use rasn::{AsnType,Decode,Encode};
use std::error::Error;
#[derive(Debug, AsnType, Decode, Encode)]
struct Message {
id: i32,
#[rasn(choice, enumerated)]
body: BODY,
}
#[derive(Debug, AsnType, Decode, Encode)]
#[rasn(choice)]
enum BODY {
#[rasn(tag(context, 3000))]
Request(Request),
#[rasn(tag(context, 3001))]
Response(Response),
}
#[derive(Debug, AsnType, Decode, Encode)]
struct Request {
num: i32,
}
#[derive(Debug, AsnType, Decode, Encode)]
struct Response {
ret: i32,
}
fn main() -> Result<(), Box<dyn Error>> {
let p = Message {
id: 1,
body: BODY::Request(Request { num: 1 }),
};
let e = rasn::der::encode(&p).unwrap();
println!("e: {:02x?}", e);
Ok(())
}
the above piece of code prints: 30, 0a, 02, 01, 01, bf, 97, 38, 03, 02, 01, 01
i want it to print 30, 0c, 02, 01, 01, bf, 97, 38, 05, 30, 03, 02, 01, 01
because it need to talk to some program written using c++ and golang. This encoding requires each CHOICE of BODY explicitly tagged. See 5.2 Explicitly tagged types of http://luca.ntop.org/Teaching/Appunti/asn1.html
Is it possible to achieve this?
I'd like to be able to provide easy integration between rasn and python code, so I'd like to add a feature to the macros to have them also generate PyO3 bindings, and provide these bindings for the crates in the repository. My hope is that all that should be required is adding #[pyclass]
, and adding a decode
and encode
#[pymethod]
functions that accept a enum (enum Codec { Ber, Der, Cer }
, I know python doesn't have enums like rust but you know what I mean) and call the appropriate encode
/decode
Rust functions depending on the Codec
.
I don't have time to work on this in the near future, but I would be able to help and review a PR to add it, so if anyone is interested in adding this themselves feel free to post in the issue.
The commit 3d94b96 is causing some issues with choices, on that commit most of my unit tests are failing with Invalid `CHOICE` discriminant."
error. Does that commit require some changes to the way choices are used?
it's been a while and there are some juicy bugfixes. would be nice to get a new release
Hello,
I believe that long form tag decoding might be currently broken due to endianness being little instead of big (as per https://en.wikipedia.org/wiki/X.690#Long_form).
Here's a possible fix that worked for me:
Fix long form tag decoding
* change endianness to big,
* change the test to make the first octet nonzero
diff --git a/src/ber/de/parser.rs b/src/ber/de/parser.rs
index d66da33..16dc8a9 100644
--- a/src/ber/de/parser.rs
+++ b/src/ber/de/parser.rs
@@ -188,14 +188,19 @@ impl Appendable for crate::types::BitString {
/// Concatenates a series of 7 bit numbers delimited by `1`'s and
/// ended by a `0` in the 8th bit.
fn concat_number(body: &[u8], start: u8) -> Integer {
- let mut number = Integer::from(start);
+ let start = Integer::from(start);
+ if body.is_empty() {
+ return start;
+ }
+
+ let mut number = Integer::from(body[0] & 0x7F);
- for byte in body.iter().rev() {
+ for byte in body[1..].iter() {
number <<= 7usize;
number |= Integer::from(byte & 0x7F);
}
- number
+ (number << 7usize) | start
}
fn take_contents(input: &[u8], length: u8) -> IResult<&[u8], &[u8]> {
@@ -237,9 +242,9 @@ mod tests {
#[test]
fn long_tag() {
- let (_, identifier) = parse_identifier_octet([0xFF, 0x80, 0x7F][..].into()).unwrap();
+ let (_, identifier) = parse_identifier_octet([0xFF, 0x83, 0x7F][..].into()).unwrap();
assert!(identifier.is_constructed);
- assert_eq!(Tag::new(Class::Private, 16256), identifier.tag);
+ assert_eq!(Tag::new(Class::Private, 511), identifier.tag);
}
#[test]
Thanks for the great library.
I’m trying to use the rasn_kerberos structures but am having some issues. I’m by no means an expert here, so may be doing something wrong.
Specifically, if I’m building an AS-REQ - which is defined in RFC 4120 as follows:
AS-REQ ::= [APPLICATION 10] KDC-REQ
KDC-REQ ::= SEQUENCE {
-- NOTE: first tag is [1], not [0]
pvno [1] INTEGER (5) ,
msg-type [2] INTEGER (10 -- AS -- | 12 -- TGS --),
padata [3] SEQUENCE OF PA-DATA OPTIONAL
-- NOTE: not empty --,
req-body [4] KDC-REQ-BODY
}
The rasn generated der encoded value starts:
6A, 81, 9D, 81, 01, 05 …
With 6A representing the application tag of 10, and 81, 9D representing the length. However I would be expecting a sequence tag next (30).
Based on this - I believe that implicit tagging is being used here?
As per RFC 4120, explicit tagging should be used:
The type definitions in this section assume an ASN.1 module
definition of the following form:
KerberosV5Spec2 {
iso(1) identified-organization(3) dod(6) internet(1)
security(5) kerberosV5(2) modules(4) krb5spec2(2)
} DEFINITIONS EXPLICIT TAGS ::= BEGIN
-- rest of definitions here
END
This specifies that the tagging context for the module will be
explicit and non-automatic.
If I compare the rasn generated encoding with a functioning AS-REQ that I’ve captured (bare in mind different size), it includes the tag and length for the sequence (30, 81, DC) as well as for the Integer (02, 01)
6A, 81, DF, 30, 81, DC, A1, 03, 02, 01, 05 …
I note from #36 that explicit tagging is available via the macro now. This seems to have worked fine for the fields, but I can’t figure out how one would set the application tag as explicit.
#[rasn(tag(explicit(application, 11)), delegate)]
Panics with:
the `#[rasn(tag)]` attribute must be a list
I would appreciate your thoughts - apologies if I’m incorrect with my assessment here
Hey - it looks like the macro explicit tagging on structs is only working correctly for structs with delegate
set (as tested here). For other structs, the sequence
tag isn't being applied.
I'm specifically trying to use the kerberos library and the ticket struct, defined as:
/// Record that helps a client authenticate to a service.
#[derive(AsnType, Clone, Debug, Decode, Encode, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[rasn(tag(explicit(application, 1)))]
pub struct Ticket {
/// The version number for the ticket format.
#[rasn(tag(explicit(0)))]
pub tkt_vno: Integer,
/// The realm that issued a ticket. It also serves to identify the realm
/// part of the server's principal identifier. Since a Kerberos server can
/// only issue tickets for servers within its realm, the two will always
/// be identical.
#[rasn(tag(explicit(1)))]
pub realm: Realm,
/// All components of the name part of the server's identity, including
/// those parts that identify a specific instance of a service.
#[rasn(tag(explicit(2)))]
pub sname: PrincipalName,
/// The encrypted encoding of [EncTicketPart]. It is encrypted in the key
/// shared by Kerberos and the end server (the server's secret key), using a
/// key usage value of `2`.
#[rasn(tag(explicit(3)))]
pub enc_part: EncryptedData,
}
Code snippet:
let ticket = Ticket {
tkt_vno: Integer::parse_bytes(b"5", 10).unwrap(),
realm: KerberosString::new("COMPANY.INT".to_string()),
sname: PrincipalName {
r#type: 2,
string: vec![
KerberosString::new(String::from("krbtgt")),
KerberosString::new(String::from("COMPANY.INT")),
],
},
enc_part: EncryptedData {
etype: 28,
kvno: None,
cipher: OctetString::from("ab"),
},
};
let data: &[u8] = &[0x61, 0x43, 0x30, 0x41, 0xA0, 0x03, 0x02, 0x01, 0x05];
let enc = rasn::der::encode(&ticket).unwrap();
// this fails
assert_eq!(data, &enc[..data.len()]);
the encoded ticket is:
61, 41, A0, 03, 02, 01, 05...
it's gone straight from the explicit application tag 61, 41
to the first field's tag A0, 03 ...
, however I would expect the sequence
tag as per the above assert against data
.
Getting more familiar with the code and rust in general so will hopefully be able to make useful contributions soon
My fuzzer for this was
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(value) = rasn::der::decode::<rasn::types::Open>(data) {
assert_eq!(value, rasn::der::decode(&rasn::der::encode(&value).unwrap()).unwrap());
}
});
Not sure if this a valid round fuzz test, feel free to close as invalid if not.
Reproducer is
fn main() {
let data = [
24, 19, 43, 53, 49, 54, 49, 53, 32, 32, 48, 53, 50, 52, 48, 57, 52, 48, 50, 48, 90,
];
let value = rasn::der::decode::<rasn::types::Open>(&data).expect("passes");
rasn::der::decode::<rasn::types::Open>(&rasn::der::encode(&value).unwrap()).expect("fails");
}
Fails with thread 'main' panicked at 'fails: NoValidChoice { name: "Open" }', src/main.rs:7:81
One of the things that needs a good representation the the rasn framework is defining and using Objects/Classes from ASN.1 in rasn.
Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3f86d7648af09a1fb561b0c510fe0c9e
I didn't notice any MSRV policy, but if you're not concerned with supporting older Rust versions, ConstOid
can be removed and replaced with const
associated functions on Oid
now 🎉 Option::unwrap
still isn't const
however its trivial to create a function to do the same thing as shown in the playground link, so I don't think that's really a blocker since its on the user's end.
Hello,
This crate is amazing and speeds up my small ASN.1 encoding project enormously :-)
However, I found a bug when encoding OIDs, having values bigger than 127. E.g. when taking following example ID from MS website encoding using rasn
gives back:
[2b, 6, 1, 4, 1, b7, 2,15,14]
(this actually decodes to 1.3.6.1.4.1.7042.21.20
, look here)[2b, 6, 1, 4, 1, b7, 2, 15, 14]
Another example is from stackoverflow and 1.2.840.113549.1.1.5 (sha1WithRsaEncryption)
:
[2a, c8, 6, 8d, f7, 6, 1, 1, 5]
(lapo.it)[2a, 86, 48, 86, f7, d, 1, 1, 5]
(lapo.it)BR, Piotr
Currently the compiler does not support parameterisation which prevents it from being able to support many modern ASN.1 modules. These should be translated into type or const generics in Rust.
Hey, I am trying to implement ITU-T X.227 (ISO Association Control Service Element). Part of it requires me to write down the following OID:
joint-iso-itu-t(2) association-control(2) as-id(3) acse-ase(1) version(1)
So I decided to add it to your src/types/oid.rs
file in then import it into my own crate (that I'm putting in the standards
folder for a pull request later). That's when I noticed that a block of OIDs for X9-CM (or X9.57) is incorrectly written under root OID JOINT_ISO_ITU_T(2)
. I think it should be under ISO(1)
.
Here is the reference for my belief: Reference record for OID 1.2.840.10040.2.
Let me know if this is correct, I'll be happy to draw up a pull request.
hello, here is my asn file:
World-Schema DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
Request ::= SEQUENCE {
num INTEGER
}
Response ::= SEQUENCE {
ret INTEGER
}
BODY ::= CHOICE {
request [3000] Request,
response [3001] Response
}
Message ::= SEQUENCE {
id INTEGER,
body BODY
}
END
and message with value
value Message ::= {
id 1,
body request : {
num 1
}
}
I have tested this on https://asn1.io/asn1playground/Default.aspx and the message is encoded as:
0101BF9738 0101
here is my rust code using rasn
use rasn::AsnType;
use rasn::Decode;
use rasn::Encode;
use std::error::Error;
#[derive(Debug, AsnType, Decode, Encode)]
struct Message {
id: i32,
#[rasn(choice)]
body: BODY,
}
#[derive(Debug, AsnType, Decode, Encode)]
#[rasn(choice)]
enum BODY {
#[rasn(tag(context, 3000))]
Request(Request),
#[rasn(tag(context, 3001))]
Response(Response),
}
#[derive(Debug, AsnType, Decode, Encode)]
struct Request {
num: i32,
}
#[derive(Debug, AsnType, Decode, Encode)]
struct Response {
ret: i32,
}
fn main() -> Result<(), Box<dyn Error>> {
let p = Message {
id: 1,
body: BODY::Request(Request { num: 1 }),
};
let e = rasn::der::encode(&p).unwrap();
println!("e: {:#04x?}", e);
Ok(())
}
and it outputs
0x30,0x0a,0x02,0x01,0x01,0xbf,0xb8,0x17,0x03,0x02,0x01,0x01,
the big difference is the body choice on https://asn1.io/asn1playground/Default.aspx is decoded as BF9738
but using rasn decoded as BFB817
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.