53 个版本 (15 个重大更新)
新 0.16.5 | 2024 年 8 月 23 日 |
---|---|
0.16.0 | 2024 年 7 月 17 日 |
0.13.1 | 2024 年 3 月 21 日 |
0.12.4 | 2023 年 12 月 4 日 |
0.0.0 | 2019 年 6 月 22 日 |
在 编码 类别下排名 #60
每月下载量 154,610
用于 38 个 包(20 个直接使用)
695KB
17K SLoC
Rasn
欢迎来到 rasn
(发音为 "raisin"),一个安全的 #[no_std]
ASN.1 编解码框架。这使您能够安全地创建、共享和处理从不同编码规则转换的 ASN.1 数据类型。如果您对 ASN.1 和 BER/DER 等编码格式不熟悉,我建议在继续之前阅读 Let's Encrypt 的"A Warm Welcome to ASN.1 and DER" 作为快速介绍。简而言之,它是一种“接口描述语言”(和数据模型),以及为该模型定义的一组编码格式(称为规则)。它最初在 20 世纪 80 年代末期设计,并在整个行业中使用,特别是在电信和密码学领域。
特性
抽象编解码数据模型
目前已经有相当多的 ASN.1 相关的 Rust 包,但是它们目前都针对单一格式,甚至单一标准,这使得共享和重用 ASN.1 中指定的标准变得困难。现在,有了 rasn
的抽象模型,您可以将 ASN.1 数据类型构建为可以与任何编码器或解码器一起工作的包,无论其底层编码规则是 BER、CER、DER 还是您自己的自定义编码。
#[no_std]
支持
Rasn 完全是 #[no_std]
,因此您可以在支持 alloc
的任何 Rust 目标平台上共享相同的 ASN.1 实现。
丰富的数据类型
Rasn 目前支持 ASN.1 几乎所有的数据类型。 rasn
使用流行的社区库,如 bitvec
、bytes
和 chrono
,以及提供一些自己的数据类型。查看 types
模块,了解目前可用的内容。
安全编解码器
编码器和解码器已经用100%安全的Rust编写,并使用美国模糊跳蛙++进行模糊测试,以确保解码器可以正确处理随机输入,如果有效,编码器可以正确重新编码该值。
支持的编解码器
- 基本编码规则(BER)
- 规范编码规则(CER)
- 区分编码规则(DER)
- 对齐打包编码规则(APER)
- 非对齐打包编码规则(UPER)
- JSON编码规则(JER)
- 八位编码规则(OER)
- 规范八位编码规则(COER)
RFC实现
Rasn还提供了使用rasn
框架实现的多个IETF RFC,可直接使用。这些crate为必要的数据类型提供了强类型定义。与rasn
一样,它们是#[no_std]
,并且是传输层和编码规则无关的。
- CMS: 密码学消息语法
- Kerberos认证框架
- LDAP: 轻量级目录访问协议
- MIB-II: 信息库管理
- OCSP: 线上证书状态协议
- PKIX: 公共密钥基础设施
- SMI: 管理信息结构
- SNMP: 简单网络管理协议
- S/MIME: 安全/多用途互联网邮件扩展
强大的派生宏
使用所有特质的派生等效物轻松建模您的结构和枚举。这些宏提供自动实现,确保您的模型在编译时是有效的ASN.1类型。但要解释这一点,首先我们必须解释…
工作原理
编解码器API已被设计为易于使用、安全且难以误用。最常见的错误是处理长度和确保其正确编码和解码。在rasn
中,这一点完全抽象化,让您专注于抽象模型。让我们看看解码简单的自定义SEQUENCE
类型的样子。
Person ::= SEQUENCE {
age INTEGER,
name UTF8String
}
我们希望将其映射到以下等效的Rust代码。
struct Person {
age: rasn::types::Integer,
name: String, // or rasn::types::Utf8String
}
实现特剧行为
在建模ASN.1数据类型时,我们需要实现三个特剧行为。对于将编码规则转换为和从编码规则转换的Decode
和Encode
,以及共享的AsnType
特剧行为;它定义了一些需要提供给编码器和解码器的关联数据。目前我们唯一要定义的是用于识别我们类型的标签。
# struct Person;
use rasn::{AsnType, Tag};
impl AsnType for Person {
// Default tag for sequences.
const TAG: Tag = Tag::SEQUENCE;
}
接下来是Decode
和Encode
特剧行为。这两个是对方的镜像,并且都提供了一个提供的方法(decode/encode
)和一个必需的方法(decode_with_tag/encode_with_tag
)。由于在ASN.1中几乎每种类型都可以隐式标记,允许任何人覆盖与类型关联的标记,所以必需的*_with_tag
方法要求实现者正确处理这种情况,而提供的方法只是简单地将类型关联的AsnType::TAG
作为参数调用*_with_tag
方法。让我们看看Person
的编解码器实现。
# use rasn::{AsnType, types::{Constructed, fields::{Field, Fields}}};
# struct Person { name: Utf8String, age: Integer }
# impl AsnType for Person { const TAG: Tag = Tag::SEQUENCE; }
# impl Constructed for Person {
# const FIELDS: Fields = Fields::from_static(&[
# Field::new_required(Utf8String::TAG, Utf8String::TAG_TREE, "age"),
# Field::new_required(Integer::TAG, Integer::TAG_TREE, "name"),
# ]);
# }
use rasn::{prelude::*, types::{Integer, Utf8String}};
impl Decode for Person {
fn decode_with_tag_and_constraints<D: Decoder>(decoder: &mut D, tag: Tag, constraints: Constraints) -> Result<Self, D::Error> {
// Accepts a closure that decodes the contents of the sequence.
decoder.decode_sequence(tag, None::<fn () -> Self>, |decoder| {
let age = Integer::decode(decoder)?;
let name = Utf8String::decode(decoder)?;
Ok(Self { age, name })
})
}
}
impl Encode for Person {
fn encode_with_tag_and_constraints<E: Encoder>(&self, encoder: &mut E, tag: Tag, constraints: Constraints) -> Result<(), E::Error> {
// Accepts a closure that encodes the contents of the sequence.
encoder.encode_sequence::<Self, _>(tag, |encoder| {
self.age.encode(encoder)?;
self.name.encode(encoder)?;
Ok(())
})?;
Ok(())
}
}
这就完成了!我们刚刚创建了一个新的ASN.1,它可以被编码和解码为BER、CER和DER;在编码和解码过程中,我们不需要检查标签、长度或字符串是原始的还是构造的编码。所有那些讨厌的编码规则细节都被完全抽象化,因此您的类型只需处理如何映射到和从ASN.1的数据模型。
由于所有的实际转换代码都被隔离到编解码器实现中,您可以知道您的模型始终是安全的。API也被设计用来防止您犯导致无效编码的常见逻辑错误。例如,如果我们回顾一下我们的 Encode
实现,如果我们忘记在 encode_sequence
中使用我们得到的编码器,而是尝试使用父编码器会怎么样呢?
error[E0501]: cannot borrow `*encoder` as mutable because previous closure requires unique access
--> tests/derive.rs:122:9
|
122 | encoder.encode_sequence(tag, |sequence| {
| ^ --------------- ---------- closure construction occurs here
| | |
| _________| first borrow later used by call
| |
123 | | self.age.encode(encoder)?;
| | ------- first borrow occurs due to use of `encoder` in closure
124 | | self.name.encode(sequence)?;
125 | | Ok(())
126 | | })?;
| |__________^ second borrow occurs here
error[E0500]: closure requires unique access to `encoder` but it is already borrowed
--> tests/derive.rs:122:38
|
122 | encoder.encode_sequence(tag, |sequence| {
| ------- --------------- ^^^^^^^^^^ closure construction occurs here
| | |
| | first borrow later used by call
| borrow occurs here
123 | self.age.encode(encoder)?;
| ------- second borrow occurs due to use of `encoder` in closure
我们的代码编译失败了!这在当前情况下是个好事,因为我们没有机会因为忘记更改变量名称而导致内容被错误地编码。这些所有权语义还意味着一个 Encoder
在实现中不会意外地多次编码序列的内容。让我们看看如何更进一步。
使用宏的编译安全ASN.1
到目前为止,我们已经展示了rasn的API如何采取措施确保安全并防止意外创建无效模型。然而,在命令式API中很难涵盖所有内容。关于ASN.1的一个重要理解,在上述示例中并不明显的是,在ASN.1中,所有类型都可以通过一个标签(本质上两个数字,例如 INTEGER
的标签是 0, 2
)来识别)。字段和变体名称在大多数编码规则中不传输,因此这个标签也用于在 SEQUENCE
或 CHOICE
中识别字段或变体。这意味着在ASN.1结构体或枚举中,每个字段和变体 必须 有一个独特的标签,整个类型才能被认为是有效的。例如;如果我们把 Person
中的 age
改成一个 String
,就像下面这样,它将是不有效的ASN.1,即使它编译和运行正确,我们必须使用不同的类型或覆盖 age
的标签,使其与 name
的标签不同。当自己实现 AsnType
特性时,必须手动检查此要求,但正如我们将看到的,您通常不需要这样做。
rasn 包含一系列 derive 宏,使您能够以声明性方式实现 ASN.1 模型实现。 Encode
和 Decode
宏将基本自动生成我们之前展示的实现,但真正的魔法在于 AsnType
derive 宏。多亏了 static-assertions
crate 和最近的 const fn
发展;AsnType
derive 不仅可以生成您的 AsnType
实现,它还会生成一个在 编译时 断言每个字段或变体具有独特标签的检查。这意味着现在如果由于某种原因我们对 person 中的一个类型进行了更改,我们不需要重新检查我们的模型是否仍然有效,编译器会为我们处理这个问题。
// Invalid
#[derive(rasn::AsnType)]
struct Person {
age: Option<String>,
name: Option<String>,
}
现在在尝试编译上述定义时,我们会得到以下错误。
error[E0080]: evaluation of constant value failed
--> tests/derive.rs:146:10
|
146 | #[derive(rasn::AsnType)]
| ^^^^^^^^^^^^^ the evaluated program panicked at 'Person's fields is not a valid order of ASN.1 tags, ensure that your field's tags and OPTIONAL
s are correct.', tests/derive.rs:146:10
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
在编译时验证您的模型可以使您在编写 ASN.1 代码时无需担心无意中更改后台的某些内容。我敢打赌你现在可能想知道,我们应该如何有一个包含两个字段的 struct?答案是幸运的很简单,您只需添加 #[rasn(tag)]
属性来覆盖一个或多个类型的标签。然而,我们可以更进一步,因为在 ASN.1 中存在 AUTOMATIC TAGS
的概念,这本质上告诉您的 ASN.1 编译器自动为您 ASN.1 定义生成不同的标签。现在,使用 rasn,您可以在 Rust 中实现这一点!将 #[rasn(automatic_tags)]
应用到容器将自动生成标签,这将应用您从 ASN.1 编译器期望的相同自动标签转换。
use rasn::AsnType;
// Valid
#[derive(AsnType)]
struct Person {
#[rasn(tag(context, 0))] // or just #[rasn(tag(0))]
age: Option<String>,
name: Option<String>,
}
// Also valid
#[derive(AsnType)]
#[rasn(automatic_tags)]
struct Person2 {
age: Option<String>,
name: Option<String>,
}
参考
以下表格提供了一系列示例,展示了如何使用 rasn
声明数据类型。
ASN1 | rasn | |
类型别名 |
|
|
BOOLEAN 类型 |
|
|
NULL 类型 |
|
|
INTEGER 类型 |
|
|
单值约束 |
|
|
值范围约束 |
|
|
可扩展值约束 |
|
|
ENUMERATED 类型 |
|
|
可扩展 ENUMERATED 类型 |
|
|
AUTOMATIC TAGS 环境 |
|
|
EXPLICIT TAGS 环境 |
|
|
IMPLICIT TAGS 环境 |
|
|
CHOICE 类型 |
|
|
SEQUENCE 类型 |
|
|
SET 类型 |
|
|
重命名字段 |
|
|
可选和默认字段 |
|
|
SEQUENCE OF 类型 |
|
|
字符字符串类型 |
|
|
BIT STRING 类型 |
|
|
OCTET STRING 类型 |
|
|
大小约束 |
|
|
允许的字母表约束 |
|
|
赞助商
该项目是通过 NLnet 建立的 NGI Assure Fund 资助的,NLnet 由欧洲委员会的 Next Generation Internet 计划提供资金支持,在 DG Communications Networks、Content and Technology 的指导下,根据第 957073 号资助协议。
免责声明
软件按原样提供,作者在此软件方面放弃所有保证,包括所有默示的适销性和适用性保证。在任何情况下,作者均不对任何特殊、直接、间接或后果性损害或任何损害(无论因合同、疏忽或其他侵权行为而引起或与之相关)承担责任,包括但不限于使用或数据或利润的损失。
依赖性
~7MB
~144K SLoC