diff --git a/Cargo.toml b/Cargo.toml index 49f8bfa..6928bc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ hex = "0.4.3" thiserror = "2.0.18" time = "0.3.45" x509-parser = { version = "0.18.0", features = ["verify"] } +url = "2.5.8" diff --git a/specs/01_tal.md b/specs/01_tal.md new file mode 100644 index 0000000..1502de1 --- /dev/null +++ b/specs/01_tal.md @@ -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 "" or "". +``` + +## 1.3 抽象数据模型 + +### 1.3.1 TAL + +| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 | +|----------|-------------|-------------------------|--------------------------------------------|---------------| +| uris | Vec | 指向TA的URI列表 | 允许rsync和https协议。 | RFC 8630 §2.1 | +| comment | Vec | 注释(可选) | | RFC 8630 §2.2 | +| spki_der | Vec | 原始的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 | + diff --git a/specs/02_ta.md b/specs/02_ta.md new file mode 100644 index 0000000..b01d664 --- /dev/null +++ b/specs/02_ta.md @@ -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 | 原始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 | IPv4前缀集合 | | RFC 3779 §2 |) + +[//]: # (| v6 | PrefixSet | IPv6前缀集合 | | RFC 3779 §2 |) + +[//]: # () +[//]: # (### 2.3.4 AsnResourceSet) + +[//]: # () +[//]: # (| 字段 | 类型 | 语义 | 约束/解析规则 | RFC 引用 |) + +[//]: # (|-------|--------------------|-------|-------------|-------------|) + +[//]: # (| range | RangeSet | 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. 更新本地存储库缓存。 diff --git a/specs/03_rc.md b/specs/03_rc.md new file mode 100644 index 0000000..9b03541 --- /dev/null +++ b/specs/03_rc.md @@ -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 | 证书原始数据 | | | +| 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 | + + diff --git a/src/data_model/mod.rs b/src/data_model/mod.rs index d20a38c..946ad86 100644 --- a/src/data_model/mod.rs +++ b/src/data_model/mod.rs @@ -1,2 +1,6 @@ pub mod crl; - +mod rc; +mod tal; +mod ta; +mod resources; +mod oids; diff --git a/src/data_model/oids.rs b/src/data_model/oids.rs new file mode 100644 index 0000000..08cd5b4 --- /dev/null +++ b/src/data_model/oids.rs @@ -0,0 +1,13 @@ +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"; diff --git a/src/data_model/rc.rs b/src/data_model/rc.rs new file mode 100644 index 0000000..cc9e224 --- /dev/null +++ b/src/data_model/rc.rs @@ -0,0 +1,519 @@ +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, + CRLDistributionPoints, DistributionPointName, GeneralName}; +use crate::data_model::crl::CrlDecodeError; +use crate::data_model::resources::ip_resources::IPAddrBlocks; +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, + pub authority_info_access: Vec, + pub subject_info_access: Vec, + pub certificate_policies: Vec, + pub ip_resource: IPAddrBlocks, + pub as_resource: ASIdentifiers, + +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResourceCert { + /// 证书原始DER内容 + pub cert_der: Vec, + + /// 基本证书信息 + 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, + +} + +// impl ResourceCert{ +// pub fn from_der(cert_der: &[u8]) -> Result { +// 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 { +// ///逐个校验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())?; +// +// Ok(ResourceCert { +// cert_der: x509_rc.to_der().to_vec(), +// version: version.0, +// serial_number: x509_rc.serial(), +// signature_algorithm_oid: signature_algorithm.algorithm.to_id_string(), +// issuer_dn: x509_rc.issuer().to_string(), +// subject_dn: x509_rc.subject().to_string(), +// validity, +// subject_public_key_info: SubjectPublicKeyInfo { +// // algorithm_oid: x509_rc.tbs_certificate.subject_pki.algorithm.algorithm.to_id_string(), +// // subject_public_key: x509_rc.tbs_certificate.subject_pki.subject_public_key.unused_bits, +// }, +// extensions, +// }) +// +// +// } +// +// 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 { +// 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); +// } +// } +// +// +// } +// Ok(RcExtension { +// basic_constraints, +// ip_addr_blocks, +// as_identifiers, +// subject_key_id: ski, +// authority_key_id: aki, +// crl_distribution_points: crl_dp, +// authority_info_access: aia, +// }) +// } +// +// fn parse_basic_constraints(ext: &X509Extension<'_>) -> Result { +// 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, 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, 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 { +// 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, 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, 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, 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, ResourceCertError> { +// let ParsedExtension::CertificatePolicies(cp) = ext.parsed_extension() else { +// return Err(ResourceCertError::ParseCert( +// "certificatePolicies parse failed".into(), +// )); +// }; +// let mut policies = Vec::new(); +// +// } diff --git a/src/data_model/resources/as_resources.rs b/src/data_model/resources/as_resources.rs new file mode 100644 index 0000000..13eb095 --- /dev/null +++ b/src/data_model/resources/as_resources.rs @@ -0,0 +1,90 @@ + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ASIdentifiers { + pub asn: Vec +} + + + + +// ASN +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ASIdentifierChoice { + Inherit, + ASIDsOrRanges(Vec), +} + +#[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 for Asn { + fn from(id: u32) -> Self { + Asn(id) + } +} + +impl From for u32 { + fn from(id: Asn) -> Self { + id.0 + } +} \ No newline at end of file diff --git a/src/data_model/resources/ip_resources.rs b/src/data_model/resources/ip_resources.rs new file mode 100644 index 0000000..9590bf8 --- /dev/null +++ b/src/data_model/resources/ip_resources.rs @@ -0,0 +1,46 @@ + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct IPAddrBlocks { + ips: Vec +} + + +// 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), +} + +#[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); \ No newline at end of file diff --git a/src/data_model/resources/mod.rs b/src/data_model/resources/mod.rs new file mode 100644 index 0000000..39284d6 --- /dev/null +++ b/src/data_model/resources/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod ip_resources; +pub(crate) mod as_resources; +pub mod resource; \ No newline at end of file diff --git a/src/data_model/resources/resource.rs b/src/data_model/resources/resource.rs new file mode 100644 index 0000000..98b442c --- /dev/null +++ b/src/data_model/resources/resource.rs @@ -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, +} + + diff --git a/src/data_model/ta.rs b/src/data_model/ta.rs new file mode 100644 index 0000000..e2f2507 --- /dev/null +++ b/src/data_model/ta.rs @@ -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, +// +// /// 证书 +// pub cert: X509Certificate<'static>, +// +// /// 资源集合 +// pub resources: ResourceSet, +// +// ///发布点 +// pub publication_point: Url, +// } +// +// impl TrustAnchorCert { +// +// } diff --git a/src/data_model/tal.rs b/src/data_model/tal.rs new file mode 100644 index 0000000..5dfd02d --- /dev/null +++ b/src/data_model/tal.rs @@ -0,0 +1,18 @@ +/// TAL Model +#[derive(Clone, Debug)] +pub struct Tal { + /// Optional human-readable comments + pub comments: Vec, + + /// Ordered list of URIs pointing to the TA certificate + pub uris: Vec, + + /// SubjectPublicKeyInfo DER + pub spki_der: Vec, +} + +#[derive(Debug, Clone)] +pub enum TalUri { + Rsync(String), + Https(String), +}