Compare commits
2 Commits
dev_1.0_xu
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f5b7da921a | |||
| 421847d329 |
@ -9,3 +9,7 @@ hex = "0.4.3"
|
|||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
time = "0.3.45"
|
time = "0.3.45"
|
||||||
x509-parser = { version = "0.18.0", features = ["verify"] }
|
x509-parser = { version = "0.18.0", features = ["verify"] }
|
||||||
|
url = "2.5.8"
|
||||||
|
asn1-rs = "0.7.1"
|
||||||
|
asn1-rs-derive = "0.6.0"
|
||||||
|
asn1 = "0.23.0"
|
||||||
|
|||||||
36
specs/01_tal.md
Normal file
36
specs/01_tal.md
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# 01. Trust Anchor Locator (TAL)
|
||||||
|
|
||||||
|
## 1.1 对象定位
|
||||||
|
TAL是一个数据格式/配置文件,目的是告诉RP信任锚的公钥是什么,以及相关对象可以从哪里获取。
|
||||||
|
|
||||||
|
## 1.2 数据格式 (RFC 8630 §2.2)
|
||||||
|
TAL是一个配置文件,格式定义如下:
|
||||||
|
```
|
||||||
|
The TAL is an ordered sequence of:
|
||||||
|
1. an optional comment section consisting of one or more lines each starting with the "#" character, followed by human-readable informational UTF-8 text, conforming to the restrictions defined
|
||||||
|
in Section 2 of [RFC5198], and ending with a line break,
|
||||||
|
2. a URI section that is comprised of one or more ordered lines, each containing a TA URI, and ending with a line break,
|
||||||
|
3. a line break, and
|
||||||
|
4. a subjectPublicKeyInfo [RFC5280] in DER format [X.509], encoded in base64 (see Section 4 of [RFC4648]). To avoid long lines,
|
||||||
|
line breaks MAY be inserted into the base64-encoded string.
|
||||||
|
Note that line breaks in this file can use either "<CRLF>" or "<LF>".
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1.3 抽象数据模型
|
||||||
|
|
||||||
|
### 1.3.1 TAL
|
||||||
|
|
||||||
|
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|----------|-------------|-------------------------|--------------------------------------------|---------------|
|
||||||
|
| uris | Vec<TalUri> | 指向TA的URI列表 | 允许rsync和https协议。 | RFC 8630 §2.1 |
|
||||||
|
| comment | Vec<String> | 注释(可选) | | RFC 8630 §2.2 |
|
||||||
|
| spki_der | Vec<u8> | 原始的subjectPublicKeyInfo | x.509 SubjectPublicKeyInfo DER编码,再base64编码 | RFC 8630 §2.2 |
|
||||||
|
|
||||||
|
|
||||||
|
### 1.3.2 TalUri
|
||||||
|
|
||||||
|
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|-------|--------|---------|---------|---------------|
|
||||||
|
| Rsync | String | rsync地址 | | RFC 8630 §2.1 |
|
||||||
|
| Https | String | https地址 | | RFC 8630 §2.1 |
|
||||||
|
|
||||||
121
specs/02_ta.md
Normal file
121
specs/02_ta.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
# 02. Trust Anchor (TA)
|
||||||
|
|
||||||
|
## 2.1 对象定位
|
||||||
|
TA是一个自签名的CA证书。
|
||||||
|
|
||||||
|
## 2.2 原始载体与编码
|
||||||
|
|
||||||
|
- 载体:X.509 certificates.
|
||||||
|
- 编码:DER(遵循 RFC 5280 的 certificate 结构与字段语义,但受限于RFC 8630 §2.3)
|
||||||
|
|
||||||
|
|
||||||
|
## 2.3 抽象数据类型
|
||||||
|
|
||||||
|
### 2.3.1 TA
|
||||||
|
|
||||||
|
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|-------------------|------------------|---------------|---------|---------------|
|
||||||
|
| name | String | 标识该TA,如apnic等 | | |
|
||||||
|
| cert_der | Vec<u8> | 原始DER内容 | | |
|
||||||
|
| cert | X509Certificate | 基础X509证书 | | RFC 5280 §4.1 |
|
||||||
|
| resource | ResourceSet | 资源集合 | | |
|
||||||
|
| publication_point | Uri | 获取该TA的URI | | |
|
||||||
|
|
||||||
|
### 2.3.2 ResourceSet
|
||||||
|
资源集合是来自RFC 3779的IP地址块(§2)和AS号段(§3),受约束于RFC 8630 §2.3
|
||||||
|
|
||||||
|
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|------|----------------|--------|-------------|---------------------------|
|
||||||
|
| ips | IpResourceSet | IP地址集合 | 不能是inherit | RFC 3779 §2和RFC 8630 §2.3 |
|
||||||
|
| asns | AsnResourceSet | ASN集合 | 不能是inherit | RFC 3779 §3和RFC 8630 §2.3 |
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.3 IpResourceSet)
|
||||||
|
|
||||||
|
[//]: # (包括IPv4和IPv6的前缀表示)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|----|------------------------|----------|-------------|--------------|)
|
||||||
|
|
||||||
|
[//]: # (| v4 | PrefixSet<Ipv4Prefix> | IPv4前缀集合 | | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # (| v6 | PrefixSet<Ipv6Prefix> | IPv6前缀集合 | | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.4 AsnResourceSet)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|-------|--------------------|-------|-------------|-------------|)
|
||||||
|
|
||||||
|
[//]: # (| range | RangeSet<AsnBlock> | ASN集合 | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.5 Ipv4Prefix)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|------|-----|-----|---------|-------------|)
|
||||||
|
|
||||||
|
[//]: # (| addr | u32 | 地址 | | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # (| len | u8 | 长度 | 0-32 | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.6 Ipv6Prefix)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|------|------|-----|---------|-------------|)
|
||||||
|
|
||||||
|
[//]: # (| addr | u128 | 地址 | | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # (| len | u8 | 长度 | 0-128 | RFC 3779 §2 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.7 AsnBlock)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|----------|----------|-------|---------|--------------|)
|
||||||
|
|
||||||
|
[//]: # (| asn | Asn | ASN | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
[//]: # (| asnRange | AsnRange | ASN范围 | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.8 Asn)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|-----|-----|-----|---------|-------------|)
|
||||||
|
|
||||||
|
[//]: # (| asn | u32 | ASN | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (### 2.3.8 AsnRange)
|
||||||
|
|
||||||
|
[//]: # ()
|
||||||
|
[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |)
|
||||||
|
|
||||||
|
[//]: # (|-----|-----|-------|---------|--------------|)
|
||||||
|
|
||||||
|
[//]: # (| min | Asn | 最小ASN | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
[//]: # (| max | Asn | 最大ASN | | RFC 3779 §3 |)
|
||||||
|
|
||||||
|
# 2.4 TA校验流程(RFC 8630 §3)
|
||||||
|
1. 从TAL的URI列表中获取证书对象。(顺序访问,若前面失效,再访问后面的)
|
||||||
|
2. 验证证书格式,必须是当前、有效的自签名RPKI证书。
|
||||||
|
3. 验证公钥匹配。TAL中的SubjectPublicKeyInfo与下载证书的公钥一致。
|
||||||
|
4. 其他检查。
|
||||||
|
5. 更新本地存储库缓存。
|
||||||
314
specs/03_rc.md
Normal file
314
specs/03_rc.md
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# 03. RC (Resource Certifications)
|
||||||
|
|
||||||
|
## 3.1 对象定位
|
||||||
|
RC是资源证书,包括CA和EE
|
||||||
|
|
||||||
|
## 3.2 原始载体与编码
|
||||||
|
|
||||||
|
- 载体:X.509 certificates.
|
||||||
|
- 编码:DER(遵循 RFC 5280 的 Certificate 结构与字段语义,但受 RPKI profile 限制)RFC 6487 §4
|
||||||
|
|
||||||
|
### 3.2.1 基本语法(RFC 5280 §4,RFC 6487 )
|
||||||
|
|
||||||
|
RC是遵循RFC5280定义的X.509Certificate语法(RFC 5280 §4),并且符合RFC 6487 §4的约束。只选取RFC 6487 §4章节列出来的字段。(Unless specifically noted as being OPTIONAL, all the fields listed
|
||||||
|
here MUST be present, and any other fields MUST NOT appear in a
|
||||||
|
conforming resource certificate.)
|
||||||
|
|
||||||
|
```
|
||||||
|
Certificate ::= SEQUENCE {
|
||||||
|
tbsCertificate TBSCertificate,
|
||||||
|
signatureAlgorithm AlgorithmIdentifier,
|
||||||
|
signatureValue BIT STRING
|
||||||
|
}
|
||||||
|
|
||||||
|
TBSCertificate ::= SEQUENCE {
|
||||||
|
version [0] EXPLICIT Version MUST be v3,
|
||||||
|
serialNumber CertificateSerialNumber,
|
||||||
|
signature AlgorithmIdentifier,
|
||||||
|
issuer Name,
|
||||||
|
subject Name,
|
||||||
|
validity Validity,
|
||||||
|
subjectPublicKeyInfo SubjectPublicKeyInfo,
|
||||||
|
extensions [3] EXPLICIT Extensions OPTIONAL
|
||||||
|
-- If present, version MUST be v3
|
||||||
|
}
|
||||||
|
|
||||||
|
Version ::= INTEGER { v1(0), v2(1), v3(2) }
|
||||||
|
|
||||||
|
CertificateSerialNumber ::= INTEGER
|
||||||
|
|
||||||
|
Validity ::= SEQUENCE {
|
||||||
|
notBefore Time,
|
||||||
|
notAfter Time }
|
||||||
|
|
||||||
|
Time ::= CHOICE {
|
||||||
|
utcTime UTCTime,
|
||||||
|
generalTime GeneralizedTime }
|
||||||
|
|
||||||
|
UniqueIdentifier ::= BIT STRING
|
||||||
|
|
||||||
|
SubjectPublicKeyInfo ::= SEQUENCE {
|
||||||
|
algorithm AlgorithmIdentifier,
|
||||||
|
subjectPublicKey BIT STRING }
|
||||||
|
|
||||||
|
Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 其中`Name` "a valid X.501 distinguished name"(RFC 6487 §4.4)
|
||||||
|
|
||||||
|
### 3.2.2 证书扩展字段 (RFC 6487 §4.8)
|
||||||
|
|
||||||
|
RC的证书扩展字段按照RFC 6487 §4.8的规定,有以下几个扩展:
|
||||||
|
|
||||||
|
- Basic Constraints
|
||||||
|
- Subject Key Identifier
|
||||||
|
- Authority Key Identifier
|
||||||
|
- Key Usage
|
||||||
|
- Extended Key Usage(CA证书,以及验证RPKI对象的EE证书不能出现该字段。非RPKI对象的EE可以出现EKU,但必须为non-critical)
|
||||||
|
- CRL Distribution Points
|
||||||
|
- Authority Information Access
|
||||||
|
- Subject Information Access
|
||||||
|
- SIA for CA Certificates
|
||||||
|
- SIA for EE Certificates
|
||||||
|
- Certificate Policies
|
||||||
|
- IP Resources
|
||||||
|
- AS Resources
|
||||||
|
|
||||||
|
```
|
||||||
|
# Basic Constraints
|
||||||
|
id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
|
||||||
|
|
||||||
|
BasicConstraints ::= SEQUENCE {
|
||||||
|
cA BOOLEAN DEFAULT FALSE }
|
||||||
|
|
||||||
|
|
||||||
|
# Subject Key Identifier
|
||||||
|
id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 }
|
||||||
|
|
||||||
|
SubjectKeyIdentifier ::= KeyIdentifier
|
||||||
|
|
||||||
|
KeyIdentifier ::= OCTET STRING
|
||||||
|
|
||||||
|
|
||||||
|
# Authority Key Identifier
|
||||||
|
id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
|
||||||
|
|
||||||
|
AuthorityKeyIdentifier ::= SEQUENCE {
|
||||||
|
keyIdentifier [0] KeyIdentifier OPTIONAL }
|
||||||
|
|
||||||
|
|
||||||
|
# Key Usage
|
||||||
|
id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
|
||||||
|
|
||||||
|
KeyUsage ::= BIT STRING {
|
||||||
|
digitalSignature (0),
|
||||||
|
nonRepudiation (1), -- recent editions of X.509 have
|
||||||
|
-- renamed this bit to contentCommitment
|
||||||
|
keyEncipherment (2),
|
||||||
|
dataEncipherment (3),
|
||||||
|
keyAgreement (4),
|
||||||
|
keyCertSign (5),
|
||||||
|
cRLSign (6),
|
||||||
|
encipherOnly (7),
|
||||||
|
decipherOnly (8) }
|
||||||
|
|
||||||
|
|
||||||
|
# Extended Key Usage
|
||||||
|
id-ce-extKeyUsage OBJECT IDENTIFIER ::= { id-ce 37 }
|
||||||
|
|
||||||
|
ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
|
||||||
|
|
||||||
|
KeyPurposeId ::= OBJECT IDENTIFIER
|
||||||
|
|
||||||
|
|
||||||
|
# CRL Distribution Points
|
||||||
|
id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= { id-ce 31 }
|
||||||
|
|
||||||
|
CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
|
||||||
|
|
||||||
|
DistributionPoint ::= SEQUENCE {
|
||||||
|
distributionPoint [0] DistributionPointName OPTIONAL }
|
||||||
|
|
||||||
|
DistributionPointName ::= CHOICE {
|
||||||
|
fullName [0] GeneralNames }
|
||||||
|
|
||||||
|
|
||||||
|
## Authority Information Access
|
||||||
|
id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
|
||||||
|
|
||||||
|
AuthorityInfoAccessSyntax ::=
|
||||||
|
SEQUENCE SIZE (1..MAX) OF AccessDescription
|
||||||
|
|
||||||
|
AccessDescription ::= SEQUENCE {
|
||||||
|
accessMethod OBJECT IDENTIFIER,
|
||||||
|
accessLocation GeneralName }
|
||||||
|
|
||||||
|
# AccessDescription
|
||||||
|
id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
|
||||||
|
# CA 证书发布位置
|
||||||
|
id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
|
||||||
|
# OCSP 服务地址
|
||||||
|
id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
|
||||||
|
|
||||||
|
|
||||||
|
# Subject Information Access
|
||||||
|
id-pe-subjectInfoAccess OBJECT IDENTIFIER ::= { id-pe 11 }
|
||||||
|
|
||||||
|
SubjectInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF AccessDescription
|
||||||
|
AccessDescription ::= SEQUENCE {
|
||||||
|
accessMethod OBJECT IDENTIFIER,
|
||||||
|
accessLocation GeneralName }
|
||||||
|
|
||||||
|
## Subject Information Access for CA (RFC 6487 §4.8.8.1)
|
||||||
|
id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
|
||||||
|
id-ad-rpkiManifest OBJECT IDENTIFIER ::= { id-ad 10 }
|
||||||
|
|
||||||
|
必须存在一个accessMethod=id-ad-caRepository,accessLocation=rsyncURI。
|
||||||
|
必须存在一个accessMethod=id-ad-repiManifest, accessLocation=rsync URI,指向该CA的mft对象。
|
||||||
|
|
||||||
|
## Subject Information Access for EE (RFC 6487 §4.8.8.2)
|
||||||
|
id-ad-signedObject OBJECT IDENTIFIER ::= { id-ad 11 }
|
||||||
|
|
||||||
|
必须存在一个accessMethod=id-ad-signedObject, accessLocation=rsyncURI
|
||||||
|
不允许其他的accessMethod
|
||||||
|
|
||||||
|
|
||||||
|
# Certificate Policies
|
||||||
|
id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
|
||||||
|
anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
|
||||||
|
|
||||||
|
certificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
|
||||||
|
|
||||||
|
PolicyInformation ::= SEQUENCE {
|
||||||
|
policyIdentifier CertPolicyId,
|
||||||
|
policyQualifiers SEQUENCE SIZE (1..MAX) OF PolicyQualifierInfo OPTIONAL }
|
||||||
|
|
||||||
|
CertPolicyId ::= OBJECT IDENTIFIER
|
||||||
|
|
||||||
|
PolicyQualifierInfo ::= SEQUENCE {
|
||||||
|
policyQualifierId PolicyQualifierId,
|
||||||
|
qualifier ANY DEFINED BY policyQualifierId }
|
||||||
|
|
||||||
|
-- policyQualifierIds for Internet policy qualifiers
|
||||||
|
id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
|
||||||
|
id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
|
||||||
|
id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
|
||||||
|
|
||||||
|
PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
|
||||||
|
|
||||||
|
Qualifier ::= CHOICE {
|
||||||
|
cPSuri CPSuri,
|
||||||
|
userNotice UserNotice }
|
||||||
|
|
||||||
|
CPSuri ::= IA5String
|
||||||
|
|
||||||
|
UserNotice ::= SEQUENCE {
|
||||||
|
noticeRef NoticeReference OPTIONAL,
|
||||||
|
explicitText DisplayText OPTIONAL }
|
||||||
|
|
||||||
|
NoticeReference ::= SEQUENCE {
|
||||||
|
organization DisplayText,
|
||||||
|
noticeNumbers SEQUENCE OF INTEGER }
|
||||||
|
|
||||||
|
DisplayText ::= CHOICE {
|
||||||
|
ia5String IA5String (SIZE (1..200)),
|
||||||
|
visibleString VisibleString (SIZE (1..200)),
|
||||||
|
bmpString BMPString (SIZE (1..200)),
|
||||||
|
utf8String UTF8String (SIZE (1..200)) }
|
||||||
|
|
||||||
|
|
||||||
|
# IP Resources
|
||||||
|
id-pe-ipAddrBlocks OBJECT IDENTIFIER ::= { id-pe 7 }
|
||||||
|
|
||||||
|
IPAddrBlocks ::= SEQUENCE OF IPAddressFamily
|
||||||
|
|
||||||
|
IPAddressFamily ::= SEQUENCE { -- AFI & optional SAFI --
|
||||||
|
addressFamily OCTET STRING (SIZE (2..3)),
|
||||||
|
ipAddressChoice IPAddressChoice }
|
||||||
|
|
||||||
|
IPAddressChoice ::= CHOICE {
|
||||||
|
inherit NULL, -- inherit from issuer --
|
||||||
|
addressesOrRanges SEQUENCE OF IPAddressOrRange }
|
||||||
|
|
||||||
|
IPAddressOrRange ::= CHOICE {
|
||||||
|
addressPrefix IPAddress,
|
||||||
|
addressRange IPAddressRange }
|
||||||
|
|
||||||
|
IPAddressRange ::= SEQUENCE {
|
||||||
|
min IPAddress,
|
||||||
|
max IPAddress }
|
||||||
|
|
||||||
|
IPAddress ::= BIT STRING
|
||||||
|
|
||||||
|
|
||||||
|
# AS Resources
|
||||||
|
id-pe-autonomousSysIds OBJECT IDENTIFIER ::= { id-pe 8 }
|
||||||
|
ASIdentifiers ::= SEQUENCE {
|
||||||
|
asnum [0] EXPLICIT ASIdentifierChoice OPTIONAL,
|
||||||
|
rdi [1] EXPLICIT ASIdentifierChoice OPTIONAL}
|
||||||
|
|
||||||
|
ASIdentifierChoice ::= CHOICE {
|
||||||
|
inherit NULL, -- inherit from issuer --
|
||||||
|
asIdsOrRanges SEQUENCE OF ASIdOrRange }
|
||||||
|
|
||||||
|
ASIdOrRange ::= CHOICE {
|
||||||
|
id ASId,
|
||||||
|
range ASRange }
|
||||||
|
|
||||||
|
ASRange ::= SEQUENCE {
|
||||||
|
min ASId,
|
||||||
|
max ASId }
|
||||||
|
|
||||||
|
ASId ::= INTEGER
|
||||||
|
```
|
||||||
|
|
||||||
|
# 3.3 抽象数据结构
|
||||||
|
采用X509 Certificate + Resource + 约束校验的方式组合
|
||||||
|
|
||||||
|
| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|----------|---------------------|----------|---------|---------------|
|
||||||
|
| cert_der | Vec<u8> | 证书原始数据 | | |
|
||||||
|
| cert | X509Certificate | 基础X509证书 | | RFC 5280 §4.1 |
|
||||||
|
| resource | ResourceSet | 资源集合 | | |
|
||||||
|
|
||||||
|
|
||||||
|
# 3.4 约束规则
|
||||||
|
|
||||||
|
## 3.4.1 Cert约束校验规则
|
||||||
|
RFC 6487中规定的证书的字段参见[3.2.1 ](#321-基本语法rfc-5280-4rfc-6487-)
|
||||||
|
-
|
||||||
|
|
||||||
|
| 字段 | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|-----------|-------|----------------------------------------------|--------------|
|
||||||
|
| version | 证书版本 | 必须是v3(值为2) | RFC6487 §4.1 |
|
||||||
|
| serial | 证书编号 | 同一个CA签发的证书编号必须唯一 | RFC6487 §4.2 |
|
||||||
|
| validity | 证书有效期 | notBefore:时间不能早于证书的生成时间。若时间段大于上级证书的有效期,也是有效的 | RFC6487 §4.6 |
|
||||||
|
|
||||||
|
|
||||||
|
## 3.4.2 Cert Extentions中字段的约束校验规则
|
||||||
|
RFC 6487中规定的扩展字段参见[3.2.2 ](#322-证书扩展字段-rfc-6487-48)
|
||||||
|
|
||||||
|
| 字段 | critical | 语义 | 约束/解析规则 | RFC 引用 |
|
||||||
|
|----------------------------|----------|-------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
|
||||||
|
| basicConstraints | Y | 证书类型 | CA证书:cA=TRUE; EE证书:cA=FALSE | RFC6487 §4.8.1 |
|
||||||
|
| subjectKeyIdentifier | N | 证书公钥 | SKI = SHA-1(DER-encoded SPKI bit string) | RFC6487 §4.8.2 |
|
||||||
|
| authorityKeyIdentifier | N | 父证书的公钥 | 字段只包含keyIdentifier,不能包含authorityCertIssuer和authorityCertSerialNumber;除了自签名CA外,其余证书必须出现。自签名CA若出现该字段,则等于SKI | RFC6487 §4.8.3 |
|
||||||
|
| keyUsage | Y | 证书公钥的用途权限 | CA证书:keyCertSign = TRUE, cRLSign = TRUE 其他都是FALSE。EE证书:digitalSignature = TRUE 其他都是FALSE | RFC6487 §4.8.4 |
|
||||||
|
| extendedKeyUsage | N | 扩展证书公钥的用途权限 | CA证书:不能出现EKU;验证 RPKI 对象的 EE 证书:不能出现EKU;非 RPKI 对象的 EE:可以出现EKU,但必须为non-critical. | RFC6487 §4.8.5 |
|
||||||
|
| cRLDistributionPoints | N | CRL的发布点位置 | 字段:distributionPoint,不能包含reasons、cRLIssuer。其中distributionPoint字段包含:fullName,不能包含nameRelativeToCRLIssuer。fullName的格式必须是URI。自签名证书禁止出现该字段。非自签名证书必须出现。一个CA只能有一个CRL。一个CRLDP只能包含一个distributionPoint。但一个distributionPoint字段中可以包含多于1个的URI,但必须包含rsync URI且必须是最新的。 | RFC6487 §4.8.6 |
|
||||||
|
| authorityInformationAccess | N | 签发者的发布点位置 | 除了自签名的CA,必须出现。自签名CA,禁止出现。推荐的URI访问方式是rsync,并且rsyncURI的话,必须指定accessMethod=id-ad-caIssuers | RFC6487 §4.8.7 |
|
||||||
|
| subjectInformationAccess | N | 发布点位置 | CA证书:必须存在。必须存在一个accessMethod=id-ad-caRepository,accessLocation=rsyncURI。必须存在一个accessMethod=id-ad-repiManifest,accessLocation=rsync URI,指向该CA的mft对象。 EE证书:必须存在。必须存在一个accessMethod=id-ad-signedObject,accessLocation=rsyncURI。不允许其他的accessMethod | RFC6487 §4.8.8 |
|
||||||
|
| certificatePolicies | Y | 证书策略 | 必须存在,并且只能存在一种策略:RFC 6484 — RPKI Certificate Policy (CP) | RFC6487 §4.8.9 |
|
||||||
|
| iPResources | Y | IP地址集合 | 所有的RPKI证书中必须包含IP Resources或者ASResources,或者两者都包含。 | RFC6487 §4.8.10 |
|
||||||
|
| aSResources | Y | ASN集合 | 所有的RPKI证书中必须包含IP Resources或者ASResources,或者两者都包含。 | RFC6487 §4.8.11 |
|
||||||
|
|
||||||
|
|
||||||
@ -1,2 +1,6 @@
|
|||||||
pub mod crl;
|
pub mod crl;
|
||||||
|
mod rc;
|
||||||
|
mod tal;
|
||||||
|
mod ta;
|
||||||
|
mod resources;
|
||||||
|
mod oids;
|
||||||
|
|||||||
16
src/data_model/oids.rs
Normal file
16
src/data_model/oids.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
pub const OID_BASIC_CONSTRAINTS: &str = "2.5.29.19";
|
||||||
|
pub const OID_SUBJECT_KEY_IDENTIFIER: &str = "2.5.29.14";
|
||||||
|
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
||||||
|
pub const OID_KEY_USAGE: &str = "2.5.29.15";
|
||||||
|
pub const OID_EXTENDED_KEY_USAGE: &str = "2.5.29.37";
|
||||||
|
pub const OID_CRL_DISTRIBUTION_POINTS: &str = "2.5.29.31";
|
||||||
|
pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
|
||||||
|
pub const OID_ACCESS_DESCRIPTION: &str = "1.3.6.1.5.5.7.48";
|
||||||
|
pub const OID_AD_CA_ISSUERS: &str = "1.3.6.1.5.5.7.48.2";
|
||||||
|
pub const OID_AD_OCSP: &str = "1.3.6.1.5.5.7.48.1";
|
||||||
|
pub const OID_SUBJECT_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.11";
|
||||||
|
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
|
||||||
|
pub const OID_SHA256_WITH_RSA_ENCRYPTION: &str = "1.2.840.113549.1.1.11";
|
||||||
|
pub const OID_IP_ADDRESS_BLOCKS: &str = "1.3.6.1.5.5.7.1.7";
|
||||||
|
pub const OID_AS_IDENTIFIERS: &str = "1.3.6.1.5.5.7.1.8";
|
||||||
|
pub const OID_RPKI_CP: &str = "1.3.6.1.5.5.7.14.2";
|
||||||
639
src/data_model/rc.rs
Normal file
639
src/data_model/rc.rs
Normal file
@ -0,0 +1,639 @@
|
|||||||
|
use asn1::{parse, BitString};
|
||||||
|
use der_parser::asn1_rs::Tag;
|
||||||
|
use der_parser::num_bigint::BigUint;
|
||||||
|
use url::Url;
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use x509_parser::x509::AlgorithmIdentifier;
|
||||||
|
use x509_parser::prelude::{Validity, KeyUsage, X509Certificate, FromDer,
|
||||||
|
X509Version, X509Extension, ParsedExtension,
|
||||||
|
DistributionPointName, GeneralName};
|
||||||
|
use crate::data_model::crl::CrlDecodeError;
|
||||||
|
use crate::data_model::resources::ip_resources::{Afi, IPAddrBlocks, IPAddress, IPAddressChoice,
|
||||||
|
IPAddressOrRange, IPAddressPrefix, IPAddressRange,
|
||||||
|
IPAddressFamily};
|
||||||
|
use crate::data_model::resources::as_resources::ASIdentifiers;
|
||||||
|
use crate::data_model::oids;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct SubjectPublicKeyInfo {
|
||||||
|
pub algorithm_oid: String,
|
||||||
|
pub subject_public_key: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct AccessDescription {
|
||||||
|
pub access_method_oid: String,
|
||||||
|
pub access_location: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct PolicyInformation {
|
||||||
|
pub policy_oid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct RcExtension {
|
||||||
|
pub basic_constraints: bool,
|
||||||
|
pub subject_key_identifier: u8,
|
||||||
|
pub authority_key_identifier: u8,
|
||||||
|
pub key_usage: KeyUsage,
|
||||||
|
pub extended_key_usage_oid: u8,
|
||||||
|
pub crl_distribution_points: Vec<Url>,
|
||||||
|
pub authority_info_access: Vec<AccessDescription>,
|
||||||
|
pub subject_info_access: Vec<AccessDescription>,
|
||||||
|
pub certificate_policies: Vec<PolicyInformation>,
|
||||||
|
pub ip_resource: IPAddrBlocks,
|
||||||
|
pub as_resource: ASIdentifiers,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ResourceCert {
|
||||||
|
/// 证书原始DER内容
|
||||||
|
pub cert_der: Vec<u8>,
|
||||||
|
|
||||||
|
/// 基本证书信息
|
||||||
|
pub version: u32,
|
||||||
|
pub serial_number: BigUint,
|
||||||
|
pub signature_algorithm_oid: String,
|
||||||
|
pub issuer_dn: String,
|
||||||
|
pub subject_dn: String,
|
||||||
|
pub validity: Validity,
|
||||||
|
pub subject_public_key_info: SubjectPublicKeyInfo,
|
||||||
|
pub extensions: RcExtension,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum ResourceCertError {
|
||||||
|
#[error("X.509 parse resource cert error: {0}")]
|
||||||
|
ParseCert(String),
|
||||||
|
|
||||||
|
#[error("trailing bytes after CRL DER: {0} bytes")]
|
||||||
|
TrailingBytes(usize),
|
||||||
|
|
||||||
|
#[error("invalid version {0}")]
|
||||||
|
InvalidVersion(u32),
|
||||||
|
|
||||||
|
#[error("signatureAlgorithm does not match tbsCertificate.signature")]
|
||||||
|
SignatureAlgorithmMismatch,
|
||||||
|
|
||||||
|
#[error("unsupported signature algorithm")]
|
||||||
|
UnsupportedSignatureAlgorithm,
|
||||||
|
|
||||||
|
#[error("invalid Cert signature algorithm parameters")]
|
||||||
|
InvalidSignatureParameters,
|
||||||
|
|
||||||
|
#[error("invalid Cert validity range")]
|
||||||
|
InvalidValidityRange,
|
||||||
|
|
||||||
|
#[error("Cert not yet valid")]
|
||||||
|
NotYetValid,
|
||||||
|
|
||||||
|
#[error("expired")]
|
||||||
|
Expired,
|
||||||
|
|
||||||
|
#[error("Critical error, {0} should be {1}")]
|
||||||
|
CriticalError(String, String),
|
||||||
|
|
||||||
|
#[error("Duplicate Extension: {0}")]
|
||||||
|
DuplicateExtension(String),
|
||||||
|
|
||||||
|
#[error("AKI missing keyIdentifier")]
|
||||||
|
AkiMissingKeyIdentifier,
|
||||||
|
|
||||||
|
#[error("Unexpected parameter: {0}")]
|
||||||
|
UnexceptedParameter(String),
|
||||||
|
|
||||||
|
#[error("Missing parameter: {0}")]
|
||||||
|
MissingParameter(String),
|
||||||
|
|
||||||
|
#[error("CRL DP invalid distributionPointName: {0}")]
|
||||||
|
CrlDpInvalidDistributionPointName(String),
|
||||||
|
|
||||||
|
#[error("CRL DP unexpected distributionPointType: {0}")]
|
||||||
|
CrlDpUnexpectedDistributionPointType(String),
|
||||||
|
|
||||||
|
#[error("invalid URI: {0}")]
|
||||||
|
InvalidUri(String),
|
||||||
|
|
||||||
|
#[error("Unsupported General Name in {0}")]
|
||||||
|
UnsupportedGeneralName(String),
|
||||||
|
|
||||||
|
#[error("Unsupported CRL Distribution Point")]
|
||||||
|
UnsupportedCrlDistributionPoint,
|
||||||
|
|
||||||
|
#[error("Invalid Access Location Type")]
|
||||||
|
InvalidAccessLocationType,
|
||||||
|
|
||||||
|
#[error("Empty AuthorityInfoAccess!")]
|
||||||
|
EmptyAuthorityInfoAccess,
|
||||||
|
|
||||||
|
#[error("Certificate Policies must exists one policy")]
|
||||||
|
CertificatePoliciesTooMany,
|
||||||
|
|
||||||
|
#[error("Certificate Policies invalid")]
|
||||||
|
CertificatePoliciesInvalid,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResourceCert{
|
||||||
|
pub fn from_der(cert_der: &[u8]) -> Result<Self, ResourceCertError> {
|
||||||
|
let (rem, x509_rc) = X509Certificate::from_der(cert_der)
|
||||||
|
.map_err(|e| ResourceCertError::ParseCert(e.to_string()))?;
|
||||||
|
|
||||||
|
if !rem.is_empty() {
|
||||||
|
return Err(ResourceCertError::TrailingBytes(rem.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验
|
||||||
|
parse_and_validate_cert(x509_rc)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_and_validate_cert(x509_rc: X509Certificate) -> Result<ResourceCert, ResourceCertError> {
|
||||||
|
///逐个校验RC的内容, 如果有任何一个校验失败, 则返回错误
|
||||||
|
|
||||||
|
// 1. 版本号必须是V3
|
||||||
|
let version = match x509_rc.version() {
|
||||||
|
X509Version::V3 => X509Version::V3,
|
||||||
|
v => {
|
||||||
|
return Err(ResourceCertError::InvalidVersion(v.0));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2.校验签名算法
|
||||||
|
// 2.1. 校验外层的签名算法与里层的一致
|
||||||
|
let outer = &x509_rc.signature_algorithm;
|
||||||
|
let inner = &x509_rc.tbs_certificate.signature;
|
||||||
|
|
||||||
|
if outer.algorithm != inner.algorithm || outer.parameters != inner.parameters {
|
||||||
|
return Err(ResourceCertError::SignatureAlgorithmMismatch);
|
||||||
|
}
|
||||||
|
//2.2 RPKI的签名算法必须是rsaWithSHA256
|
||||||
|
let signature_algorithm = &x509_rc.signature_algorithm;
|
||||||
|
if signature_algorithm.algorithm.to_id_string() != oids::OID_SHA256_WITH_RSA_ENCRYPTION {
|
||||||
|
return Err(ResourceCertError::UnsupportedSignatureAlgorithm);
|
||||||
|
}
|
||||||
|
validate_sig_params(signature_algorithm)?;
|
||||||
|
|
||||||
|
// 3. 校验Validity
|
||||||
|
let validity = x509_rc.validity();
|
||||||
|
validate_validity(validity, OffsetDateTime::now_utc())?;
|
||||||
|
|
||||||
|
// 4. SubjectPublicKeyInfo
|
||||||
|
let subject_public_key_info = x509_rc.tbs_certificate.subject_pki;
|
||||||
|
|
||||||
|
let extensions = parse_and_validate_extensions(x509_rc.extensions())?;
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
Ok(ResourceCert {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 validate_validity(
|
||||||
|
validity: &Validity,
|
||||||
|
now: OffsetDateTime,
|
||||||
|
) -> Result<(), ResourceCertError> {
|
||||||
|
let not_before = validity.not_before.to_datetime();
|
||||||
|
let not_after = validity.not_after.to_datetime();
|
||||||
|
|
||||||
|
if not_after < not_before {
|
||||||
|
return Err(ResourceCertError::InvalidValidityRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if now < not_before {
|
||||||
|
return Err(ResourceCertError::NotYetValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if now > not_after {
|
||||||
|
return Err(ResourceCertError::Expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn parse_and_validate_extensions(
|
||||||
|
exts: &[X509Extension<'_>],
|
||||||
|
) -> Result<RcExtension, ResourceCertError> {
|
||||||
|
let mut basic_constraints = None;
|
||||||
|
let mut ip_addr_blocks = None;
|
||||||
|
let mut as_identifiers = None;
|
||||||
|
let mut ski = None;
|
||||||
|
let mut aki = None;
|
||||||
|
let mut crl_dp = None;
|
||||||
|
let mut aia = None;
|
||||||
|
let mut sia = None;
|
||||||
|
let mut key_usage = None;
|
||||||
|
let mut extended_key_usage = None;
|
||||||
|
let mut certificate_policies = None;
|
||||||
|
|
||||||
|
for ext in exts {
|
||||||
|
let oid = ext.oid.to_id_string();
|
||||||
|
let critical = ext.critical;
|
||||||
|
match oid.as_str() {
|
||||||
|
oids::OID_BASIC_CONSTRAINTS => {
|
||||||
|
if basic_constraints.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("basicConstraints".into()));
|
||||||
|
}
|
||||||
|
if !critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("basicConstraints".into(), "critical".into()));
|
||||||
|
}
|
||||||
|
let bc = parse_basic_constraints(ext)?;
|
||||||
|
basic_constraints = Some(bc);
|
||||||
|
}
|
||||||
|
oids::OID_SUBJECT_KEY_IDENTIFIER => {
|
||||||
|
if ski.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("subjectKeyIdentifier".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("subjectKeyIdentifier".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let s = parse_subject_key_identifier(ext)?;
|
||||||
|
ski = Some(s);
|
||||||
|
}
|
||||||
|
oids::OID_AUTHORITY_KEY_IDENTIFIER => {
|
||||||
|
if aki.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("authorityKeyIdentifier".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("authorityKeyIdentifier".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let a = parse_authority_key_identifier(ext)?;
|
||||||
|
aki = Some(a);
|
||||||
|
}
|
||||||
|
oids::OID_KEY_USAGE => {
|
||||||
|
if key_usage.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("keyUsage".into()));
|
||||||
|
}
|
||||||
|
if !critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("keyUsage".into(), "critical".into()));
|
||||||
|
}
|
||||||
|
let ku = parse_key_usage(ext)?;
|
||||||
|
key_usage = Some(ku);
|
||||||
|
}
|
||||||
|
oids::OID_EXTENDED_KEY_USAGE => {
|
||||||
|
if extended_key_usage.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("extendedKeyUsage".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("extendedKeyUsage".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let eku = oids::OID_EXTENDED_KEY_USAGE;
|
||||||
|
}
|
||||||
|
oids::OID_CRL_DISTRIBUTION_POINTS => {
|
||||||
|
if crl_dp.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("crlDistributionPoints".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("crlDistributionPoints".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let cdp = parse_crl_distribution_points(ext)?;
|
||||||
|
crl_dp = Some(cdp);
|
||||||
|
}
|
||||||
|
oids::OID_AUTHORITY_INFO_ACCESS => {
|
||||||
|
if aia.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("authorityInfoAccess".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("authorityInfoAccess".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let p_aia = parse_authority_info_access(ext)?;
|
||||||
|
aia = Some(p_aia);
|
||||||
|
}
|
||||||
|
oids::OID_SUBJECT_INFO_ACCESS => {
|
||||||
|
if sia.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("subjectInfoAccess".into()));
|
||||||
|
}
|
||||||
|
if critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("subjectInfoAccess".into(), "non-critical".into()));
|
||||||
|
}
|
||||||
|
let p_sia = parse_subject_info_access(ext)?;
|
||||||
|
sia = Some(p_sia);
|
||||||
|
}
|
||||||
|
oids::OID_CERTIFICATE_POLICIES => {
|
||||||
|
if certificate_policies.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("certificatePolicies".into()));
|
||||||
|
}
|
||||||
|
if !critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("certificatePolicies".into(), "critical".into()));
|
||||||
|
}
|
||||||
|
let p_cp = parse_certificate_policies(ext)?;
|
||||||
|
certificate_policies = Some(p_cp);
|
||||||
|
}
|
||||||
|
oids::OID_IP_ADDRESS_BLOCKS => {
|
||||||
|
if ip_addr_blocks.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("ipAddressBlocks".into()));
|
||||||
|
}
|
||||||
|
if !critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("ipAddressBlocks".into(), "critical".into()));
|
||||||
|
}
|
||||||
|
let p_ip = parse_ip_address_blocks(ext)?;
|
||||||
|
ip_addr_blocks = Some(p_ip);
|
||||||
|
}
|
||||||
|
oids::OID_AS_IDENTIFIERS => {
|
||||||
|
if as_identifiers.is_some() {
|
||||||
|
return Err(ResourceCertError::DuplicateExtension("asIdentifiers".into()));
|
||||||
|
}
|
||||||
|
if !critical {
|
||||||
|
return Err(ResourceCertError::CriticalError("asIdentifiers".into(), "critical".into()));
|
||||||
|
}
|
||||||
|
let p_as = parse_as_identifiers(ext)?;
|
||||||
|
as_identifiers = Some(p_as);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
Ok(RcExtension {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_basic_constraints(ext: &X509Extension<'_>) -> Result<bool, ResourceCertError> {
|
||||||
|
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert("basicConstraints parse failed".into()));
|
||||||
|
};
|
||||||
|
Ok(bc.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_subject_key_identifier(ext: &X509Extension<'_>) -> Result<Vec<u8>, ResourceCertError> {
|
||||||
|
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert("subjectKeyIdentifier parse failed".into()));
|
||||||
|
};
|
||||||
|
Ok(s.0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_authority_key_identifier(ext: &X509Extension<'_>) -> Result<Vec<u8>, ResourceCertError> {
|
||||||
|
let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert("authorityKeyIdentifier parse failed".into()));
|
||||||
|
};
|
||||||
|
let key_id = aki
|
||||||
|
.key_identifier
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(ResourceCertError::MissingParameter("key_identifier".into()))?;
|
||||||
|
|
||||||
|
if aki.authority_cert_issuer.is_some() {
|
||||||
|
return Err(ResourceCertError::UnexceptedParameter("authority_cert_issuer".into()));
|
||||||
|
}
|
||||||
|
if aki.authority_cert_serial.is_some() {
|
||||||
|
return Err(ResourceCertError::UnexceptedParameter("authority_cert_serial".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ok(key_id.0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key_usage(ext: &X509Extension<'_>) -> Result<KeyUsage, ResourceCertError> {
|
||||||
|
let ParsedExtension::KeyUsage(ku) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert("keyUsage parse failed".into()));
|
||||||
|
};
|
||||||
|
Ok(ku.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_crl_distribution_points(ext: &X509Extension<'_>) -> Result<Vec<Url>, ResourceCertError> {
|
||||||
|
let ParsedExtension::CRLDistributionPoints(cdp) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert("crlDistributionPoints parse failed".into()));
|
||||||
|
};
|
||||||
|
let mut urls = Vec::new();
|
||||||
|
for point in cdp.points.iter() {
|
||||||
|
if point.reasons.is_some() {
|
||||||
|
return Err(ResourceCertError::UnexceptedParameter("reasons".into()));
|
||||||
|
}
|
||||||
|
if point.crl_issuer.is_some() {
|
||||||
|
return Err(ResourceCertError::UnexceptedParameter("crl_issuer".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let dp_name = point.distribution_point.as_ref()
|
||||||
|
.ok_or(ResourceCertError::MissingParameter("distribution_point".into()))?;
|
||||||
|
match dp_name {
|
||||||
|
DistributionPointName::FullName(names) => {
|
||||||
|
for name in names {
|
||||||
|
match name {
|
||||||
|
GeneralName::URI(uri) => {
|
||||||
|
let url = Url::parse(uri)
|
||||||
|
.map_err(|_| ResourceCertError::InvalidUri(uri.to_string()))?;
|
||||||
|
urls.push(url);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ResourceCertError::UnsupportedGeneralName("distribution_point".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
DistributionPointName::NameRelativeToCRLIssuer(_) => {
|
||||||
|
return Err(ResourceCertError::UnsupportedCrlDistributionPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if urls.is_empty() {
|
||||||
|
return Err(ResourceCertError::MissingParameter("distribution_point".into()));
|
||||||
|
}
|
||||||
|
Ok(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_authority_info_access(
|
||||||
|
ext: &X509Extension<'_>,
|
||||||
|
) -> Result<Vec<AccessDescription>, ResourceCertError> {
|
||||||
|
let ParsedExtension::AuthorityInfoAccess(aia) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert(
|
||||||
|
"authorityInfoAccess parse failed".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut access_descriptions = Vec::new();
|
||||||
|
|
||||||
|
for access in &aia.accessdescs {
|
||||||
|
let access_method_oid = access.access_method.to_id_string();
|
||||||
|
|
||||||
|
let uri = match &access.access_location {
|
||||||
|
GeneralName::URI(uri) => uri,
|
||||||
|
_ => {
|
||||||
|
return Err(ResourceCertError::InvalidAccessLocationType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = Url::parse(uri)
|
||||||
|
.map_err(|_| ResourceCertError::InvalidUri(uri.to_string()))?;
|
||||||
|
|
||||||
|
access_descriptions.push(AccessDescription {
|
||||||
|
access_method_oid,
|
||||||
|
access_location: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if access_descriptions.is_empty() {
|
||||||
|
return Err(ResourceCertError::EmptyAuthorityInfoAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(access_descriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_subject_info_access(ext: &X509Extension<'_>) -> Result<Vec<AccessDescription>, ResourceCertError> {
|
||||||
|
let ParsedExtension::SubjectInfoAccess(sia) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert(
|
||||||
|
"subjectInfoAccess parse failed".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let mut access_descriptions = Vec::new();
|
||||||
|
|
||||||
|
for access in &sia.accessdescs {
|
||||||
|
let access_method_oid = access.access_method.to_id_string();
|
||||||
|
|
||||||
|
// accessLocation: MUST be URI in RPKI
|
||||||
|
let uri = match &access.access_location {
|
||||||
|
GeneralName::URI(uri) => uri,
|
||||||
|
_ => {
|
||||||
|
return Err(ResourceCertError::InvalidAccessLocationType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let url = Url::parse(uri)
|
||||||
|
.map_err(|_| ResourceCertError::InvalidUri(uri.to_string()))?;
|
||||||
|
|
||||||
|
access_descriptions.push(AccessDescription {
|
||||||
|
access_method_oid,
|
||||||
|
access_location: url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if access_descriptions.is_empty() {
|
||||||
|
return Err(ResourceCertError::EmptyAuthorityInfoAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(access_descriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_certificate_policies(ext: &X509Extension<'_>) -> Result<Vec<PolicyInformation>, ResourceCertError> {
|
||||||
|
let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else {
|
||||||
|
return Err(ResourceCertError::ParseCert(
|
||||||
|
"certificatePolicies parse failed".into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
let mut policies = Vec::new();
|
||||||
|
if cp.len() > 1 {
|
||||||
|
return Err(ResourceCertError::CertificatePoliciesTooMany);
|
||||||
|
}
|
||||||
|
let policy_info = cp.first().unwrap();
|
||||||
|
let policy_id = policy_info.policy_id.to_id_string();
|
||||||
|
if policy_id != oids::OID_RPKI_CP {
|
||||||
|
return Err(ResourceCertError::CertificatePoliciesInvalid);
|
||||||
|
}
|
||||||
|
let policy_info = PolicyInformation{
|
||||||
|
policy_oid: policy_id,
|
||||||
|
};
|
||||||
|
policies.push(policy_info);
|
||||||
|
Ok(policies)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bitstring_to_ip(b: &BitString, afi: &Afi) -> Result<IPAddress, ResourceCertError> {
|
||||||
|
let bytes = b.as_bytes();
|
||||||
|
let ip = match afi {
|
||||||
|
Afi::Ipv4 => {
|
||||||
|
if bytes.len() != 4 { return Err(ResourceCertError::ParseCert("IPv4 length mismatch".into())); }
|
||||||
|
u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u128
|
||||||
|
},
|
||||||
|
Afi::Ipv6 => {
|
||||||
|
if bytes.len() != 16 { return Err(ResourceCertError::ParseCert("IPv6 length mismatch".into())); }
|
||||||
|
u128::from_be_bytes([
|
||||||
|
bytes[0], bytes[1], bytes[2], bytes[3],
|
||||||
|
bytes[4], bytes[5], bytes[6], bytes[7],
|
||||||
|
bytes[8], bytes[9], bytes[10], bytes[11],
|
||||||
|
bytes[12], bytes[13], bytes[14], bytes[15],
|
||||||
|
])
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Ok(IPAddress(ip))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ip_address_blocks(ext: &X509Extension<'_>) -> Result<IPAddrBlocks, ResourceCertError> {
|
||||||
|
|
||||||
|
let ip_blocks_der = ext.value;
|
||||||
|
|
||||||
|
let ips = parse(ip_blocks_der, |p| {
|
||||||
|
// 顶层 SEQUENCE OF IPAddressFamily
|
||||||
|
let mut ip_families = Vec::new();
|
||||||
|
p.read_sequence_of(|p2| {
|
||||||
|
// 每个 IPAddressFamily 是 SEQUENCE { addressFamily OCTET STRING, ipAddressChoice }
|
||||||
|
p2.read_sequence(|p3| {
|
||||||
|
let address_family_bytes = p3.read_element::<&[u8]>()?;
|
||||||
|
let afi = match address_family_bytes {
|
||||||
|
[0,1] => Afi::Ipv4,
|
||||||
|
[0,2] => Afi::Ipv6,
|
||||||
|
_ => return Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析 IPAddressChoice
|
||||||
|
let ip_address_choice = {
|
||||||
|
let peek_tag = p3.peek_tag()?;
|
||||||
|
match peek_tag.tag_number() {
|
||||||
|
5 => IPAddressChoice::Inherit, // NULL
|
||||||
|
16 => { // SEQUENCE OF IPAddressOrRange
|
||||||
|
let ranges = p3.read_sequence_of(|p4| {
|
||||||
|
// 解析 IPAddressOrRange CHOICE
|
||||||
|
let peek = p4.peek_tag()?.tag_number();
|
||||||
|
if peek == 16 { // SEQUENCE -> AddressPrefix
|
||||||
|
let (addr_bytes, prefix_len): (&[u8], u8) = p4.read_sequence(|p5| {
|
||||||
|
let addr = p5.read_element::<BitString>()?.as_bytes();
|
||||||
|
let prefix_len = addr.len() as u8 * 8; // 简化:用字节长度推前缀
|
||||||
|
Ok((addr, prefix_len))
|
||||||
|
})?;
|
||||||
|
Ok(IPAddressOrRange::AddressPrefix(IPAddressPrefix{
|
||||||
|
address: bitstring_to_ip(addr_bytes, &afi)?,
|
||||||
|
prefix_length: prefix_len,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
// AddressRange
|
||||||
|
let (min_bytes, max_bytes) = p4.read_sequence(|p5| {
|
||||||
|
let min = p5.read_element::<BitString>()?.as_bytes();
|
||||||
|
let max = p5.read_element::<BitString>()?.as_bytes();
|
||||||
|
Ok((min, max))
|
||||||
|
})?;
|
||||||
|
Ok(IPAddressOrRange::AddressRange(IPAddressRange{
|
||||||
|
min: bitstring_to_ip(min_bytes, &afi)?,
|
||||||
|
max: bitstring_to_ip(max_bytes, &afi)?,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
IPAddressChoice::AddressOrRange(ranges)
|
||||||
|
}
|
||||||
|
_ => return Err(asn1::ParseError::new(asn1::ParseErrorKind::InvalidValue)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ip_families.push(IPAddressFamily {
|
||||||
|
address_family: afi,
|
||||||
|
ip_address_choice,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(IPAddrBlocks { ips: ip_families })
|
||||||
|
}).map_err(|_| ResourceCertError::ParseCert("Failed to parse IPAddrBlocks DER".into()))?;
|
||||||
|
|
||||||
|
Ok(ips)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_as_identifiers(ext: &X509Extension<'_>) -> Result<Vec<AsIdentifier>, ResourceCertError> {
|
||||||
|
let as_identifiers_der = ext.value;
|
||||||
|
// TODO: 解析 ASIdentifiers DER
|
||||||
|
|
||||||
|
}
|
||||||
90
src/data_model/resources/as_resources.rs
Normal file
90
src/data_model/resources/as_resources.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ASIdentifiers {
|
||||||
|
pub asn: Vec<ASIdentifierChoice>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ASN
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ASIdentifierChoice {
|
||||||
|
Inherit,
|
||||||
|
ASIDsOrRanges(Vec<ASIDOrRange>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ASIDOrRange {
|
||||||
|
Id(Asn),
|
||||||
|
AsRange(ASRange),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ASRange {
|
||||||
|
pub min: Asn,
|
||||||
|
pub max: Asn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ASRange {
|
||||||
|
/// Creates a new AS number range from the smallest and largest number.
|
||||||
|
pub fn new(min: Asn, max: Asn) -> Self {
|
||||||
|
ASRange { min, max }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an AS block covering all ASNs.
|
||||||
|
pub fn all() -> ASRange {
|
||||||
|
ASRange::new(Asn::MIN, Asn::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the smallest AS number that is part of this range.
|
||||||
|
pub fn min(self) -> Asn {
|
||||||
|
self.min
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the largest AS number that is still part of this range.
|
||||||
|
pub fn max(self) -> Asn {
|
||||||
|
self.max
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of ASNs covered by this value.
|
||||||
|
pub fn asn_count(self) -> u32 {
|
||||||
|
u32::from(self.max) - u32::from(self.min) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct Asn(u32);
|
||||||
|
|
||||||
|
impl Asn {
|
||||||
|
pub const MIN: Asn = Asn(u32::MIN);
|
||||||
|
pub const MAX: Asn = Asn(u32::MAX);
|
||||||
|
|
||||||
|
/// Creates an AS number from a `u32`.
|
||||||
|
pub fn from_u32(value: u32) -> Self {
|
||||||
|
Asn(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an AS number into a `u32`.
|
||||||
|
pub fn into_u32(self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an AS number into a network-order byte array.
|
||||||
|
pub fn to_raw(self) -> [u8; 4] {
|
||||||
|
self.0.to_be_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u32> for Asn {
|
||||||
|
fn from(id: u32) -> Self {
|
||||||
|
Asn(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Asn> for u32 {
|
||||||
|
fn from(id: Asn) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/data_model/resources/ip_resources.rs
Normal file
47
src/data_model/resources/ip_resources.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct IPAddrBlocks {
|
||||||
|
pub ips: Vec<IPAddressFamily>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// IP Address Family
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct IPAddressFamily {
|
||||||
|
pub address_family: Afi,
|
||||||
|
pub ip_address_choice: IPAddressChoice,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum Afi {
|
||||||
|
Ipv4,
|
||||||
|
Ipv6,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum IPAddressChoice {
|
||||||
|
Inherit,
|
||||||
|
AddressOrRange(Vec<IPAddressOrRange>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum IPAddressOrRange {
|
||||||
|
AddressPrefix(IPAddressPrefix),
|
||||||
|
AddressRange(IPAddressRange),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IPAddressPrefix {
|
||||||
|
pub address: IPAddress,
|
||||||
|
pub prefix_length: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct IPAddressRange {
|
||||||
|
pub min: IPAddress,
|
||||||
|
pub max: IPAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||||
|
pub struct IPAddress(u128);
|
||||||
|
|
||||||
3
src/data_model/resources/mod.rs
Normal file
3
src/data_model/resources/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub(crate) mod ip_resources;
|
||||||
|
pub(crate) mod as_resources;
|
||||||
|
pub mod resource;
|
||||||
10
src/data_model/resources/resource.rs
Normal file
10
src/data_model/resources/resource.rs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
use crate::data_model::resources::as_resources::ASIdentifiers;
|
||||||
|
use crate::data_model::resources::ip_resources::IPAddrBlocks;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct ResourceSet {
|
||||||
|
ip_addr_blocks: IPAddrBlocks,
|
||||||
|
as_identifiers: ASIdentifiers,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
27
src/data_model/ta.rs
Normal file
27
src/data_model/ta.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
use url::Url;
|
||||||
|
use x509_parser::prelude::*;
|
||||||
|
|
||||||
|
use crate::data_model::resources::resource::ResourceSet;
|
||||||
|
|
||||||
|
//
|
||||||
|
// #[derive(Debug, Clone)]
|
||||||
|
// pub struct TrustAnchorCert {
|
||||||
|
// /// 信任锚证书名称
|
||||||
|
// pub name: String,
|
||||||
|
//
|
||||||
|
// /// 证书原始DER内容
|
||||||
|
// pub cert_der: Vec<u8>,
|
||||||
|
//
|
||||||
|
// /// 证书
|
||||||
|
// pub cert: X509Certificate<'static>,
|
||||||
|
//
|
||||||
|
// /// 资源集合
|
||||||
|
// pub resources: ResourceSet,
|
||||||
|
//
|
||||||
|
// ///发布点
|
||||||
|
// pub publication_point: Url,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// impl TrustAnchorCert {
|
||||||
|
//
|
||||||
|
// }
|
||||||
18
src/data_model/tal.rs
Normal file
18
src/data_model/tal.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/// TAL Model
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Tal {
|
||||||
|
/// Optional human-readable comments
|
||||||
|
pub comments: Vec<String>,
|
||||||
|
|
||||||
|
/// Ordered list of URIs pointing to the TA certificate
|
||||||
|
pub uris: Vec<TalUri>,
|
||||||
|
|
||||||
|
/// SubjectPublicKeyInfo DER
|
||||||
|
pub spki_der: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TalUri {
|
||||||
|
Rsync(String),
|
||||||
|
Https(String),
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user