add crl data model
This commit is contained in:
parent
177883c50c
commit
5e474fffd2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rpki"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
der-parser = "10.0.0"
|
||||
hex = "0.4.3"
|
||||
thiserror = "2.0.18"
|
||||
time = "0.3.45"
|
||||
x509-parser = { version = "0.18.0", features = ["verify"] }
|
||||
217
specs/00_common_types.md
Normal file
217
specs/00_common_types.md
Normal file
@ -0,0 +1,217 @@
|
||||
# 00. 公共类型、编码约定与 OID 表
|
||||
|
||||
本文件定义各数据对象文档共用的类型与记号。
|
||||
|
||||
## 0.1 规范关键字
|
||||
|
||||
文中 “MUST/SHOULD/MAY/MUST NOT/SHOULD NOT” 语义遵循 RFC 2119 / RFC 8174。
|
||||
|
||||
## 0.2 编码/序列化约定
|
||||
|
||||
### 0.2.1 DER
|
||||
|
||||
- `DER`:ASN.1 Distinguished Encoding Rules(DER)。RPKI Signed Object 与其 eContent 均要求 DER 编码。RFC 6488 §2;RFC 9286 §4.2;RFC 9582 §4。
|
||||
|
||||
### 0.2.2 X.509 v3 扩展(Extension)编码模板
|
||||
|
||||
> 用途:解释证书/CRL里“按 OID 挂扩展”的通用编码方式。该模板来自 RFC 5280,对 RPKI 来说是“所有 extnID 扩展”的外层载体。
|
||||
|
||||
证书与 CRL 的扩展都使用同一个外层结构(DER 编码)。其 ASN.1 定义如下:RFC 5280 §4.1。
|
||||
|
||||
```asn1
|
||||
Extension ::= SEQUENCE {
|
||||
extnID OBJECT IDENTIFIER,
|
||||
critical BOOLEAN DEFAULT FALSE,
|
||||
extnValue OCTET STRING
|
||||
-- contains the DER encoding of an ASN.1 value
|
||||
-- corresponding to the extension type identified
|
||||
-- by extnID
|
||||
}
|
||||
```
|
||||
|
||||
解码要点:
|
||||
|
||||
- 先按 `extnID` 选择“要用哪一种内层 ASN.1 结构”。
|
||||
- 再把 `extnValue` 的 octets 作为 DER 进行**二次解码**,得到该扩展的内层值(例如 SIA/AIA/CRLDP/3779 资源扩展等)。
|
||||
|
||||
编码要点:
|
||||
|
||||
- 先把内层值按其 ASN.1 结构 DER 编码,得到 `inner_der`;
|
||||
- 再写入 `extnValue = OCTET STRING(inner_der)`。
|
||||
|
||||
RFC 引用:RFC 5280 §4.1(证书扩展位置);RFC 5280 §4.2(扩展定义与语义)。
|
||||
|
||||
### 0.2.3 AIA/SIA 的内层容器结构(AccessDescription + GeneralName URI)
|
||||
|
||||
Authority Information Access(AIA)与 Subject Information Access(SIA)的内层值都是一个 “AccessDescription 列表”。其 ASN.1 定义如下:RFC 5280 §4.2.2.1;RFC 5280 §4.2.2.2。
|
||||
|
||||
```asn1
|
||||
AuthorityInfoAccessSyntax ::=
|
||||
SEQUENCE SIZE (1..MAX) OF AccessDescription
|
||||
|
||||
SubjectInfoAccessSyntax ::=
|
||||
SEQUENCE SIZE (1..MAX) OF AccessDescription
|
||||
|
||||
AccessDescription ::= SEQUENCE {
|
||||
accessMethod OBJECT IDENTIFIER,
|
||||
accessLocation GeneralName }
|
||||
```
|
||||
|
||||
其中 `accessLocation` 的 `GeneralName`(URI 用 `uniformResourceIdentifier` 分支)定义如下:RFC 5280 §4.2.1.6。
|
||||
|
||||
```asn1
|
||||
GeneralName ::= CHOICE {
|
||||
otherName [0] OtherName,
|
||||
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 }
|
||||
```
|
||||
|
||||
解码要点:
|
||||
|
||||
- 先按 `extnID` 识别是 AIA(`1.3.6.1.5.5.7.1.1`) 或 SIA(`1.3.6.1.5.5.7.1.11`);
|
||||
- 再把 `extnValue` 解码成 “SEQUENCE OF AccessDescription”;
|
||||
- 每条 `AccessDescription` 用 `accessMethod` 的 OID 区分语义;
|
||||
- URI 通常出现在 `GeneralName.uniformResourceIdentifier`(IA5String)中。
|
||||
|
||||
RFC 引用:RFC 5280 §4.2.2.1;RFC 5280 §4.2.2.2;RFC 5280 §4.2.1.6。
|
||||
|
||||
### 0.2.2 Base64(仅 TAL)
|
||||
|
||||
- `Base64`:TAL 中的 `subjectPublicKeyInfo` 以 DER 字节序列经 Base64 编码表示;允许插入换行。RFC 8630 §2.2。
|
||||
|
||||
### 0.2.4 换行
|
||||
|
||||
- `LF` 或 `CRLF`:TAL 文件允许使用 `\n` 或 `\r\n`。RFC 8630 §2.2。
|
||||
|
||||
## 0.3 基本类型(抽象模型层)
|
||||
|
||||
> 说明:这些类型用于描述“语义对象”,不等同于 ASN.1 具体类型,但会给出从 ASN.1/文本到该类型的解析规则。
|
||||
|
||||
- `DerBytes`: `bytes`,承载一个 DER 编码对象的原始字节(入口)。
|
||||
- `Oid`: `string`(点分十进制),例如 `1.2.840.113549.1.9.16.1.26`。
|
||||
- `Uri`: `string`,URI 文本;其语法来自 RFC 3986,但在 RPKI profile 下会被进一步限制(见各对象文档)。
|
||||
- `Utf8Text`: `string`,UTF-8 文本(例如 TAL 注释行)。RFC 8630 §2.2(引用 RFC 5198 §2 的限制)。
|
||||
- `UtcTime`: `string`,承载“UTC 时间点”的语义值;来源可能是 X.509 `Time`(UTCTime/GeneralizedTime)或 ASN.1 `GeneralizedTime`(如 Manifest)。RFC 5280 §4.1.2.5;RFC 9286 §4.2。
|
||||
- `HexBytes`: `bytes` 的十六进制展示形态(仅文档说明用,不建议作为接口字段)。
|
||||
|
||||
## 0.4 RPKI 资源集合语义(高层表示)
|
||||
|
||||
### 0.4.1 IP 资源集合(语义模型)
|
||||
|
||||
用于表达 RFC 3779 的 `IPAddrBlocks`(以及 ROA 的“仅前缀”子集)。
|
||||
|
||||
#### 类型:`IpResourceSet`
|
||||
|
||||
- `families: list[IpFamilyResources]`
|
||||
|
||||
#### 类型:`IpFamilyResources`
|
||||
|
||||
- `afi: enum { ipv4, ipv6 }`
|
||||
- `safi: optional[int]`
|
||||
- 在公用互联网资源证书语义中,SAFI **MUST NOT** 使用。RFC 6487 §4.8.10。
|
||||
- `choice: enum { inherit, explicit }`
|
||||
- `explicit_resources: optional[list[IpRangeOrPrefix]]`
|
||||
|
||||
#### 类型:`IpRangeOrPrefix`
|
||||
|
||||
- `kind: enum { prefix, range }`
|
||||
- `prefix: optional[IpPrefix]`
|
||||
- `range: optional[IpRange]`
|
||||
|
||||
#### 类型:`IpPrefix`
|
||||
|
||||
- `address: bytes`(IPv4 4 字节或 IPv6 16 字节的网络序)
|
||||
- `prefix_len: int`
|
||||
|
||||
#### 类型:`IpRange`
|
||||
|
||||
- `min: bytes`
|
||||
- `max: bytes`
|
||||
|
||||
解析与约束要点(实现应在解析阶段保留必要信息供后续验证使用):
|
||||
|
||||
- RFC 3779 `IPAddress`/`IPAddressRange` 以 `BIT STRING` 编码前缀/范围,并对排序/去重/规范编码给出要求。RFC 3779 §2.2.3.6-§2.2.3.9。
|
||||
- RFC 3779 `inherit` 语义表示资源集合继承自签发者。RFC 3779 §2.2.3.5。
|
||||
|
||||
### 0.4.2 AS 资源集合(语义模型)
|
||||
|
||||
用于表达 RFC 3779 的 `ASIdentifiers`。
|
||||
|
||||
#### 类型:`AsResourceSet`
|
||||
|
||||
- `asnum: optional[AsIdentifierChoice]`
|
||||
- 在 RPKI profile 中,`asnum` 与 `rdi` 不能都缺省到导致“无 AS 资源扩展”的语义;但 RC 层面允许 “仅 IP” 或 “仅 AS” 或两者都存在。RFC 6487 §4.8.10;RFC 6487 §4.8.11。
|
||||
- `rdi: optional[AsIdentifierChoice]`
|
||||
- RPKI profile **不支持** RDI,MUST NOT 使用。RFC 6487 §4.8.11。
|
||||
|
||||
#### 类型:`AsIdentifierChoice`
|
||||
|
||||
- `choice: enum { inherit, explicit }`
|
||||
- `explicit_ranges: optional[list[AsRangeOrId]]`
|
||||
|
||||
#### 类型:`AsRangeOrId`
|
||||
|
||||
- `kind: enum { id, range }`
|
||||
- `id: optional[int]`(0..4294967295)
|
||||
- `range: optional[AsRange]`
|
||||
|
||||
#### 类型:`AsRange`
|
||||
|
||||
- `min: int`
|
||||
- `max: int`
|
||||
|
||||
解析与约束要点:
|
||||
|
||||
- `inherit` 语义与排序/范围编码规则来自 RFC 3779。RFC 3779 §3.2.3.3;RFC 3779 §3.2.3.8。
|
||||
|
||||
## 0.5 常用 OID 表(最小集合)
|
||||
|
||||
> 说明:表中 “来源 RFC” 给出该 OID 在本模型中的直接规范引用点;在实现时也可以直接用点分十进制比对。
|
||||
|
||||
| OID | 符号名/含义 | 来源 RFC |
|
||||
|---|---|---|
|
||||
| `1.2.840.113549.1.7.2` | CMS SignedData(ContentInfo.contentType) | RFC 6488 §3(1a) |
|
||||
| `1.2.840.113549.1.9.3` | CMS signedAttrs: content-type | RFC 6488 §3(1f) |
|
||||
| `1.2.840.113549.1.9.4` | CMS signedAttrs: message-digest | RFC 6488 §3(1f) |
|
||||
| `1.2.840.113549.1.9.5` | CMS signedAttrs: signing-time | RFC 9589 §4(更新 RFC 6488 §3(1f)/(1g)) |
|
||||
| `1.2.840.113549.1.9.16.1.24` | ROA eContentType: id-ct-routeOriginAuthz | RFC 9582 §3 |
|
||||
| `1.2.840.113549.1.9.16.1.26` | Manifest eContentType: id-ct-rpkiManifest | RFC 9286 §4.1 |
|
||||
| `1.3.6.1.5.5.7.1.1` | X.509 v3 扩展:authorityInfoAccess | RFC 5280 §4.2.2.1 |
|
||||
| `1.3.6.1.5.5.7.1.11` | X.509 v3 扩展:subjectInfoAccess | RFC 5280 §4.2.2.2;RPKI 约束见 RFC 6487 §4.8.8 |
|
||||
| `1.3.6.1.5.5.7.48.2` | AIA accessMethod: id-ad-caIssuers | RFC 5280 §4.2.2.1 |
|
||||
| `1.3.6.1.5.5.7.48.5` | SIA accessMethod: id-ad-caRepository | RFC 5280 §4.2.2.2;RPKI 语义见 RFC 6481 §2.2;约束见 RFC 6487 §4.8.8.1 |
|
||||
| `1.3.6.1.5.5.7.48.10` | SIA accessMethod: id-ad-rpkiManifest | RFC 6487 §4.8.8.1 |
|
||||
| `1.3.6.1.5.5.7.48.11` | SIA accessMethod: id-ad-signedObject | RFC 6487 §4.8.8.2 |
|
||||
| `1.3.6.1.5.5.7.48.13` | SIA accessMethod: id-ad-rpkiNotify(RRDP Notification) | RFC 8182 §3.2 |
|
||||
| `1.3.6.1.5.5.7.1.7` | RFC 3779 IP 资源扩展:id-pe-ipAddrBlocks | RFC 3779 §2.2.1 |
|
||||
| `1.3.6.1.5.5.7.1.8` | RFC 3779 AS 资源扩展:id-pe-autonomousSysIds | RFC 3779 §3.2.1 |
|
||||
| `2.5.29.31` | X.509 v3 扩展:cRLDistributionPoints | RFC 5280 §4.2.1.13;RPKI 约束见 RFC 6487 §4.8.6 |
|
||||
| `2.5.29.32` | X.509 v3 扩展:certificatePolicies | RFC 5280 §4.2.1.4;RPKI 约束见 RFC 6487 §4.8.9;更新见 RFC 7318 §2 |
|
||||
| `2.5.29.14` | X.509 v3 扩展:subjectKeyIdentifier | RFC 5280 §4.2.1.2;RPKI 约束见 RFC 6487 §4.8.2 |
|
||||
| `2.5.29.35` | X.509 v3 扩展:authorityKeyIdentifier(证书/CRL) | RFC 5280 §4.2.1.1(证书);RFC 5280 §5.2.1(CRL);RPKI 约束见 RFC 6487 §4.8.3 / RFC 9829 §3.1 |
|
||||
| `2.5.29.15` | X.509 v3 扩展:keyUsage | RFC 5280 §4.2.1.3;RPKI 约束见 RFC 6487 §4.8.4 |
|
||||
| `2.5.29.19` | X.509 v3 扩展:basicConstraints | RFC 5280 §4.2.1.9;RPKI 约束见 RFC 6487 §4.8.1 |
|
||||
| `2.5.29.20` | CRL 扩展:cRLNumber | RFC 5280 §5.2.3;RPKI 约束更新见 RFC 9829 §3.1 |
|
||||
| `1.3.6.1.5.5.7.14.2` | RPKI Certificate Policy:id-cp-ipAddr-asNumber | RFC 6484 §1.2;证书中使用要求见 RFC 6487 §4.8.9 |
|
||||
| `1.3.6.1.5.5.7.2.1` | Policy Qualifier:id-qt-cps | RFC 5280 §4.2.1.4;RPKI 限制见 RFC 7318 §2 |
|
||||
| `2.16.840.1.101.3.4.2.1` | `id-sha256`(SHA-256 摘要算法 OID) | RFC 7935 §2(引用 RFC 5754) |
|
||||
| `1.2.840.113549.1.1.1` | `rsaEncryption`(CMS SignerInfo.signatureAlgorithm 生成时使用) | RFC 7935 §2(引用 RFC 3370) |
|
||||
| `1.2.840.113549.1.1.11` | `sha256WithRSAEncryption`(证书/CRL 签名算法;CMS 验证兼容) | RFC 7935 §2(引用 RFC 4055) |
|
||||
|
||||
## 0.6 `None` 类型字段的含义(文档约定)
|
||||
|
||||
在本目录的“抽象数据模型(接口)”字段表里,如果某字段的类型标为 `None`,表示:
|
||||
|
||||
- 该字段/子字段在通用 ASN.1 结构里**可能存在**(或是可选字段/可扩展集合),但在 RPKI profile 下被明确规定 **MUST NOT be present / MUST be omitted**;
|
||||
- 因此它**不应作为正常语义数据对象的可用字段**出现;
|
||||
- 实现可以选择两种方式之一:
|
||||
1) **不在对象结构中建模该字段**,仅在“约束清单”里写明禁止出现;解析时若遇到该字段则直接报“profile 违规”;或
|
||||
2) 为了便于输出结构化诊断,把它保留成“永远为 None 的占位字段”,并规定:若从 DER 中解析到该字段,则对象不符合 profile。
|
||||
|
||||
注意:像 CMS `signedAttrs` 中的 “other attributes MUST NOT be included” 这类规则,本质是“集合不得含额外成员”。文档里用 `other_attrs: None` 只是为了把“禁止项”放进同一张字段表,便于实现逐条对照;它不代表 ASN.1 里真的有一个名为 `other_attrs` 的字段。
|
||||
141
specs/04_crl.md
Normal file
141
specs/04_crl.md
Normal file
@ -0,0 +1,141 @@
|
||||
# 04. CRL(Resource Certificate Revocation List)
|
||||
|
||||
## 4.1 对象定位
|
||||
|
||||
RPKI CA 必须发布符合 profile 的 CRL,用于声明其签发且未过期证书中的撤销集合。RFC 6487 §5。
|
||||
|
||||
RFC 9829 更新了 RFC 6487 对 CRL Number 以及 “current CRL” 识别的处理规则。RFC 9829 §3。
|
||||
|
||||
## 4.2 原始载体与编码
|
||||
|
||||
- 载体:X.509 CRL。
|
||||
- 编码:DER(遵循 RFC 5280 的 CRL 结构与字段语义,但受 RPKI profile 限制)。RFC 6487 §5(“consistent with RFC 5280”)。
|
||||
|
||||
### 4.2.1 X.509 v2 CRL 基本语法(ASN.1;RFC 5280 §5.1)
|
||||
|
||||
CRL 在编码层面是 RFC 5280 定义的 `CertificateList`(DER)。RFC 5280 §5.1。
|
||||
|
||||
```asn1
|
||||
CertificateList ::= SEQUENCE {
|
||||
tbsCertList TBSCertList,
|
||||
signatureAlgorithm AlgorithmIdentifier,
|
||||
signatureValue BIT STRING }
|
||||
|
||||
TBSCertList ::= SEQUENCE {
|
||||
version Version OPTIONAL,
|
||||
-- if present, MUST be v2
|
||||
signature AlgorithmIdentifier,
|
||||
issuer Name,
|
||||
thisUpdate Time,
|
||||
nextUpdate Time OPTIONAL,
|
||||
revokedCertificates SEQUENCE OF SEQUENCE {
|
||||
userCertificate CertificateSerialNumber,
|
||||
revocationDate Time,
|
||||
crlEntryExtensions Extensions OPTIONAL
|
||||
-- if present, version MUST be v2
|
||||
} OPTIONAL,
|
||||
crlExtensions [0] EXPLICIT Extensions OPTIONAL
|
||||
-- if present, version MUST be v2
|
||||
}
|
||||
```
|
||||
|
||||
> 注:`Version`/`Time`/`CertificateSerialNumber`/`Extensions` 的定义在 RFC 5280 §4.1;`AlgorithmIdentifier` 的定义在 RFC 5280 §4.1.1.2(RFC 5280 §5.1 的注释段落给出引用)。
|
||||
|
||||
### 4.2.2 CRL 扩展中常用内层结构(ASN.1;RFC 5280 §4.2.1.1;RFC 5280 §5.2.3)
|
||||
|
||||
RPKI profile(经 RFC 9829 更新)要求 CRL **仅允许**两个扩展:AKI 与 CRL Number。RFC 9829 §3.1。
|
||||
|
||||
```asn1
|
||||
id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
|
||||
|
||||
AuthorityKeyIdentifier ::= SEQUENCE {
|
||||
keyIdentifier [0] KeyIdentifier OPTIONAL,
|
||||
authorityCertIssuer [1] GeneralNames OPTIONAL,
|
||||
authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
||||
|
||||
KeyIdentifier ::= OCTET STRING
|
||||
|
||||
id-ce-cRLNumber OBJECT IDENTIFIER ::= { id-ce 20 }
|
||||
|
||||
CRLNumber ::= INTEGER (0..MAX)
|
||||
```
|
||||
|
||||
|
||||
## 4.3 抽象数据模型(接口)
|
||||
|
||||
### 4.3.0 `Asn1TimeUtc`(X.509 `Time` 的抽象)
|
||||
|
||||
X.509 中的 `Time` 是一个 CHOICE,可用 `UTCTime` 或 `GeneralizedTime` 编码;但在语义层面都表达一个 UTC 时间点。RFC 5280 §4.1.2.5;RFC 5280 §5.1.2.4。
|
||||
|
||||
因此在抽象模型中,将 `Time` 规范化为 “UTC 时间点 + 原始编码形态”:
|
||||
|
||||
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||
|---|---|---|---|---|
|
||||
| `utc` | `UtcTime` | 规范化后的 UTC 时间点 | 从 `UTCTime`/`GeneralizedTime` 解码并转换为 UTC 时间点 | RFC 5280 §4.1.2.5.1;RFC 5280 §4.1.2.5.2 |
|
||||
| `encoding` | `enum{UTCTime, GeneralizedTime}` | 原始编码类型 | 需要保留编码形态以支持 profile 的编码约束校验 | RFC 5280 §5.1.2.4;RFC 5280 §5.1.2.5 |
|
||||
|
||||
### 4.3.1 `RpkixCrl`
|
||||
|
||||
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||
|---|---|---|---|---|
|
||||
| `raw_der` | `DerBytes` | CRL DER | 原样保留(建议) | RFC 6487 §5 |
|
||||
| `version` | `int` | CRL 版本 | MUST 为 v2;RP 不要求处理 v1 | RFC 6487 §5 |
|
||||
| `issuer_dn` | `RpkixDistinguishedName` | CRL issuer | issuer DN 约束同证书 issuer(CN/serialNumber 规则) | RFC 6487 §5(引用 §4.4) |
|
||||
| `signature_algorithm` | `Oid` | CRL 签名算法 | 必须为 `sha256WithRSAEncryption`(`1.2.840.113549.1.1.11`) | RFC 6487 §5;RFC 7935 §2(引用 RFC 4055) |
|
||||
| `this_update` | `Asn1TimeUtc` | 本次 CRL 生成时间 | X.509 `Time`(UTCTime/GeneralizedTime)解码为 UTC 时间点,并保留原始编码形态 | RFC 5280 §5.1.2.4;RFC 5280 §4.1.2.5 |
|
||||
| `next_update` | `Asn1TimeUtc` | 下次 CRL 计划时间 | **MUST present**(尽管 ASN.1 标注 OPTIONAL);解码同 `this_update` | RFC 5280 §5.1.2.5 |
|
||||
| `revoked_certs` | `list[RevokedCert]` | 撤销条目 | 仅包含未过期且已撤销证书 | RFC 6487 §5 |
|
||||
| `extensions` | `CrlExtensions` | CRL 扩展 | **仅允许** AKI 与 CRL Number 两个扩展 | RFC 9829 §3.1(更新 RFC 6487 §5) |
|
||||
|
||||
### 4.3.2 `RevokedCert`
|
||||
|
||||
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||
|---|---|---|---|---|
|
||||
| `serial_number` | `int` | 被撤销证书序列号 | 必须存在 | RFC 6487 §5 |
|
||||
| `revocation_date` | `Asn1TimeUtc` | 撤销时间 | X.509 `Time` 解码为 UTC 时间点,并保留原始编码形态;revocationDate 的表达规则同 `thisUpdate` | RFC 6487 §5;RFC 5280 §5.1.2.6(引用 §5.1.2.4) |
|
||||
| `entry_extensions` | `None` | 条目扩展 | CRL entry extensions MUST NOT present | RFC 6487 §5 |
|
||||
|
||||
### 4.3.3 `CrlExtensions`
|
||||
|
||||
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||
|---|---|---|---|---|
|
||||
| `authority_key_identifier` | `bytes` | AKI.keyIdentifier | **extnID=`2.5.29.35`**;RP MUST 处理 AKI;CRL 扩展仅允许 AKI 与 CRLNumber | RFC 9829 §3.1;RFC 5280 §5.2.1 |
|
||||
| `crl_number` | `int` | CRLNumber | **extnID=`2.5.29.20`**;RP 必须忽略其排序语义;仅检查 non-critical 且数值范围:0..2^159-1 | RFC 9829 §3.1(引用 RFC 5280 §5.2.3) |
|
||||
|
||||
## 4.4 字段级约束清单(实现对照)
|
||||
|
||||
- CA 必须签发 version 2 CRL;RP 不要求处理 v1。RFC 6487 §5。
|
||||
- CRL 不得包含 Indirect CRL 或 Delta CRL。RFC 6487 §5。
|
||||
- CRL 范围必须覆盖该 CA 签发的全部证书。RFC 6487 §5。
|
||||
- `nextUpdate` MUST present(尽管 ASN.1 标注 OPTIONAL)。RFC 5280 §5.1.2.5。
|
||||
- 时间编码规则(`thisUpdate`/`nextUpdate`/`revocationDate`):2049(含)及之前必须用 `UTCTime`;2050(含)及之后必须用 `GeneralizedTime`;应用必须能处理两者。RFC 5280 §5.1.2.4;RFC 5280 §5.1.2.5;RFC 5280 §5.1.2.6(引用 §5.1.2.4)。
|
||||
- 只允许两个 CRL 扩展:AKI 与 CRLNumber;除此之外不允许任何 CRL 扩展。RFC 9829 §3.1。
|
||||
- RP 必须处理 AKI;CRLNumber 仅做 “non-critical + 数值范围” 检查并忽略其它语义。RFC 9829 §3.1。
|
||||
- 每个撤销条目仅允许 Serial Number 与 Revocation Date;不允许条目扩展。RFC 6487 §5。
|
||||
|
||||
## 4.5 CRL 验签与绑定校验(验证阶段)
|
||||
|
||||
> 本节描述“基于已解析的数据对象对 CRL 做签名校验(cryptographic signature validation)”所需输入与处理步骤。其定位属于验证阶段(而非纯解码阶段)。
|
||||
|
||||
### 4.5.1 验签所需输入
|
||||
|
||||
- **CRL 对象本身**:包含 `tbsCertList`(待签名数据)、`signatureAlgorithm`、`signatureValue`。RFC 5280 §5.1。
|
||||
- **CRL issuer 的 CA 证书(或其公钥)**:用于提供验签公钥(`SubjectPublicKeyInfo`)。RFC 5280 §6.3.3 (f)-(g)。
|
||||
- (链路上下文)用于验证 issuer CA 证书链的同一信任锚(trust anchor):CRL issuer 的证书链必须与目标证书使用同一信任锚。RFC 5280 §6.3.3 (f)。
|
||||
|
||||
### 4.5.2 绑定与一致性校验(推荐最小集合)
|
||||
|
||||
在执行签名验签之前,RP 通常应进行下列绑定校验以确保“用的是正确的 issuer 证书/公钥”:
|
||||
|
||||
1. **Issuer DN 匹配**:`CRL.issuer_dn` 必须等于 issuer CA 证书的 `subject`。RFC 5280 §5.1(`issuer` 字段);RFC 5280 §6.3.3 (b)(“verify that the CRL issuer matches the certificate issuer” 的一般化要求)。
|
||||
2. **AKI ↔ SKI 绑定**:若 issuer CA 证书包含 `SubjectKeyIdentifier`(SKI,OID `2.5.29.14`),则其值应与 CRL 的 `AuthorityKeyIdentifier.keyIdentifier`(AKI,OID `2.5.29.35`)匹配。RFC 5280 §4.2.1.1;RFC 5280 §4.2.1.2;RFC 5280 §6.3.3 (c)(3)(delta 与 complete CRL 的 AKI 匹配规则;在非 delta 场景下同样用于选择正确的签发者公钥)。
|
||||
3. **KeyUsage 约束**:若 issuer CA 证书存在 `KeyUsage` 扩展(OID `2.5.29.15`),则必须包含 `cRLSign` 位。RFC 5280 §4.2.1.3;RFC 5280 §6.3.3 (f)。
|
||||
|
||||
> 注:SKI 的生成算法在 RFC 5280 中不强制限定(不同 CA 可能采用不同方式生成 keyIdentifier);因此一般做“字节值匹配”,而不对 SKI 值做“从公钥推导再比对”的强校验。
|
||||
|
||||
### 4.5.3 签名验签
|
||||
|
||||
完成上述绑定后,使用 issuer CA 证书提供的公钥对 CRL 签名进行验签:
|
||||
|
||||
- 使用 issuer 的 `SubjectPublicKeyInfo` 验证 `signatureValue` 是对 `tbsCertList` 的正确签名。RFC 5280 §6.3.3 (g)。
|
||||
- RPKI profile 限定签名算法为 `sha256WithRSAEncryption`(OID `1.2.840.113549.1.1.11`),并要求算法参数编码符合 X.509/PKIX 约束(常见为 absent 或 NULL)。RFC 6487 §5;RFC 7935 §2;RFC 5280 §4.1.1.2。
|
||||
429
src/data_model/crl.rs
Normal file
429
src/data_model/crl.rs
Normal file
@ -0,0 +1,429 @@
|
||||
use x509_parser::extensions::{AuthorityKeyIdentifier, ParsedExtension, X509Extension};
|
||||
use x509_parser::prelude::FromDer;
|
||||
use x509_parser::prelude::X509Version;
|
||||
use x509_parser::revocation_list::CertificateRevocationList;
|
||||
use x509_parser::asn1_rs::Tag;
|
||||
use x509_parser::certificate::X509Certificate;
|
||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||
use x509_parser::x509::AlgorithmIdentifier;
|
||||
|
||||
const OID_SHA256_WITH_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.11";
|
||||
const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
||||
const OID_CRL_NUMBER: &str = "2.5.29.20";
|
||||
const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Asn1TimeEncoding {
|
||||
UtcTime,
|
||||
GeneralizedTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Asn1TimeUtc {
|
||||
pub utc: time::OffsetDateTime,
|
||||
pub encoding: Asn1TimeEncoding,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BigUnsigned {
|
||||
/// Minimal big-endian bytes. For zero, this is `[0]`.
|
||||
pub bytes_be: Vec<u8>,
|
||||
}
|
||||
|
||||
impl BigUnsigned {
|
||||
pub fn to_hex_upper(&self) -> String {
|
||||
hex::encode_upper(&self.bytes_be)
|
||||
}
|
||||
|
||||
pub fn to_u64(&self) -> Option<u64> {
|
||||
if self.bytes_be.len() > 8 {
|
||||
return None;
|
||||
}
|
||||
let mut value: u64 = 0;
|
||||
for &b in &self.bytes_be {
|
||||
value = (value << 8) | (b as u64);
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RevokedCert {
|
||||
pub serial_number: BigUnsigned,
|
||||
pub revocation_date: Asn1TimeUtc,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CrlExtensions {
|
||||
pub authority_key_identifier: Vec<u8>,
|
||||
pub crl_number: BigUnsigned,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RpkixCrl {
|
||||
pub raw_der: Vec<u8>,
|
||||
pub version: u32,
|
||||
pub issuer_dn: String,
|
||||
pub signature_algorithm_oid: String,
|
||||
pub this_update: Asn1TimeUtc,
|
||||
pub next_update: Asn1TimeUtc,
|
||||
pub revoked_certs: Vec<RevokedCert>,
|
||||
pub extensions: CrlExtensions,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CrlDecodeError {
|
||||
#[error("X.509 CRL parse error: {0}")]
|
||||
Parse(String),
|
||||
|
||||
#[error("trailing bytes after CRL DER: {0} bytes")]
|
||||
TrailingBytes(usize),
|
||||
|
||||
#[error("CRL version must be v2, got {0:?}")]
|
||||
InvalidVersion(Option<u32>),
|
||||
|
||||
#[error("CRL signatureAlgorithm must be sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0}")]
|
||||
InvalidSignatureAlgorithm(String),
|
||||
|
||||
#[error("CRL signature algorithm parameters must be absent or NULL")]
|
||||
InvalidSignatureAlgorithmParameters,
|
||||
|
||||
#[error("CRL signatureAlgorithm must match TBSCertList.signature")]
|
||||
SignatureAlgorithmMismatch,
|
||||
|
||||
#[error("CRL extensions must be exactly two (AKI + CRLNumber), got {0}")]
|
||||
InvalidExtensionsCount(usize),
|
||||
|
||||
#[error("unsupported CRL extension OID {0}")]
|
||||
UnsupportedExtension(String),
|
||||
|
||||
#[error("duplicate CRL extension OID {0}")]
|
||||
DuplicateExtension(String),
|
||||
|
||||
#[error("AuthorityKeyIdentifier must contain keyIdentifier")]
|
||||
AkiMissingKeyIdentifier,
|
||||
|
||||
#[error("AuthorityKeyIdentifier must not contain authorityCertIssuer or authorityCertSerialNumber")]
|
||||
AkiHasOtherFields,
|
||||
|
||||
#[error("CRLNumber must be non-critical")]
|
||||
CrlNumberCritical,
|
||||
|
||||
#[error("CRLNumber out of range (must fit in 0..2^159-1)")]
|
||||
CrlNumberOutOfRange,
|
||||
|
||||
#[error("CRL entry extensions must not be present")]
|
||||
EntryExtensionsNotAllowed,
|
||||
|
||||
#[error("CRL nextUpdate must be present (RFC 5280 §5.1.2.5)")]
|
||||
NextUpdateMissing,
|
||||
|
||||
#[error("{field} time encoding invalid for year {year}: got {encoding:?}")]
|
||||
InvalidTimeEncoding {
|
||||
field: &'static str,
|
||||
year: i32,
|
||||
encoding: Asn1TimeEncoding,
|
||||
},
|
||||
}
|
||||
|
||||
impl RpkixCrl {
|
||||
/// Decode a DER-encoded X.509 v2 CRL and enforce the RPKI profile constraints from
|
||||
/// `specs/prepare/data_models/04_crl.md` (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1).
|
||||
pub fn decode_der(der: &[u8]) -> Result<Self, CrlDecodeError> {
|
||||
let (rem, crl) = CertificateRevocationList::from_der(der)
|
||||
.map_err(|e| CrlDecodeError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(CrlDecodeError::TrailingBytes(rem.len()));
|
||||
}
|
||||
|
||||
let version = match crl.version() {
|
||||
Some(X509Version::V2) => 2,
|
||||
Some(v) => return Err(CrlDecodeError::InvalidVersion(Some(v.0))),
|
||||
None => return Err(CrlDecodeError::InvalidVersion(None)),
|
||||
};
|
||||
|
||||
let sig_oid = crl.signature_algorithm.algorithm.to_id_string();
|
||||
let tbs_sig_oid = crl.tbs_cert_list.signature.algorithm.to_id_string();
|
||||
if sig_oid != tbs_sig_oid {
|
||||
return Err(CrlDecodeError::SignatureAlgorithmMismatch);
|
||||
}
|
||||
if sig_oid != OID_SHA256_WITH_RSA_ENCRYPTION {
|
||||
return Err(CrlDecodeError::InvalidSignatureAlgorithm(sig_oid));
|
||||
}
|
||||
validate_sig_params(&crl.signature_algorithm)?;
|
||||
validate_sig_params(&crl.tbs_cert_list.signature)?;
|
||||
|
||||
let extensions = parse_and_validate_extensions(crl.extensions())?;
|
||||
|
||||
let revoked_certs = crl
|
||||
.iter_revoked_certificates()
|
||||
.map(|rc| {
|
||||
if !rc.extensions().is_empty() {
|
||||
return Err(CrlDecodeError::EntryExtensionsNotAllowed);
|
||||
}
|
||||
let revocation_date = asn1_time_to_model(rc.revocation_date);
|
||||
validate_time_encoding("revocationDate", &revocation_date)?;
|
||||
Ok(RevokedCert {
|
||||
serial_number: biguint_to_big_unsigned(rc.serial()),
|
||||
revocation_date,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let this_update = asn1_time_to_model(crl.last_update());
|
||||
validate_time_encoding("thisUpdate", &this_update)?;
|
||||
|
||||
let next_update = crl
|
||||
.next_update()
|
||||
.map(asn1_time_to_model)
|
||||
.ok_or(CrlDecodeError::NextUpdateMissing)?;
|
||||
validate_time_encoding("nextUpdate", &next_update)?;
|
||||
|
||||
Ok(RpkixCrl {
|
||||
raw_der: der.to_vec(),
|
||||
version,
|
||||
issuer_dn: crl.issuer().to_string(),
|
||||
signature_algorithm_oid: OID_SHA256_WITH_RSA_ENCRYPTION.to_string(),
|
||||
this_update,
|
||||
next_update,
|
||||
revoked_certs,
|
||||
extensions,
|
||||
})
|
||||
}
|
||||
|
||||
/// Verify the cryptographic signature on this CRL using the issuer certificate.
|
||||
///
|
||||
/// Signature verification needs the issuer public key (RFC 5280 §6.3.3 (f)-(g)).
|
||||
/// In RPKI practice, this public key is obtained from the CRL issuer CA certificate
|
||||
/// (and that certificate must already be validated up to the same trust anchor).
|
||||
///
|
||||
/// This helper also performs common binding checks:
|
||||
/// - CRL `issuer_dn` must equal issuer certificate `subject`
|
||||
/// - if issuer KeyUsage is present, require `cRLSign`
|
||||
/// - if issuer SKI is present, require it matches CRL AKI.keyIdentifier
|
||||
pub fn verify_signature_with_issuer_certificate_der(
|
||||
&self,
|
||||
issuer_cert_der: &[u8],
|
||||
) -> Result<(), CrlVerifyError> {
|
||||
let (rem, issuer_cert) = X509Certificate::from_der(issuer_cert_der)
|
||||
.map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(CrlVerifyError::IssuerCertificateTrailingBytes(rem.len()));
|
||||
}
|
||||
|
||||
let subject_dn = issuer_cert.subject().to_string();
|
||||
if subject_dn != self.issuer_dn {
|
||||
return Err(CrlVerifyError::IssuerSubjectMismatch {
|
||||
crl_issuer_dn: self.issuer_dn.clone(),
|
||||
issuer_subject_dn: subject_dn,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(ku) = issuer_cert
|
||||
.key_usage()
|
||||
.map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))?
|
||||
{
|
||||
if !ku.value.crl_sign() {
|
||||
return Err(CrlVerifyError::IssuerKeyUsageMissingCrlSign);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(issuer_ski) = get_subject_key_identifier(&issuer_cert) {
|
||||
if issuer_ski != self.extensions.authority_key_identifier {
|
||||
return Err(CrlVerifyError::AkiSkiMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
self.verify_signature_with_issuer_spki(issuer_cert.public_key())
|
||||
}
|
||||
|
||||
/// Verify the cryptographic signature on this CRL using the issuer SubjectPublicKeyInfo.
|
||||
pub fn verify_signature_with_issuer_spki(
|
||||
&self,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
) -> Result<(), CrlVerifyError> {
|
||||
let (rem, crl) = CertificateRevocationList::from_der(&self.raw_der)
|
||||
.map_err(|e| CrlVerifyError::CrlParse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(CrlVerifyError::CrlTrailingBytes(rem.len()));
|
||||
}
|
||||
crl.verify_signature(issuer_spki)
|
||||
.map_err(|e| CrlVerifyError::InvalidSignature(e.to_string()))
|
||||
}
|
||||
|
||||
/// Verify the cryptographic signature on this CRL using a DER-encoded SubjectPublicKeyInfo.
|
||||
pub fn verify_signature_with_issuer_spki_der(
|
||||
&self,
|
||||
issuer_spki_der: &[u8],
|
||||
) -> Result<(), CrlVerifyError> {
|
||||
let (rem, spki) = SubjectPublicKeyInfo::from_der(issuer_spki_der)
|
||||
.map_err(|e| CrlVerifyError::IssuerSpkiParse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(CrlVerifyError::IssuerSpkiTrailingBytes(rem.len()));
|
||||
}
|
||||
self.verify_signature_with_issuer_spki(&spki)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CrlVerifyError {
|
||||
#[error("issuer certificate parse error: {0}")]
|
||||
IssuerCertificateParse(String),
|
||||
|
||||
#[error("trailing bytes after issuer certificate DER: {0} bytes")]
|
||||
IssuerCertificateTrailingBytes(usize),
|
||||
|
||||
#[error("issuer SubjectPublicKeyInfo parse error: {0}")]
|
||||
IssuerSpkiParse(String),
|
||||
|
||||
#[error("trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes")]
|
||||
IssuerSpkiTrailingBytes(usize),
|
||||
|
||||
#[error("CRL parse error: {0}")]
|
||||
CrlParse(String),
|
||||
|
||||
#[error("trailing bytes after CRL DER: {0} bytes")]
|
||||
CrlTrailingBytes(usize),
|
||||
|
||||
#[error("CRL issuer DN does not match issuer certificate subject")]
|
||||
IssuerSubjectMismatch {
|
||||
crl_issuer_dn: String,
|
||||
issuer_subject_dn: String,
|
||||
},
|
||||
|
||||
#[error("issuer certificate keyUsage present but missing cRLSign")]
|
||||
IssuerKeyUsageMissingCrlSign,
|
||||
|
||||
#[error("CRL AKI.keyIdentifier does not match issuer certificate SKI")]
|
||||
AkiSkiMismatch,
|
||||
|
||||
#[error("CRL signature verification failed: {0}")]
|
||||
InvalidSignature(String),
|
||||
}
|
||||
|
||||
fn asn1_time_to_model(t: x509_parser::time::ASN1Time) -> Asn1TimeUtc {
|
||||
let encoding = if t.is_utctime() {
|
||||
Asn1TimeEncoding::UtcTime
|
||||
} else {
|
||||
Asn1TimeEncoding::GeneralizedTime
|
||||
};
|
||||
Asn1TimeUtc {
|
||||
utc: t.to_datetime(),
|
||||
encoding,
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_time_encoding(field: &'static str, t: &Asn1TimeUtc) -> Result<(), CrlDecodeError> {
|
||||
let year = t.utc.year();
|
||||
let expected = if year <= 2049 {
|
||||
Asn1TimeEncoding::UtcTime
|
||||
} else {
|
||||
Asn1TimeEncoding::GeneralizedTime
|
||||
};
|
||||
if t.encoding != expected {
|
||||
return Err(CrlDecodeError::InvalidTimeEncoding {
|
||||
field,
|
||||
year,
|
||||
encoding: t.encoding,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_sig_params(sig: &AlgorithmIdentifier<'_>) -> Result<(), CrlDecodeError> {
|
||||
match sig.parameters.as_ref() {
|
||||
None => Ok(()),
|
||||
Some(p) if p.tag() == Tag::Null => Ok(()),
|
||||
Some(_p) => Err(CrlDecodeError::InvalidSignatureAlgorithmParameters),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_and_validate_extensions(exts: &[X509Extension<'_>]) -> Result<CrlExtensions, CrlDecodeError> {
|
||||
if exts.len() != 2 {
|
||||
return Err(CrlDecodeError::InvalidExtensionsCount(exts.len()));
|
||||
}
|
||||
|
||||
let mut authority_key_identifier: Option<Vec<u8>> = None;
|
||||
let mut crl_number: Option<BigUnsigned> = None;
|
||||
|
||||
for ext in exts {
|
||||
let oid = ext.oid.to_id_string();
|
||||
match oid.as_str() {
|
||||
OID_AUTHORITY_KEY_IDENTIFIER => {
|
||||
if authority_key_identifier.is_some() {
|
||||
return Err(CrlDecodeError::DuplicateExtension(oid));
|
||||
}
|
||||
let aki = parse_aki(ext)?;
|
||||
authority_key_identifier = Some(aki);
|
||||
}
|
||||
OID_CRL_NUMBER => {
|
||||
if crl_number.is_some() {
|
||||
return Err(CrlDecodeError::DuplicateExtension(oid));
|
||||
}
|
||||
if ext.critical {
|
||||
return Err(CrlDecodeError::CrlNumberCritical);
|
||||
}
|
||||
let n = parse_crl_number(ext)?;
|
||||
if n.bits() > 159 {
|
||||
return Err(CrlDecodeError::CrlNumberOutOfRange);
|
||||
}
|
||||
crl_number = Some(biguint_to_big_unsigned(&n));
|
||||
}
|
||||
_ => return Err(CrlDecodeError::UnsupportedExtension(oid)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(CrlExtensions {
|
||||
authority_key_identifier: authority_key_identifier.unwrap(),
|
||||
crl_number: crl_number.unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_aki(ext: &X509Extension<'_>) -> Result<Vec<u8>, CrlDecodeError> {
|
||||
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
|
||||
return Err(CrlDecodeError::Parse("AKI extension parse failed".into()));
|
||||
};
|
||||
validate_aki_profile(aki)?;
|
||||
Ok(aki
|
||||
.key_identifier
|
||||
.as_ref()
|
||||
.ok_or(CrlDecodeError::AkiMissingKeyIdentifier)?
|
||||
.0
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
fn validate_aki_profile(aki: &AuthorityKeyIdentifier<'_>) -> Result<(), CrlDecodeError> {
|
||||
if aki.key_identifier.is_none() {
|
||||
return Err(CrlDecodeError::AkiMissingKeyIdentifier);
|
||||
}
|
||||
if aki.authority_cert_issuer.is_some() || aki.authority_cert_serial.is_some() {
|
||||
return Err(CrlDecodeError::AkiHasOtherFields);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_crl_number(ext: &X509Extension<'_>) -> Result<der_parser::num_bigint::BigUint, CrlDecodeError> {
|
||||
match ext.parsed_extension() {
|
||||
ParsedExtension::CRLNumber(n) => Ok(n.clone()),
|
||||
ParsedExtension::ParseError { error } => Err(CrlDecodeError::Parse(error.to_string())),
|
||||
_ => Err(CrlDecodeError::Parse("CRLNumber extension parse failed".into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn biguint_to_big_unsigned(n: &der_parser::num_bigint::BigUint) -> BigUnsigned {
|
||||
let mut bytes = n.to_bytes_be();
|
||||
if bytes.is_empty() {
|
||||
bytes.push(0);
|
||||
}
|
||||
BigUnsigned { bytes_be: bytes }
|
||||
}
|
||||
|
||||
fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option<Vec<u8>> {
|
||||
cert.extensions()
|
||||
.iter()
|
||||
.find(|ext| ext.oid.to_id_string() == OID_SUBJECT_KEY_IDENTIFIER)
|
||||
.and_then(|ext| match ext.parsed_extension() {
|
||||
ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
2
src/data_model/mod.rs
Normal file
2
src/data_model/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod crl;
|
||||
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod data_model;
|
||||
BIN
tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl
vendored
Normal file
BIN
tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl
vendored
Normal file
BIN
tests/fixtures/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142067.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142067.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142068.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142068.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142069.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142069.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142070.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142070.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142071.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142071.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142072.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142072.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142073.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142073.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142074.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142074.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142075.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142075.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142076.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142076.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142077.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142077.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142078.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142078.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142079.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142079.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142080.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142080.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142081.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142081.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142082.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142082.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142083.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142083.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142084.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142084.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142085.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142085.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142086.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142086.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142087.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142087.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142088.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142088.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142089.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142089.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142090.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142090.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142091.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142091.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142092.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142092.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142093.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142093.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142094.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142094.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142095.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142095.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142096.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142096.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142097.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142097.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142098.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142098.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142099.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142099.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142100.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142100.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142101.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142101.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142102.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142102.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142103.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142103.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142104.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142104.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142105.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142105.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142106.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142106.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142650.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142650.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142651.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142651.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142652.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142652.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142653.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142653.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142654.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142654.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142655.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142655.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142656.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142656.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142657.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142657.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142658.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142658.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142659.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS142659.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144698.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144698.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144699.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144699.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144700.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144700.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144701.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144701.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144702.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144702.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144703.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144703.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144704.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144704.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144705.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144705.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144706.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144706.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144707.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS144707.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS23910.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS23910.roa
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa
vendored
Normal file
BIN
tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa
vendored
Normal file
Binary file not shown.
68
tests/test_crl_decode.rs
Normal file
68
tests/test_crl_decode.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rpki::data_model::crl::RpkixCrl;
|
||||
use rpki::data_model::crl::Asn1TimeEncoding;
|
||||
|
||||
#[test]
|
||||
fn decode_and_validate_crl_fixture() {
|
||||
let path = PathBuf::from("tests/fixtures/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl");
|
||||
let der = std::fs::read(&path).expect("read CRL fixture");
|
||||
|
||||
let crl = RpkixCrl::decode_der(&der).expect("decode CRL");
|
||||
|
||||
assert_eq!(crl.version, 2);
|
||||
assert_eq!(crl.signature_algorithm_oid, "1.2.840.113549.1.1.11");
|
||||
assert_eq!(crl.this_update.encoding, Asn1TimeEncoding::UtcTime);
|
||||
assert_eq!(crl.next_update.encoding, Asn1TimeEncoding::UtcTime);
|
||||
assert_eq!(
|
||||
hex::encode_upper(&crl.extensions.authority_key_identifier),
|
||||
"05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA"
|
||||
);
|
||||
assert_eq!(crl.extensions.crl_number.bytes_be, vec![12]);
|
||||
assert!(crl.revoked_certs.is_empty());
|
||||
|
||||
println!("{crl:#?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crl_signature_verification_succeeds_with_issuer_cert() {
|
||||
let crl_der = std::fs::read(
|
||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||
)
|
||||
.expect("read CRL fixture");
|
||||
let issuer_cert_der = std::fs::read(
|
||||
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
||||
)
|
||||
.expect("read issuer certificate fixture");
|
||||
|
||||
let crl = RpkixCrl::decode_der(&crl_der).expect("decode CRL");
|
||||
crl.verify_signature_with_issuer_certificate_der(&issuer_cert_der)
|
||||
.expect("CRL signature must verify with issuer certificate");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_crl_with_revoked_entries() {
|
||||
let der =
|
||||
std::fs::read("tests/fixtures/0099DEAB073EFD74C250C0A382B25012B5082AEE.crl")
|
||||
.expect("read CRL fixture with revoked entries");
|
||||
|
||||
let crl = RpkixCrl::decode_der(&der).expect("decode CRL");
|
||||
|
||||
assert_eq!(crl.revoked_certs.len(), 21);
|
||||
for entry in &crl.revoked_certs {
|
||||
assert!(!entry.serial_number.bytes_be.is_empty());
|
||||
// 0 should be encoded as [0], otherwise no leading zero bytes.
|
||||
if entry.serial_number.bytes_be.len() > 1 {
|
||||
assert_ne!(entry.serial_number.bytes_be[0], 0);
|
||||
}
|
||||
let year = entry.revocation_date.utc.year();
|
||||
let expected = if year <= 2049 {
|
||||
Asn1TimeEncoding::UtcTime
|
||||
} else {
|
||||
Asn1TimeEncoding::GeneralizedTime
|
||||
};
|
||||
assert_eq!(entry.revocation_date.encoding, expected);
|
||||
}
|
||||
|
||||
println!("{crl:#?}");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user