aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Walbran <qwandor@google.com>2023-12-18 15:34:43 +0000
committerAndrew Walbran <qwandor@google.com>2023-12-18 15:34:43 +0000
commit840f94c87f29ada09c264dd3c65c8a2677a15a20 (patch)
treeb322cfee12da8882324c96f516346c030c95c5b5
parent6cfe7d76a7fb53f260840cd3426123845b9d30bf (diff)
parentd2a24d5102ef49e4e18ee2beaf3a1cef139df382 (diff)
downloadx509-cert-trusty-main.tar.gz
Merge branch 'tmp_auto_upgrade' into trusty-maintrusty-main
Change-Id: Iae3c0051fd9ec1a04eae0f435f56213d5bee7c7a
-rw-r--r--.cargo_vcs_info.json2
-rw-r--r--Android.bp17
-rw-r--r--CHANGELOG.md125
-rw-r--r--Cargo.toml77
-rw-r--r--Cargo.toml.orig41
-rw-r--r--METADATA21
-rw-r--r--README.md4
-rw-r--r--cargo2android.json22
-rw-r--r--cargo2android_viz.bp1
-rw-r--r--cargo_embargo.json34
-rw-r--r--src/anchor.rs48
-rw-r--r--src/attr.rs134
-rw-r--r--src/builder.rs526
-rw-r--r--src/certificate.rs164
-rw-r--r--src/crl.rs29
-rw-r--r--src/ext.rs33
-rw-r--r--src/ext/pkix.rs56
-rw-r--r--src/ext/pkix/access.rs18
-rw-r--r--src/ext/pkix/authkeyid.rs24
-rw-r--r--src/ext/pkix/certpolicy.rs43
-rw-r--r--src/ext/pkix/constraints/basic.rs25
-rw-r--r--src/ext/pkix/constraints/name.rs16
-rw-r--r--src/ext/pkix/constraints/policy.rs2
-rw-r--r--src/ext/pkix/crl.rs36
-rw-r--r--src/ext/pkix/crl/dp.rs16
-rw-r--r--src/ext/pkix/keyusage.rs119
-rw-r--r--src/ext/pkix/name/dirstr.rs11
-rw-r--r--src/ext/pkix/name/dp.rs6
-rw-r--r--src/ext/pkix/name/ediparty.rs6
-rw-r--r--src/ext/pkix/name/general.rs66
-rw-r--r--src/ext/pkix/name/other.rs12
-rw-r--r--src/ext/pkix/policymap.rs1
-rw-r--r--src/lib.rs8
-rw-r--r--src/macros.rs60
-rw-r--r--src/name.rs110
-rw-r--r--src/request.rs139
-rw-r--r--src/serial_number.rs195
-rw-r--r--src/time.rs48
-rw-r--r--tests/builder.rs315
-rw-r--r--tests/certificate.rs196
-rw-r--r--tests/certreq.rs19
-rw-r--r--tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.pem61
-rw-r--r--tests/examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.derbin0 -> 1370 bytes
-rw-r--r--tests/examples/crates.io-chain.pem122
-rw-r--r--tests/examples/p256-priv.derbin0 -> 138 bytes
-rw-r--r--tests/examples/p256-pub.derbin0 -> 91 bytes
-rw-r--r--tests/examples/qualcomm.pem16
-rw-r--r--tests/examples/rsa2048-priv.derbin0 -> 1191 bytes
-rw-r--r--tests/examples/rsa2048-pub.derbin0 -> 294 bytes
-rw-r--r--tests/general_name.rs4
-rw-r--r--tests/name.rs138
-rw-r--r--tests/pkix_extensions.rs239
-rw-r--r--tests/trust_anchor_format.rs126
-rw-r--r--tests/validity.rs22
54 files changed, 2940 insertions, 613 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index 271baca..ec6a5a4 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,6 +1,6 @@
{
"git": {
- "sha1": "5747b144d21463fe471a2aaa34a5a1bd67b611f2"
+ "sha1": "dc5a611afb6d3aa7b9b9bfae3587f996bb25fdc6"
},
"path_in_vcs": "x509-cert"
} \ No newline at end of file
diff --git a/Android.bp b/Android.bp
index fe377df..01da6f9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --config cargo2android.json.
+// This file is generated by cargo_embargo.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -35,14 +35,12 @@ rust_library_host {
name: "libx509_cert",
crate_name: "x509_cert",
cargo_env_compat: true,
- cargo_pkg_version: "0.1.1",
+ cargo_pkg_version: "0.2.4",
srcs: ["src/lib.rs"],
edition: "2021",
- features: ["alloc"],
rustlibs: [
"libconst_oid",
"libder",
- "libflagset",
"libspki",
],
}
@@ -51,14 +49,12 @@ rust_library_rlib {
name: "libx509_cert_nostd",
crate_name: "x509_cert",
cargo_env_compat: true,
- cargo_pkg_version: "0.1.1",
+ cargo_pkg_version: "0.2.4",
srcs: ["src/lib.rs"],
edition: "2021",
- features: ["alloc"],
rustlibs: [
"libconst_oid_nostd",
"libder_nostd",
- "libflagset_nostd",
"libspki_nostd",
],
apex_available: [
@@ -74,7 +70,8 @@ rust_library_rlib {
product_available: true,
vendor_available: true,
visibility: [
- "//system/keymint:__subpackages__",
- ]
- ,
+ "//packages/modules/Virtualization:__subpackages__",
+ "//system/keymint:__subpackages__",
+ ],
+
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 83406c9..9922da4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,131 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## 0.2.4 (2023-07-25)
+
+### Added
+- `add_attribute` to `RequestBuilder` ([#1137])
+
+### Changed
+- bump `serde_json` from 1.0.97 to 1.0.99 ([#1122])
+- use the shortest name when looking attr OID ([#1130])
+- bump `serde_json` from 1.0.100 to 1.0.103 ([#1158])
+
+### Fixed
+- RDN string representation ([#1126])
+- `Arbitrary` for certificates ([#1150])
+
+[#1122]: https://github.com/RustCrypto/formats/pull/1122
+[#1126]: https://github.com/RustCrypto/formats/pull/1126
+[#1130]: https://github.com/RustCrypto/formats/pull/1130
+[#1137]: https://github.com/RustCrypto/formats/pull/1137
+[#1150]: https://github.com/RustCrypto/formats/pull/1150
+[#1158]: https://github.com/RustCrypto/formats/pull/1158
+
+## 0.2.3 (2023-05-30)
+
+### Added
+- Added `TryFrom` for `RelativeDistinguishedName` ([#1092])
+- Load a chain of certificates from a slice ([#1081])
+
+[#1092]: https://github.com/RustCrypto/formats/pull/1092
+[#1081]: https://github.com/RustCrypto/formats/pull/1081
+
+## 0.2.2 (2023-05-19)
+
+### Added
+- Certificate builder ([#764])
+- Support for `RandomizedSigner` in builder ([#1007])
+- Provide parsing profiles ([#987])
+- Support for `Time::INFINITY` ([#1024])
+- Conversion from `std::net::IpAddr` ([#1035])
+- `CertReq` builder ([#1034])
+- missing extension implementations ([#1050])
+- notes about `UTCTime` range being 1970-2049 ([#1052])
+- consume the `SignatureBitStringEncoding` trait ([#1048])
+
+### Changed
+- use `ErrorKind::Value` for overlength serial ([#988])
+- Bump `hex-literal` to v0.4.1 ([#999])
+- Builder updates ([#1001])
+- better debug info when `zlint` isn't installed ([#1018])
+- make SKI optional in leaf certificate ([#1028])
+- bump rsa from 0.9.0-pre.2 to 0.9.0 ([#1033])
+- bump rsa from 0.9.1 to 0.9.2 ([#1056])
+
+### Fixed
+- fix `KeyUsage` bit tests ([#993])
+- extraneous PhantomData in `TbsCertificate` ([#1017])
+- CI flakiness ([#1042])
+- usage of ecdsa signer ([#1043])
+
+[#764]: https://github.com/RustCrypto/formats/pull/764
+[#987]: https://github.com/RustCrypto/formats/pull/987
+[#988]: https://github.com/RustCrypto/formats/pull/988
+[#993]: https://github.com/RustCrypto/formats/pull/993
+[#999]: https://github.com/RustCrypto/formats/pull/999
+[#1001]: https://github.com/RustCrypto/formats/pull/1001
+[#1007]: https://github.com/RustCrypto/formats/pull/1007
+[#1017]: https://github.com/RustCrypto/formats/pull/1017
+[#1018]: https://github.com/RustCrypto/formats/pull/1018
+[#1024]: https://github.com/RustCrypto/formats/pull/1024
+[#1028]: https://github.com/RustCrypto/formats/pull/1028
+[#1033]: https://github.com/RustCrypto/formats/pull/1033
+[#1034]: https://github.com/RustCrypto/formats/pull/1034
+[#1035]: https://github.com/RustCrypto/formats/pull/1035
+[#1042]: https://github.com/RustCrypto/formats/pull/1042
+[#1043]: https://github.com/RustCrypto/formats/pull/1043
+[#1048]: https://github.com/RustCrypto/formats/pull/1048
+[#1050]: https://github.com/RustCrypto/formats/pull/1050
+[#1052]: https://github.com/RustCrypto/formats/pull/1052
+[#1056]: https://github.com/RustCrypto/formats/pull/1056
+
+## 0.2.1 (2023-03-26)
+### Added
+- `FromStr` impls for `RdnSequence` (`Name`), `RelativeDistinguishedName`, and
+ `AttributeTypeAndValue` ([#949])
+
+### Changed
+- Deprecate `encode_from_string` functions ([#951])
+
+[#949]: https://github.com/RustCrypto/formats/pull/949
+[#951]: https://github.com/RustCrypto/formats/pull/951
+
+## 0.2.0 (2023-03-18)
+### Added
+- Feature-gated `Arbitrary` impl for `Certificate` ([#761])
+- Allow request to be serialized to PEM ([#819])
+- `Display` impl for `SerialNumber` ([#820])
+- `std` feature implies `const-oid/std` ([#874])
+
+### Changed
+- Serial numbers are formatted as `PrintableString` ([#794])
+- `SerialNumber` is now a specialized object ([#795])
+- MSRV 1.65 ([#805])
+- Make types owned instead of reference-based ([#806], [#841])
+- Bump `der` to v0.7 ([#899])
+- Bump `spki` to v0.7 ([#900])
+
+### Fixed
+- Handling of negative serial numbers ([#823], [#831])
+
+### Removed
+- `alloc` feature: now unconditionally required ([#841])
+
+[#761]: https://github.com/RustCrypto/formats/pull/761
+[#794]: https://github.com/RustCrypto/formats/pull/794
+[#795]: https://github.com/RustCrypto/formats/pull/795
+[#805]: https://github.com/RustCrypto/formats/pull/805
+[#806]: https://github.com/RustCrypto/formats/pull/806
+[#819]: https://github.com/RustCrypto/formats/pull/819
+[#820]: https://github.com/RustCrypto/formats/pull/820
+[#823]: https://github.com/RustCrypto/formats/pull/823
+[#831]: https://github.com/RustCrypto/formats/pull/831
+[#841]: https://github.com/RustCrypto/formats/pull/841
+[#874]: https://github.com/RustCrypto/formats/pull/874
+[#899]: https://github.com/RustCrypto/formats/pull/899
+[#900]: https://github.com/RustCrypto/formats/pull/900
+
## 0.1.1 (2022-12-10)
### Added
- Support `TeletexString` in `DirectoryString` ([#692])
diff --git a/Cargo.toml b/Cargo.toml
index 43ad5a1..793721f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,9 @@
[package]
edition = "2021"
-rust-version = "1.56"
+rust-version = "1.65"
name = "x509-cert"
-version = "0.1.1"
+version = "0.2.4"
authors = ["RustCrypto Developers"]
description = """
Pure Rust implementation of the X.509 Public Key Infrastructure Certificate
@@ -37,37 +37,90 @@ rustdoc-args = [
"docsrs",
]
+[dependencies.arbitrary]
+version = "1.3"
+features = ["derive"]
+optional = true
+
[dependencies.const-oid]
-version = "0.9"
+version = "0.9.3"
features = ["db"]
[dependencies.der]
-version = "0.6"
+version = "0.7.6"
features = [
- "derive",
"alloc",
+ "derive",
"flagset",
+ "oid",
]
-[dependencies.flagset]
-version = "0.4.3"
+[dependencies.sha1]
+version = "0.10.0"
+optional = true
+
+[dependencies.signature]
+version = "2.1.0"
+features = ["rand_core"]
+optional = true
[dependencies.spki]
-version = "0.6"
+version = "0.7.2"
+features = ["alloc"]
+
+[dev-dependencies.ecdsa]
+version = "0.16.7"
+features = [
+ "digest",
+ "pem",
+]
[dev-dependencies.hex-literal]
-version = "0.3"
+version = "0.4"
+
+[dev-dependencies.p256]
+version = "0.13.0"
+
+[dev-dependencies.rand]
+version = "0.8.5"
+
+[dev-dependencies.rsa]
+version = "0.9.2"
+features = ["sha2"]
[dev-dependencies.rstest]
-version = "0.12"
+version = "0.17"
+
+[dev-dependencies.sha2]
+version = "0.10"
+features = ["oid"]
+
+[dev-dependencies.tempfile]
+version = "3.5.0"
[features]
-alloc = ["der/alloc"]
+arbitrary = [
+ "dep:arbitrary",
+ "std",
+ "der/arbitrary",
+ "spki/arbitrary",
+]
+builder = [
+ "std",
+ "sha1/default",
+ "signature",
+]
+default = [
+ "pem",
+ "std",
+]
+hazmat = []
pem = [
- "alloc",
"der/pem",
+ "spki/pem",
]
std = [
+ "const-oid/std",
"der/std",
"spki/std",
]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index 723504f..717379f 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,6 +1,6 @@
[package]
name = "x509-cert"
-version = "0.1.1"
+version = "0.2.4"
description = """
Pure Rust implementation of the X.509 Public Key Infrastructure Certificate
format as described in RFC 5280
@@ -12,26 +12,37 @@ categories = ["cryptography", "data-structures", "encoding", "no-std"]
keywords = ["crypto"]
readme = "README.md"
edition = "2021"
-rust-version = "1.56"
+rust-version = "1.65"
[dependencies]
-const-oid = { version = "0.9", features = ["db"], path = "../const-oid" }
-der = { version = "0.6", features = ["derive", "alloc", "flagset"], path = "../der" }
-flagset = { version = "0.4.3" }
-spki = { version = "0.6", path = "../spki" }
+const-oid = { version = "0.9.3", features = ["db"] }
+der = { version = "0.7.6", features = ["alloc", "derive", "flagset", "oid"] }
+spki = { version = "0.7.2", features = ["alloc"] }
-[dev-dependencies]
-hex-literal = "0.3"
+# optional dependencies
+arbitrary = { version = "1.3", features = ["derive"], optional = true }
+sha1 = { version = "0.10.0", optional = true }
+signature = { version = "2.1.0", features = ["rand_core"], optional = true }
-# NOTE: upgrading requires MSRV bumps
-# - rstest v0.13 requires 1.59 (for `async-global-executor`)
-# - rstest v0.14 requires a workspace-wide 1.60 MSRV (for namespaced features)
-rstest = "0.12"
+[dev-dependencies]
+hex-literal = "0.4"
+rand = "0.8.5"
+rsa = { version = "0.9.2", features = ["sha2"] }
+ecdsa = { version = "0.16.7", features = ["digest", "pem"] }
+p256 = "0.13.0"
+rstest = "0.17"
+sha2 = { version = "0.10", features = ["oid"] }
+tempfile = "3.5.0"
+x509-cert-test-support = { path = "./test-support" }
[features]
-alloc = ["der/alloc"]
-std = ["der/std", "spki/std"]
-pem = ["alloc", "der/pem"]
+default = ["pem", "std"]
+std = ["const-oid/std", "der/std", "spki/std"]
+
+arbitrary = ["dep:arbitrary", "std", "der/arbitrary", "spki/arbitrary"]
+builder = ["std", "sha1/default", "signature"]
+hazmat = []
+pem = ["der/pem", "spki/pem"]
[package.metadata.docs.rs]
all-features = true
diff --git a/METADATA b/METADATA
index c38c1c0..fb2e5f3 100644
--- a/METADATA
+++ b/METADATA
@@ -1,23 +1,20 @@
# This project was upgraded with external_updater.
# Usage: tools/external_updater/updater.sh update rust/crates/x509-cert
-# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
name: "x509-cert"
description: "Pure Rust implementation of the X.509 Public Key Infrastructure Certificate format as described in RFC 5280."
third_party {
- url {
- type: HOMEPAGE
- value: "https://crates.io/crates/x509-cert"
- }
- url {
- type: ARCHIVE
- value: "https://static.crates.io/crates/x509-cert/x509-cert-0.1.1.crate"
- }
- version: "0.1.1"
license_type: NOTICE
last_upgrade_date {
- year: 2022
+ year: 2023
month: 12
- day: 19
+ day: 15
+ }
+ homepage: "https://crates.io/crates/x509-cert"
+ identifier {
+ type: "Archive"
+ value: "https://static.crates.io/crates/x509-cert/x509-cert-0.2.4.crate"
+ version: "0.2.4"
}
}
diff --git a/README.md b/README.md
index e439c07..e131c7d 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ format as described in [RFC 5280].
## Minimum Supported Rust Version
-This crate requires **Rust 1.57** at a minimum.
+This crate requires **Rust 1.65** at a minimum.
We may change the MSRV in the future, but it will be accompanied by a minor
version bump.
@@ -43,7 +43,7 @@ dual licensed as above, without any additional terms or conditions.
[build-image]: https://github.com/RustCrypto/formats/actions/workflows/x509-cert.yml/badge.svg
[build-link]: https://github.com/RustCrypto/formats/actions/workflows/x509-cert.yml
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
-[rustc-image]: https://img.shields.io/badge/rustc-1.57+-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg
[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats
diff --git a/cargo2android.json b/cargo2android.json
deleted file mode 100644
index e6baa85..0000000
--- a/cargo2android.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "apex-available": [
- "//apex_available:platform",
- "com.android.virt"
- ],
- "run": true,
- "vendor-available": true,
- "features": "alloc",
- "variants": [
- {
- },
- {
- "device": true,
- "no-host": true,
- "add-module-block": "cargo2android_viz.bp",
- "dependency_suffix": "_nostd",
- "force-rlib": true,
- "suffix": "_nostd",
- "no-std": true
- }
- ]
-}
diff --git a/cargo2android_viz.bp b/cargo2android_viz.bp
index 6912520..8ce03b7 100644
--- a/cargo2android_viz.bp
+++ b/cargo2android_viz.bp
@@ -1,3 +1,4 @@
visibility: [
+ "//packages/modules/Virtualization:__subpackages__",
"//system/keymint:__subpackages__",
]
diff --git a/cargo_embargo.json b/cargo_embargo.json
new file mode 100644
index 0000000..e6f2c7a
--- /dev/null
+++ b/cargo_embargo.json
@@ -0,0 +1,34 @@
+{
+ "apex_available": [
+ "//apex_available:platform",
+ "com.android.virt"
+ ],
+ "features": [],
+ "run_cargo": false,
+ "variants": [
+ {
+ "package": {
+ "x509-cert": {
+ "device_supported": false
+ }
+ }
+ },
+ {
+ "module_name_overrides": {
+ "libconst_oid": "libconst_oid_nostd",
+ "libder": "libder_nostd",
+ "libflagset": "libflagset_nostd",
+ "libspki": "libspki_nostd",
+ "libx509_cert": "libx509_cert_nostd"
+ },
+ "package": {
+ "x509-cert": {
+ "add_module_block": "cargo2android_viz.bp",
+ "force_rlib": true,
+ "host_supported": false,
+ "no_std": true
+ }
+ }
+ }
+ ]
+}
diff --git a/src/anchor.rs b/src/anchor.rs
index ad7ef08..bc3a2fb 100644
--- a/src/anchor.rs
+++ b/src/anchor.rs
@@ -4,26 +4,22 @@ use crate::ext::pkix::{certpolicy::CertificatePolicies, NameConstraints};
use crate::{ext::Extensions, name::Name};
use crate::{Certificate, TbsCertificate};
-use der::asn1::{OctetStringRef, Utf8StringRef};
+use alloc::string::String;
+use der::asn1::OctetString;
+use der::flagset::{flags, FlagSet};
use der::{Choice, Enumerated, Sequence};
-use flagset::{flags, FlagSet};
-use spki::SubjectPublicKeyInfo;
+use spki::SubjectPublicKeyInfoOwned;
/// Version identifier for TrustAnchorInfo
-#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
+#[derive(Clone, Debug, Default, Copy, PartialEq, Eq, Enumerated)]
#[asn1(type = "INTEGER")]
#[repr(u8)]
pub enum Version {
/// Version 1 (default)
+ #[default]
V1 = 0,
}
-impl Default for Version {
- fn default() -> Self {
- Version::V1
- }
-}
-
/// ```text
/// TrustAnchorInfo ::= SEQUENCE {
/// version TrustAnchorInfoVersion DEFAULT v1,
@@ -41,25 +37,25 @@ impl Default for Version {
/// ```
#[derive(Clone, Debug, PartialEq, Eq, Sequence)]
#[allow(missing_docs)]
-pub struct TrustAnchorInfo<'a> {
+pub struct TrustAnchorInfo {
#[asn1(default = "Default::default")]
pub version: Version,
- pub pub_key: SubjectPublicKeyInfo<'a>,
+ pub pub_key: SubjectPublicKeyInfoOwned,
- pub key_id: OctetStringRef<'a>,
+ pub key_id: OctetString,
#[asn1(optional = "true")]
- pub ta_title: Option<Utf8StringRef<'a>>,
+ pub ta_title: Option<String>,
#[asn1(optional = "true")]
- pub cert_path: Option<CertPathControls<'a>>,
+ pub cert_path: Option<CertPathControls>,
#[asn1(context_specific = "1", tag_mode = "EXPLICIT", optional = "true")]
- pub extensions: Option<Extensions<'a>>,
+ pub extensions: Option<Extensions>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
- pub ta_title_lang_tag: Option<Utf8StringRef<'a>>,
+ pub ta_title_lang_tag: Option<String>,
}
/// ```text
@@ -74,20 +70,20 @@ pub struct TrustAnchorInfo<'a> {
/// ```
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct CertPathControls<'a> {
- pub ta_name: Name<'a>,
+pub struct CertPathControls {
+ pub ta_name: Name,
#[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")]
- pub certificate: Option<Certificate<'a>>,
+ pub certificate: Option<Certificate>,
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")]
- pub policy_set: Option<CertificatePolicies<'a>>,
+ pub policy_set: Option<CertificatePolicies>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
pub policy_flags: Option<CertPolicyFlags>,
#[asn1(context_specific = "3", tag_mode = "IMPLICIT", optional = "true")]
- pub name_constr: Option<NameConstraints<'a>>,
+ pub name_constr: Option<NameConstraints>,
#[asn1(context_specific = "4", tag_mode = "IMPLICIT", optional = "true")]
pub path_len_constraint: Option<u32>,
@@ -128,12 +124,12 @@ pub type CertPolicyFlags = FlagSet<CertPolicies>;
#[derive(Clone, Debug, PartialEq, Eq, Choice)]
#[allow(clippy::large_enum_variant)]
#[allow(missing_docs)]
-pub enum TrustAnchorChoice<'a> {
- Certificate(Certificate<'a>),
+pub enum TrustAnchorChoice {
+ Certificate(Certificate),
#[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")]
- TbsCertificate(TbsCertificate<'a>),
+ TbsCertificate(TbsCertificate),
#[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")]
- TaInfo(TrustAnchorInfo<'a>),
+ TaInfo(TrustAnchorInfo),
}
diff --git a/src/attr.rs b/src/attr.rs
index 2b4336a..d4cccc3 100644
--- a/src/attr.rs
+++ b/src/attr.rs
@@ -1,12 +1,21 @@
//! Attribute-related definitions as defined in X.501 (and updated by RFC 5280).
use alloc::vec::Vec;
-use const_oid::db::rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT};
-use core::fmt::{self, Write};
-
-use const_oid::db::DB;
-use der::asn1::{AnyRef, ObjectIdentifier, SetOfVec};
-use der::{Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd};
+use const_oid::db::{
+ rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER},
+ Database, DB,
+};
+use core::{
+ fmt::{self, Write},
+ str::FromStr,
+};
+use der::{
+ asn1::{
+ Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef,
+ Utf8StringRef,
+ },
+ Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd,
+};
/// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1].
///
@@ -24,7 +33,7 @@ pub type AttributeType = ObjectIdentifier;
/// ```
///
/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
-pub type AttributeValue<'a> = AnyRef<'a>;
+pub type AttributeValue = Any;
/// X.501 `Attribute` as defined in [RFC 5280 Appendix A.1].
///
@@ -50,17 +59,9 @@ pub type AttributeValue<'a> = AnyRef<'a>;
/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
#[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct Attribute<'a> {
+pub struct Attribute {
pub oid: AttributeType,
- pub values: SetOfVec<AttributeValue<'a>>,
-}
-
-impl<'a> TryFrom<&'a [u8]> for Attribute<'a> {
- type Error = Error;
-
- fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
- Self::from_der(bytes)
- }
+ pub values: SetOfVec<AttributeValue>,
}
/// X.501 `Attributes` as defined in [RFC 2986 Section 4].
@@ -70,7 +71,7 @@ impl<'a> TryFrom<&'a [u8]> for Attribute<'a> {
/// ```
///
/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4
-pub type Attributes<'a> = SetOfVec<Attribute<'a>>;
+pub type Attributes = SetOfVec<Attribute>;
/// X.501 `AttributeTypeAndValue` as defined in [RFC 5280 Appendix A.1].
///
@@ -82,11 +83,12 @@ pub type Attributes<'a> = SetOfVec<Attribute<'a>>;
/// ```
///
/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
-#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)]
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
+#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct AttributeTypeAndValue<'a> {
+pub struct AttributeTypeAndValue {
pub oid: AttributeType,
- pub value: AnyRef<'a>,
+ pub value: AttributeValue,
}
#[derive(Copy, Clone)]
@@ -141,9 +143,9 @@ impl Parser {
}
}
-impl AttributeTypeAndValue<'_> {
+impl AttributeTypeAndValue {
/// Parses the hex value in the `OID=#HEX` format.
- fn encode_hex(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> {
+ fn from_hex(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
// Ensure an even number of hex bytes.
let mut iter = match val.len() % 2 {
0 => [].iter().cloned().chain(val.bytes()),
@@ -153,6 +155,7 @@ impl AttributeTypeAndValue<'_> {
// Decode der bytes from hex.
let mut bytes = Vec::with_capacity((val.len() + 1) / 2);
+
while let (Some(h), Some(l)) = (iter.next(), iter.next()) {
let mut byte = 0u8;
@@ -168,14 +171,14 @@ impl AttributeTypeAndValue<'_> {
bytes.push(byte);
}
- // Serialize.
- let value = AnyRef::from_der(&bytes)?;
- let atv = AttributeTypeAndValue { oid, value };
- atv.to_vec()
+ Ok(Self {
+ oid,
+ value: Any::from_der(&bytes)?,
+ })
}
/// Parses the string value in the `NAME=STRING` format.
- fn encode_str(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> {
+ fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result<Self, Error> {
// Undo escaping.
let mut parser = Parser::new();
for c in val.bytes() {
@@ -185,13 +188,16 @@ impl AttributeTypeAndValue<'_> {
let tag = match oid {
COUNTRY_NAME => Tag::PrintableString,
DOMAIN_COMPONENT => Tag::Ia5String,
+ // Serial numbers are formatted as Printable String as per RFC 5280 Appendix A.1:
+ // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
+ SERIAL_NUMBER => Tag::PrintableString,
_ => Tag::Utf8String,
};
- // Serialize.
- let value = AnyRef::new(tag, parser.as_bytes())?;
- let atv = AttributeTypeAndValue { oid, value };
- atv.to_vec()
+ Ok(Self {
+ oid,
+ value: Any::new(tag, parser.as_bytes())?,
+ })
}
/// Converts an AttributeTypeAndValue string into an encoded AttributeTypeAndValue
@@ -199,7 +205,24 @@ impl AttributeTypeAndValue<'_> {
/// This function follows the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+ #[deprecated(
+ since = "0.2.1",
+ note = "use AttributeTypeAndValue::from_str(...)?.to_der()"
+ )]
pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> {
+ Self::from_str(s)?.to_der()
+ }
+}
+
+/// Parse an [`AttributeTypeAndValue`] string.
+///
+/// This function follows the rules in [RFC 4514].
+///
+/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+impl FromStr for AttributeTypeAndValue {
+ type Err = Error;
+
+ fn from_str(s: &str) -> der::Result<Self> {
let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?;
let (key, val) = s.split_at(idx);
let val = &val[1..];
@@ -212,8 +235,8 @@ impl AttributeTypeAndValue<'_> {
// If the value is hex-encoded DER...
match val.strip_prefix('#') {
- Some(val) => Self::encode_hex(oid, val),
- None => Self::encode_str(oid, val),
+ Some(val) => Self::from_hex(oid, val),
+ None => Self::from_delimited_str(oid, val),
}
}
}
@@ -221,17 +244,23 @@ impl AttributeTypeAndValue<'_> {
/// Serializes the structure according to the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
-impl fmt::Display for AttributeTypeAndValue<'_> {
+impl fmt::Display for AttributeTypeAndValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let val = match self.value.tag() {
- Tag::PrintableString => self.value.printable_string().ok().map(|s| s.as_str()),
- Tag::Utf8String => self.value.utf8_string().ok().map(|s| s.as_str()),
- Tag::Ia5String => self.value.ia5_string().ok().map(|s| s.as_str()),
- Tag::TeletexString => self.value.teletex_string().ok().map(|s| s.as_str()),
+ Tag::PrintableString => PrintableStringRef::try_from(&self.value)
+ .ok()
+ .map(|s| s.as_str()),
+ Tag::Utf8String => Utf8StringRef::try_from(&self.value)
+ .ok()
+ .map(|s| s.as_str()),
+ Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()),
+ Tag::TeletexString => TeletexStringRef::try_from(&self.value)
+ .ok()
+ .map(|s| s.as_str()),
_ => None,
};
- if let (Some(key), Some(val)) = (DB.by_oid(&self.oid), val) {
+ if let (Some(key), Some(val)) = (DB.shortest_name_by_oid(&self.oid), val) {
write!(f, "{}=", key.to_ascii_uppercase())?;
let mut iter = val.char_indices().peekable();
@@ -245,7 +274,7 @@ impl fmt::Display for AttributeTypeAndValue<'_> {
}
}
} else {
- let value = self.value.to_vec().or(Err(fmt::Error))?;
+ let value = self.value.to_der().or(Err(fmt::Error))?;
write!(f, "{}=#", self.oid)?;
for c in value {
@@ -256,3 +285,26 @@ impl fmt::Display for AttributeTypeAndValue<'_> {
Ok(())
}
}
+
+/// Helper trait to bring shortest name by oid lookups to Database
+trait ShortestName {
+ fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>;
+}
+
+impl<'a> ShortestName for Database<'a> {
+ fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> {
+ let mut best_match: Option<&'a str> = None;
+
+ for m in self.find_names_for_oid(*oid) {
+ if let Some(previous) = best_match {
+ if m.len() < previous.len() {
+ best_match = Some(m);
+ }
+ } else {
+ best_match = Some(m);
+ }
+ }
+
+ best_match
+ }
+}
diff --git a/src/builder.rs b/src/builder.rs
new file mode 100644
index 0000000..ec51a7e
--- /dev/null
+++ b/src/builder.rs
@@ -0,0 +1,526 @@
+//! X509 Certificate builder
+
+use alloc::vec;
+use core::fmt;
+use der::{asn1::BitString, referenced::OwnedToRef, Encode};
+use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer};
+use spki::{
+ DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding,
+ SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef,
+};
+
+use crate::{
+ certificate::{Certificate, TbsCertificate, Version},
+ ext::{
+ pkix::{
+ AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier,
+ },
+ AsExtension, Extension, Extensions,
+ },
+ name::Name,
+ request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq},
+ serial_number::SerialNumber,
+ time::Validity,
+};
+
+/// Error type
+#[derive(Debug)]
+#[non_exhaustive]
+pub enum Error {
+ /// ASN.1 DER-related errors.
+ Asn1(der::Error),
+
+ /// Public key errors propagated from the [`spki::Error`] type.
+ PublicKey(spki::Error),
+
+ /// Signing error propagated for the [`signature::Error`] type.
+ Signature(signature::Error),
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for Error {}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::Asn1(err) => write!(f, "ASN.1 error: {}", err),
+ Error::PublicKey(err) => write!(f, "public key error: {}", err),
+ Error::Signature(err) => write!(f, "signature error: {}", err),
+ }
+ }
+}
+
+impl From<der::Error> for Error {
+ fn from(err: der::Error) -> Error {
+ Error::Asn1(err)
+ }
+}
+
+impl From<spki::Error> for Error {
+ fn from(err: spki::Error) -> Error {
+ Error::PublicKey(err)
+ }
+}
+
+impl From<signature::Error> for Error {
+ fn from(err: signature::Error) -> Error {
+ Error::Signature(err)
+ }
+}
+
+type Result<T> = core::result::Result<T, Error>;
+
+/// The type of certificate to build
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum Profile {
+ /// Build a root CA certificate
+ Root,
+ /// Build an intermediate sub CA certificate
+ SubCA {
+ /// issuer Name,
+ /// represents the name signing the certificate
+ issuer: Name,
+ /// pathLenConstraint INTEGER (0..MAX) OPTIONAL
+ /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9].
+ path_len_constraint: Option<u8>,
+ },
+ /// Build an end certificate
+ Leaf {
+ /// issuer Name,
+ /// represents the name signing the certificate
+ issuer: Name,
+ /// should the key agreement flag of KeyUsage be enabled
+ enable_key_agreement: bool,
+ /// should the key encipherment flag of KeyUsage be enabled
+ enable_key_encipherment: bool,
+ /// should the subject key identifier extension be included
+ ///
+ /// From [RFC 5280 Section 4.2.1.2]:
+ /// For end entity certificates, subject key identifiers SHOULD be
+ /// derived from the public key. Two common methods for generating key
+ /// identifiers from the public key are identified above.
+ #[cfg(feature = "hazmat")]
+ include_subject_key_identifier: bool,
+ },
+ #[cfg(feature = "hazmat")]
+ /// Opt-out of the default extensions
+ Manual {
+ /// issuer Name,
+ /// represents the name signing the certificate
+ /// A `None` will make it a self-signed certificate
+ issuer: Option<Name>,
+ },
+}
+
+impl Profile {
+ fn get_issuer(&self, subject: &Name) -> Name {
+ match self {
+ Profile::Root => subject.clone(),
+ Profile::SubCA { issuer, .. } => issuer.clone(),
+ Profile::Leaf { issuer, .. } => issuer.clone(),
+ #[cfg(feature = "hazmat")]
+ Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(),
+ }
+ }
+
+ fn build_extensions(
+ &self,
+ spk: SubjectPublicKeyInfoRef<'_>,
+ issuer_spk: SubjectPublicKeyInfoRef<'_>,
+ tbs: &TbsCertificate,
+ ) -> Result<vec::Vec<Extension>> {
+ #[cfg(feature = "hazmat")]
+ // User opted out of default extensions set.
+ if let Profile::Manual { .. } = self {
+ return Ok(vec::Vec::default());
+ }
+
+ let mut extensions: vec::Vec<Extension> = vec::Vec::new();
+
+ match self {
+ #[cfg(feature = "hazmat")]
+ Profile::Leaf {
+ include_subject_key_identifier: false,
+ ..
+ } => {}
+ _ => extensions.push(
+ SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?,
+ ),
+ }
+
+ // Build Authority Key Identifier
+ match self {
+ Profile::Root => {}
+ _ => {
+ extensions.push(
+ AuthorityKeyIdentifier::try_from(issuer_spk.clone())?
+ .to_extension(&tbs.subject, &extensions)?,
+ );
+ }
+ }
+
+ // Build Basic Contraints extensions
+ extensions.push(match self {
+ Profile::Root => BasicConstraints {
+ ca: true,
+ path_len_constraint: None,
+ }
+ .to_extension(&tbs.subject, &extensions)?,
+ Profile::SubCA {
+ path_len_constraint,
+ ..
+ } => BasicConstraints {
+ ca: true,
+ path_len_constraint: *path_len_constraint,
+ }
+ .to_extension(&tbs.subject, &extensions)?,
+ Profile::Leaf { .. } => BasicConstraints {
+ ca: false,
+ path_len_constraint: None,
+ }
+ .to_extension(&tbs.subject, &extensions)?,
+ #[cfg(feature = "hazmat")]
+ Profile::Manual { .. } => unreachable!(),
+ });
+
+ // Build Key Usage extension
+ match self {
+ Profile::Root | Profile::SubCA { .. } => {
+ extensions.push(
+ KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign)
+ .to_extension(&tbs.subject, &extensions)?,
+ );
+ }
+ Profile::Leaf {
+ enable_key_agreement,
+ enable_key_encipherment,
+ ..
+ } => {
+ let mut key_usage = KeyUsages::DigitalSignature | KeyUsages::NonRepudiation;
+ if *enable_key_encipherment {
+ key_usage |= KeyUsages::KeyEncipherment;
+ }
+ if *enable_key_agreement {
+ key_usage |= KeyUsages::KeyAgreement;
+ }
+
+ extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?);
+ }
+ #[cfg(feature = "hazmat")]
+ Profile::Manual { .. } => unreachable!(),
+ }
+
+ Ok(extensions)
+ }
+}
+
+/// X509 Certificate builder
+///
+/// ```
+/// use der::Decode;
+/// use x509_cert::spki::SubjectPublicKeyInfoOwned;
+/// use x509_cert::builder::{CertificateBuilder, Profile};
+/// use x509_cert::name::Name;
+/// use x509_cert::serial_number::SerialNumber;
+/// use x509_cert::time::Validity;
+/// use std::str::FromStr;
+///
+/// # const RSA_2048_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-pub.der");
+/// # const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-priv.der");
+/// # use rsa::{pkcs1v15::SigningKey, pkcs1::DecodeRsaPrivateKey};
+/// # use sha2::Sha256;
+/// # use std::time::Duration;
+/// # use der::referenced::RefToOwned;
+/// # fn rsa_signer() -> SigningKey<Sha256> {
+/// # let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap();
+/// # let signing_key = SigningKey::<Sha256>::new_with_prefix(private_key);
+/// # signing_key
+/// # }
+///
+/// let serial_number = SerialNumber::from(42u32);
+/// let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+/// let profile = Profile::Root;
+/// let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+///
+/// let pub_key = SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key");
+///
+/// let mut signer = rsa_signer();
+/// let mut builder = CertificateBuilder::new(
+/// profile,
+/// serial_number,
+/// validity,
+/// subject,
+/// pub_key,
+/// &signer,
+/// )
+/// .expect("Create certificate");
+/// ```
+pub struct CertificateBuilder<'s, S> {
+ tbs: TbsCertificate,
+ extensions: Extensions,
+ cert_signer: &'s S,
+}
+
+impl<'s, S> CertificateBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ /// Creates a new certificate builder
+ pub fn new(
+ profile: Profile,
+ serial_number: SerialNumber,
+ mut validity: Validity,
+ subject: Name,
+ subject_public_key_info: SubjectPublicKeyInfoOwned,
+ cert_signer: &'s S,
+ ) -> Result<Self> {
+ let verifying_key = cert_signer.verifying_key();
+ let signer_pub = verifying_key
+ .to_public_key_der()?
+ .decode_msg::<SubjectPublicKeyInfoOwned>()?;
+
+ let signature_alg = cert_signer.signature_algorithm_identifier()?;
+ let issuer = profile.get_issuer(&subject);
+
+ validity.not_before.rfc5280_adjust_utc_time()?;
+ validity.not_after.rfc5280_adjust_utc_time()?;
+
+ let tbs = TbsCertificate {
+ version: Version::V3,
+ serial_number,
+ signature: signature_alg,
+ issuer,
+ validity,
+ subject,
+ subject_public_key_info,
+ extensions: None,
+
+ // We will not generate unique identifier because as per RFC5280 Section 4.1.2.8:
+ // CAs conforming to this profile MUST NOT generate
+ // certificates with unique identifiers.
+ //
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.8
+ issuer_unique_id: None,
+ subject_unique_id: None,
+ };
+
+ let extensions = profile.build_extensions(
+ tbs.subject_public_key_info.owned_to_ref(),
+ signer_pub.owned_to_ref(),
+ &tbs,
+ )?;
+ Ok(Self {
+ tbs,
+ extensions,
+ cert_signer,
+ })
+ }
+
+ /// Add an extension to this certificate
+ pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
+ let ext = extension.to_extension(&self.tbs.subject, &self.extensions)?;
+ self.extensions.push(ext);
+
+ Ok(())
+ }
+}
+
+/// Builder for X509 Certificate Requests
+///
+/// ```
+/// # use p256::{pkcs8::DecodePrivateKey, NistP256, ecdsa::DerSignature};
+/// # const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("../tests/examples/p256-priv.der");
+/// # fn ecdsa_signer() -> ecdsa::SigningKey<NistP256> {
+/// # let secret_key = p256::SecretKey::from_pkcs8_der(PKCS8_PRIVATE_KEY_DER).unwrap();
+/// # ecdsa::SigningKey::from(secret_key)
+/// # }
+/// use x509_cert::{
+/// builder::{Builder, RequestBuilder},
+/// ext::pkix::{name::GeneralName, SubjectAltName},
+/// name::Name,
+/// };
+/// use std::str::FromStr;
+///
+/// use std::net::{IpAddr, Ipv4Addr};
+/// let subject = Name::from_str("CN=service.domination.world").unwrap();
+///
+/// let signer = ecdsa_signer();
+/// let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request");
+/// builder
+/// .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4(
+/// Ipv4Addr::new(192, 0, 2, 0),
+/// ))]))
+/// .unwrap();
+///
+/// let cert_req = builder.build::<DerSignature>().unwrap();
+/// ```
+pub struct RequestBuilder<'s, S> {
+ info: CertReqInfo,
+ extension_req: ExtensionReq,
+ req_signer: &'s S,
+}
+
+impl<'s, S> RequestBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ /// Creates a new certificate request builder
+ pub fn new(subject: Name, req_signer: &'s S) -> Result<Self> {
+ let version = Default::default();
+ let verifying_key = req_signer.verifying_key();
+ let public_key = verifying_key
+ .to_public_key_der()?
+ .decode_msg::<SubjectPublicKeyInfoOwned>()?;
+ let attributes = Default::default();
+ let extension_req = Default::default();
+
+ Ok(Self {
+ info: CertReqInfo {
+ version,
+ subject,
+ public_key,
+ attributes,
+ },
+ extension_req,
+ req_signer,
+ })
+ }
+
+ /// Add an extension to this certificate request
+ pub fn add_extension<E: AsExtension>(&mut self, extension: &E) -> Result<()> {
+ let ext = extension.to_extension(&self.info.subject, &self.extension_req.0)?;
+
+ self.extension_req.0.push(ext);
+
+ Ok(())
+ }
+
+ /// Add an attribute to this certificate request
+ pub fn add_attribute<A: AsAttribute>(&mut self, attribute: &A) -> Result<()> {
+ let attr = attribute.to_attribute()?;
+
+ self.info.attributes.insert(attr)?;
+ Ok(())
+ }
+}
+
+/// Trait for X509 builders
+///
+/// This trait defines the interface between builder and the signers.
+pub trait Builder: Sized {
+ /// The builder's object signer
+ type Signer;
+
+ /// Type built by this builder
+ type Output: Sized;
+
+ /// Return a reference to the signer.
+ fn signer(&self) -> &Self::Signer;
+
+ /// Assemble the final object from signature.
+ fn assemble(self, signature: BitString) -> Result<Self::Output>;
+
+ /// Finalize and return a serialization of the object for signature.
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>>;
+
+ /// Run the object through the signer and build it.
+ fn build<Signature>(mut self) -> Result<Self::Output>
+ where
+ Self::Signer: Signer<Signature>,
+ Signature: SignatureBitStringEncoding,
+ {
+ let blob = self.finalize()?;
+
+ let signature = self.signer().try_sign(&blob)?.to_bitstring()?;
+
+ self.assemble(signature)
+ }
+
+ /// Run the object through the signer and build it.
+ fn build_with_rng<Signature>(mut self, rng: &mut impl CryptoRngCore) -> Result<Self::Output>
+ where
+ Self::Signer: RandomizedSigner<Signature>,
+ Signature: SignatureBitStringEncoding,
+ {
+ let blob = self.finalize()?;
+
+ let signature = self
+ .signer()
+ .try_sign_with_rng(rng, &blob)?
+ .to_bitstring()?;
+
+ self.assemble(signature)
+ }
+}
+
+impl<'s, S> Builder for CertificateBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ type Signer = S;
+ type Output = Certificate;
+
+ fn signer(&self) -> &Self::Signer {
+ self.cert_signer
+ }
+
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>> {
+ if !self.extensions.is_empty() {
+ self.tbs.extensions = Some(self.extensions.clone());
+ }
+
+ if self.tbs.extensions.is_none() {
+ if self.tbs.issuer_unique_id.is_some() || self.tbs.subject_unique_id.is_some() {
+ self.tbs.version = Version::V2;
+ } else {
+ self.tbs.version = Version::V1;
+ }
+ }
+
+ self.tbs.to_der()
+ }
+
+ fn assemble(self, signature: BitString) -> Result<Self::Output> {
+ let signature_algorithm = self.tbs.signature.clone();
+
+ Ok(Certificate {
+ tbs_certificate: self.tbs,
+ signature_algorithm,
+ signature,
+ })
+ }
+}
+
+impl<'s, S> Builder for RequestBuilder<'s, S>
+where
+ S: Keypair + DynSignatureAlgorithmIdentifier,
+ S::VerifyingKey: EncodePublicKey,
+{
+ type Signer = S;
+ type Output = CertReq;
+
+ fn signer(&self) -> &Self::Signer {
+ self.req_signer
+ }
+
+ fn finalize(&mut self) -> der::Result<vec::Vec<u8>> {
+ self.info
+ .attributes
+ .insert(self.extension_req.clone().try_into()?)?;
+
+ self.info.to_der()
+ }
+
+ fn assemble(self, signature: BitString) -> Result<Self::Output> {
+ let algorithm = self.req_signer.signature_algorithm_identifier()?;
+
+ Ok(CertReq {
+ info: self.info,
+ algorithm,
+ signature,
+ })
+ }
+}
diff --git a/src/certificate.rs b/src/certificate.rs
index cf71948..c5a105d 100644
--- a/src/certificate.rs
+++ b/src/certificate.rs
@@ -1,17 +1,55 @@
//! Certificate types
-use crate::{name::Name, time::Validity};
-
+use crate::{name::Name, serial_number::SerialNumber, time::Validity};
use alloc::vec::Vec;
-use core::cmp::Ordering;
-
use const_oid::AssociatedOid;
-use der::asn1::{BitStringRef, UIntRef};
-use der::{Decode, Enumerated, Error, ErrorKind, Sequence, ValueOrd};
-use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo};
+use core::{cmp::Ordering, fmt::Debug};
+use der::asn1::BitString;
+use der::{Decode, Enumerated, Error, ErrorKind, Sequence, Tag, ValueOrd};
+use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned};
#[cfg(feature = "pem")]
-use der::pem::PemLabel;
+use der::{
+ pem::{self, PemLabel},
+ DecodePem,
+};
+
+/// [`Profile`] allows the consumer of this crate to customize the behavior when parsing
+/// certificates.
+/// By default, parsing will be made in a rfc5280-compliant manner.
+pub trait Profile: PartialEq + Debug + Eq + Clone {
+ /// Checks to run when parsing serial numbers
+ fn check_serial_number(serial: &SerialNumber<Self>) -> der::Result<()> {
+ // See the note in `SerialNumber::new`: we permit lengths of 21 bytes here,
+ // since some X.509 implementations interpret the limit of 20 bytes to refer
+ // to the pre-encoded value.
+ if serial.inner.len() > SerialNumber::<Self>::MAX_DECODE_LEN {
+ Err(Tag::Integer.value_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// Parse certificates with rfc5280-compliant checks
+pub struct Rfc5280;
+
+impl Profile for Rfc5280 {}
+
+#[cfg(feature = "hazmat")]
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
+#[derive(Debug, PartialEq, Eq, Clone)]
+/// Parse raw x509 certificate and disable all the checks
+pub struct Raw;
+
+#[cfg(feature = "hazmat")]
+impl Profile for Raw {
+ fn check_serial_number(_serial: &SerialNumber<Self>) -> der::Result<()> {
+ Ok(())
+ }
+}
/// Certificate `Version` as defined in [RFC 5280 Section 4.1].
///
@@ -20,6 +58,7 @@ use der::pem::PemLabel;
/// ```
///
/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
#[asn1(type = "INTEGER")]
#[repr(u8)]
@@ -36,7 +75,7 @@ pub enum Version {
impl ValueOrd for Version {
fn value_cmp(&self, other: &Self) -> der::Result<Ordering> {
- (&(*self as u8)).value_cmp(&(*other as u8))
+ (*self as u8).value_cmp(&(*other as u8))
}
}
@@ -47,6 +86,9 @@ impl Default for Version {
}
/// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1]
+pub type TbsCertificate = TbsCertificateInner<Rfc5280>;
+
+/// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1]
///
/// ASN.1 structure containing the names of the subject and issuer, a public
/// key associated with the subject, a validity period, and other associated
@@ -71,9 +113,10 @@ impl Default for Version {
/// ```
///
/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct TbsCertificate<'a> {
+pub struct TbsCertificateInner<P: Profile = Rfc5280> {
/// The certificate version
///
/// Note that this value defaults to Version 1 per the RFC. However,
@@ -83,32 +126,30 @@ pub struct TbsCertificate<'a> {
#[asn1(context_specific = "0", default = "Default::default")]
pub version: Version,
- pub serial_number: UIntRef<'a>,
- pub signature: AlgorithmIdentifier<'a>,
- pub issuer: Name<'a>,
+ pub serial_number: SerialNumber<P>,
+ pub signature: AlgorithmIdentifierOwned,
+ pub issuer: Name,
pub validity: Validity,
- pub subject: Name<'a>,
- pub subject_public_key_info: SubjectPublicKeyInfo<'a>,
+ pub subject: Name,
+ pub subject_public_key_info: SubjectPublicKeyInfoOwned,
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")]
- pub issuer_unique_id: Option<BitStringRef<'a>>,
+ pub issuer_unique_id: Option<BitString>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
- pub subject_unique_id: Option<BitStringRef<'a>>,
+ pub subject_unique_id: Option<BitString>,
#[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")]
- pub extensions: Option<crate::ext::Extensions<'a>>,
+ pub extensions: Option<crate::ext::Extensions>,
}
-impl<'a> TbsCertificate<'a> {
+impl<P: Profile> TbsCertificateInner<P> {
/// Decodes a single extension
///
/// Returns an error if multiple of these extensions is present. Returns
/// `Ok(None)` if the extension is not present. Returns a decoding error
/// if decoding failed. Otherwise returns the extension.
- pub fn get<'b: 'a, T: Decode<'a> + AssociatedOid>(
- &'b self,
- ) -> Result<Option<(bool, T)>, Error> {
+ pub fn get<'a, T: Decode<'a> + AssociatedOid>(&'a self) -> Result<Option<(bool, T)>, Error> {
let mut iter = self.filter::<T>().peekable();
match iter.next() {
None => Ok(None),
@@ -122,20 +163,25 @@ impl<'a> TbsCertificate<'a> {
/// Filters extensions by an associated OID
///
/// Returns a filtered iterator over all the extensions with the OID.
- pub fn filter<'b: 'a, T: Decode<'a> + AssociatedOid>(
- &'b self,
- ) -> impl 'b + Iterator<Item = Result<(bool, T), Error>> {
+ pub fn filter<'a, T: Decode<'a> + AssociatedOid>(
+ &'a self,
+ ) -> impl 'a + Iterator<Item = Result<(bool, T), Error>> {
self.extensions
.as_deref()
.unwrap_or(&[])
.iter()
.filter(|e| e.extn_id == T::OID)
- .map(|e| Ok((e.critical, T::from_der(e.extn_value)?)))
+ .map(|e| Ok((e.critical, T::from_der(e.extn_value.as_bytes())?)))
}
}
/// X.509 certificates are defined in [RFC 5280 Section 4.1].
///
+/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+pub type Certificate = CertificateInner<Rfc5280>;
+
+/// X.509 certificates are defined in [RFC 5280 Section 4.1].
+///
/// ```text
/// Certificate ::= SEQUENCE {
/// tbsCertificate TBSCertificate,
@@ -145,17 +191,17 @@ impl<'a> TbsCertificate<'a> {
/// ```
///
/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct Certificate<'a> {
- pub tbs_certificate: TbsCertificate<'a>,
- pub signature_algorithm: AlgorithmIdentifier<'a>,
- pub signature: BitStringRef<'a>,
+pub struct CertificateInner<P: Profile = Rfc5280> {
+ pub tbs_certificate: TbsCertificateInner<P>,
+ pub signature_algorithm: AlgorithmIdentifierOwned,
+ pub signature: BitString,
}
#[cfg(feature = "pem")]
-#[cfg_attr(docsrs, doc(cfg(feature = "pem")))]
-impl PemLabel for Certificate<'_> {
+impl<P: Profile> PemLabel for CertificateInner<P> {
const PEM_LABEL: &'static str = "CERTIFICATE";
}
@@ -170,4 +216,56 @@ impl PemLabel for Certificate<'_> {
/// ```
///
/// [RFC 6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-10.1
-pub type PkiPath<'a> = Vec<Certificate<'a>>;
+pub type PkiPath = Vec<Certificate>;
+
+#[cfg(feature = "pem")]
+impl<P: Profile> CertificateInner<P> {
+ /// Parse a chain of pem-encoded certificates from a slice.
+ ///
+ /// Returns the list of certificates.
+ pub fn load_pem_chain(mut input: &[u8]) -> Result<Vec<Self>, Error> {
+ fn find_boundary<T>(haystack: &[T], needle: &[T]) -> Option<usize>
+ where
+ for<'a> &'a [T]: PartialEq,
+ {
+ haystack
+ .windows(needle.len())
+ .position(|window| window == needle)
+ }
+
+ let mut certs = Vec::new();
+ let mut position: usize = 0;
+
+ let end_boundary = &b"-----END CERTIFICATE-----"[..];
+
+ // Strip the trailing whitespaces
+ loop {
+ if input.is_empty() {
+ break;
+ }
+ let last_pos = input.len() - 1;
+
+ match input.get(last_pos) {
+ Some(b'\r') | Some(b'\n') => {
+ input = &input[..last_pos];
+ }
+ _ => break,
+ }
+ }
+
+ while position < input.len() - 1 {
+ let rest = &input[position..];
+ let end_pos = find_boundary(rest, end_boundary)
+ .ok_or(pem::Error::PostEncapsulationBoundary)?
+ + end_boundary.len();
+
+ let cert_buf = &rest[..end_pos];
+ let cert = Self::from_pem(cert_buf)?;
+ certs.push(cert);
+
+ position += end_pos;
+ }
+
+ Ok(certs)
+ }
+}
diff --git a/src/crl.rs b/src/crl.rs
index 09256f2..6916783 100644
--- a/src/crl.rs
+++ b/src/crl.rs
@@ -2,14 +2,15 @@
use crate::ext::Extensions;
use crate::name::Name;
+use crate::serial_number::SerialNumber;
use crate::time::Time;
use crate::Version;
use alloc::vec::Vec;
-use der::asn1::{BitStringRef, UIntRef};
+use der::asn1::BitString;
use der::{Sequence, ValueOrd};
-use spki::AlgorithmIdentifier;
+use spki::AlgorithmIdentifierOwned;
/// `CertificateList` as defined in [RFC 5280 Section 5.1].
///
@@ -24,10 +25,10 @@ use spki::AlgorithmIdentifier;
/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct CertificateList<'a> {
- pub tbs_cert_list: TbsCertList<'a>,
- pub signature_algorithm: AlgorithmIdentifier<'a>,
- pub signature: BitStringRef<'a>,
+pub struct CertificateList {
+ pub tbs_cert_list: TbsCertList,
+ pub signature_algorithm: AlgorithmIdentifierOwned,
+ pub signature: BitString,
}
/// Implicit intermediate structure from the ASN.1 definition of `TBSCertList`.
@@ -46,10 +47,10 @@ pub struct CertificateList<'a> {
/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct RevokedCert<'a> {
- pub serial_number: UIntRef<'a>,
+pub struct RevokedCert {
+ pub serial_number: SerialNumber,
pub revocation_date: Time,
- pub crl_entry_extensions: Option<Extensions<'a>>,
+ pub crl_entry_extensions: Option<Extensions>,
}
/// `TbsCertList` as defined in [RFC 5280 Section 5.1].
@@ -73,14 +74,14 @@ pub struct RevokedCert<'a> {
/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct TbsCertList<'a> {
+pub struct TbsCertList {
pub version: Version,
- pub signature: AlgorithmIdentifier<'a>,
- pub issuer: Name<'a>,
+ pub signature: AlgorithmIdentifierOwned,
+ pub issuer: Name,
pub this_update: Time,
pub next_update: Option<Time>,
- pub revoked_certificates: Option<Vec<RevokedCert<'a>>>,
+ pub revoked_certificates: Option<Vec<RevokedCert>>,
#[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")]
- pub crl_extensions: Option<Extensions<'a>>,
+ pub crl_extensions: Option<Extensions>,
}
diff --git a/src/ext.rs b/src/ext.rs
index eb6f6fb..aa663c5 100644
--- a/src/ext.rs
+++ b/src/ext.rs
@@ -1,6 +1,7 @@
//! Standardized X.509 Certificate Extensions
-use der::{Sequence, ValueOrd};
+use const_oid::AssociatedOid;
+use der::{asn1::OctetString, Sequence, ValueOrd};
use spki::ObjectIdentifier;
pub mod pkix;
@@ -22,16 +23,16 @@ pub mod pkix;
/// ```
///
/// [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct Extension<'a> {
+pub struct Extension {
pub extn_id: ObjectIdentifier,
#[asn1(default = "Default::default")]
pub critical: bool,
- #[asn1(type = "OCTET STRING")]
- pub extn_value: &'a [u8],
+ pub extn_value: OctetString,
}
/// Extensions as defined in [RFC 5280 Section 4.1.2.9].
@@ -41,4 +42,26 @@ pub struct Extension<'a> {
/// ```
///
/// [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9
-pub type Extensions<'a> = alloc::vec::Vec<Extension<'a>>;
+pub type Extensions = alloc::vec::Vec<Extension>;
+
+/// Trait to be implemented by extensions to allow them to be formated as x509 v3 extensions by
+/// builder.
+pub trait AsExtension: AssociatedOid + der::Encode {
+ /// Should the extension be marked critical
+ fn critical(&self, subject: &crate::name::Name, extensions: &[Extension]) -> bool;
+
+ /// Returns the Extension with the content encoded.
+ fn to_extension(
+ &self,
+ subject: &crate::name::Name,
+ extensions: &[Extension],
+ ) -> Result<Extension, der::Error> {
+ let content = OctetString::new(<Self as der::Encode>::to_der(self)?)?;
+
+ Ok(Extension {
+ extn_id: <Self as AssociatedOid>::OID,
+ critical: self.critical(subject, extensions),
+ extn_value: content,
+ })
+ }
+}
diff --git a/src/ext/pkix.rs b/src/ext/pkix.rs
index dec0659..95a5e2b 100644
--- a/src/ext/pkix.rs
+++ b/src/ext/pkix.rs
@@ -31,7 +31,7 @@ pub use const_oid::db::rfc5280::{
use alloc::vec::Vec;
-use der::asn1::OctetStringRef;
+use der::asn1::OctetString;
/// SubjectKeyIdentifier as defined in [RFC 5280 Section 4.2.1.2].
///
@@ -40,14 +40,19 @@ use der::asn1::OctetStringRef;
/// ```
///
/// [RFC 5280 Section 4.2.1.2]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct SubjectKeyIdentifier<'a>(pub OctetStringRef<'a>);
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct SubjectKeyIdentifier(pub OctetString);
-impl<'a> AssociatedOid for SubjectKeyIdentifier<'a> {
+impl AssociatedOid for SubjectKeyIdentifier {
const OID: ObjectIdentifier = ID_CE_SUBJECT_KEY_IDENTIFIER;
}
-impl_newtype!(SubjectKeyIdentifier<'a>, OctetStringRef<'a>);
+impl_newtype!(SubjectKeyIdentifier, OctetString);
+impl_extension!(SubjectKeyIdentifier, critical = false);
+impl_key_identifier!(
+ SubjectKeyIdentifier,
+ (|result: &[u8]| Ok(Self(OctetString::new(result)?)))
+);
/// SubjectAltName as defined in [RFC 5280 Section 4.2.1.6].
///
@@ -57,13 +62,30 @@ impl_newtype!(SubjectKeyIdentifier<'a>, OctetStringRef<'a>);
///
/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct SubjectAltName<'a>(pub name::GeneralNames<'a>);
+pub struct SubjectAltName(pub name::GeneralNames);
-impl<'a> AssociatedOid for SubjectAltName<'a> {
+impl AssociatedOid for SubjectAltName {
const OID: ObjectIdentifier = ID_CE_SUBJECT_ALT_NAME;
}
-impl_newtype!(SubjectAltName<'a>, name::GeneralNames<'a>);
+impl_newtype!(SubjectAltName, name::GeneralNames);
+
+impl crate::ext::AsExtension for SubjectAltName {
+ fn critical(&self, subject: &crate::name::Name, _extensions: &[super::Extension]) -> bool {
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
+ // Further, if the only subject identity included in the certificate is
+ // an alternative name form (e.g., an electronic mail address), then the
+ // subject distinguished name MUST be empty (an empty sequence), and the
+ // subjectAltName extension MUST be present. If the subject field
+ // contains an empty sequence, then the issuing CA MUST include a
+ // subjectAltName extension that is marked as critical. When including
+ // the subjectAltName extension in a certificate that has a non-empty
+ // subject distinguished name, conforming CAs SHOULD mark the
+ // subjectAltName extension as non-critical.
+
+ subject.is_empty()
+ }
+}
/// IssuerAltName as defined in [RFC 5280 Section 4.2.1.7].
///
@@ -73,13 +95,14 @@ impl_newtype!(SubjectAltName<'a>, name::GeneralNames<'a>);
///
/// [RFC 5280 Section 4.2.1.7]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.7
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct IssuerAltName<'a>(pub name::GeneralNames<'a>);
+pub struct IssuerAltName(pub name::GeneralNames);
-impl<'a> AssociatedOid for IssuerAltName<'a> {
+impl AssociatedOid for IssuerAltName {
const OID: ObjectIdentifier = ID_CE_ISSUER_ALT_NAME;
}
-impl_newtype!(IssuerAltName<'a>, name::GeneralNames<'a>);
+impl_newtype!(IssuerAltName, name::GeneralNames);
+impl_extension!(IssuerAltName, critical = false);
/// SubjectDirectoryAttributes as defined in [RFC 5280 Section 4.2.1.8].
///
@@ -89,16 +112,14 @@ impl_newtype!(IssuerAltName<'a>, name::GeneralNames<'a>);
///
/// [RFC 5280 Section 4.2.1.8]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.8
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct SubjectDirectoryAttributes<'a>(pub Vec<AttributeTypeAndValue<'a>>);
+pub struct SubjectDirectoryAttributes(pub Vec<AttributeTypeAndValue>);
-impl<'a> AssociatedOid for SubjectDirectoryAttributes<'a> {
+impl AssociatedOid for SubjectDirectoryAttributes {
const OID: ObjectIdentifier = ID_CE_SUBJECT_DIRECTORY_ATTRIBUTES;
}
-impl_newtype!(
- SubjectDirectoryAttributes<'a>,
- Vec<AttributeTypeAndValue<'a>>
-);
+impl_newtype!(SubjectDirectoryAttributes, Vec<AttributeTypeAndValue>);
+impl_extension!(SubjectDirectoryAttributes, critical = false);
/// InhibitAnyPolicy as defined in [RFC 5280 Section 4.2.1.14].
///
@@ -115,3 +136,4 @@ impl AssociatedOid for InhibitAnyPolicy {
}
impl_newtype!(InhibitAnyPolicy, u32);
+impl_extension!(InhibitAnyPolicy, critical = true);
diff --git a/src/ext/pkix/access.rs b/src/ext/pkix/access.rs
index 0390235..4d2d9db 100644
--- a/src/ext/pkix/access.rs
+++ b/src/ext/pkix/access.rs
@@ -16,13 +16,14 @@ use der::{asn1::ObjectIdentifier, Sequence, ValueOrd};
///
/// [RFC 5280 Section 4.2.2.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct AuthorityInfoAccessSyntax<'a>(pub Vec<AccessDescription<'a>>);
+pub struct AuthorityInfoAccessSyntax(pub Vec<AccessDescription>);
-impl<'a> AssociatedOid for AuthorityInfoAccessSyntax<'a> {
+impl AssociatedOid for AuthorityInfoAccessSyntax {
const OID: ObjectIdentifier = ID_PE_AUTHORITY_INFO_ACCESS;
}
-impl_newtype!(AuthorityInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>);
+impl_newtype!(AuthorityInfoAccessSyntax, Vec<AccessDescription>);
+impl_extension!(AuthorityInfoAccessSyntax, critical = false);
/// SubjectInfoAccessSyntax as defined in [RFC 5280 Section 4.2.2.2].
///
@@ -32,13 +33,14 @@ impl_newtype!(AuthorityInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>);
///
/// [RFC 5280 Section 4.2.2.2]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.2
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct SubjectInfoAccessSyntax<'a>(pub Vec<AccessDescription<'a>>);
+pub struct SubjectInfoAccessSyntax(pub Vec<AccessDescription>);
-impl<'a> AssociatedOid for SubjectInfoAccessSyntax<'a> {
+impl AssociatedOid for SubjectInfoAccessSyntax {
const OID: ObjectIdentifier = ID_PE_SUBJECT_INFO_ACCESS;
}
-impl_newtype!(SubjectInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>);
+impl_newtype!(SubjectInfoAccessSyntax, Vec<AccessDescription>);
+impl_extension!(SubjectInfoAccessSyntax, critical = false);
/// AccessDescription as defined in [RFC 5280 Section 4.2.2.1].
///
@@ -52,7 +54,7 @@ impl_newtype!(SubjectInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>);
/// [RFC 5280 Section 4.2.2.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.2.1
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct AccessDescription<'a> {
+pub struct AccessDescription {
pub access_method: ObjectIdentifier,
- pub access_location: GeneralName<'a>,
+ pub access_location: GeneralName,
}
diff --git a/src/ext/pkix/authkeyid.rs b/src/ext/pkix/authkeyid.rs
index e7644f5..60b61e5 100644
--- a/src/ext/pkix/authkeyid.rs
+++ b/src/ext/pkix/authkeyid.rs
@@ -1,8 +1,9 @@
use super::name::GeneralNames;
+use crate::serial_number::SerialNumber;
use const_oid::db::rfc5280::ID_CE_AUTHORITY_KEY_IDENTIFIER;
use const_oid::{AssociatedOid, ObjectIdentifier};
-use der::asn1::{OctetStringRef, UIntRef};
+use der::asn1::OctetString;
use der::Sequence;
/// AuthorityKeyIdentifier as defined in [RFC 5280 Section 4.2.1.1].
@@ -18,19 +19,28 @@ use der::Sequence;
/// ```
///
/// [RFC 5280 Section 4.2.1.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.1
-#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
+#[derive(Clone, Debug, Eq, PartialEq, Sequence, Default)]
#[allow(missing_docs)]
-pub struct AuthorityKeyIdentifier<'a> {
+pub struct AuthorityKeyIdentifier {
#[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")]
- pub key_identifier: Option<OctetStringRef<'a>>,
+ pub key_identifier: Option<OctetString>,
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")]
- pub authority_cert_issuer: Option<GeneralNames<'a>>,
+ pub authority_cert_issuer: Option<GeneralNames>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
- pub authority_cert_serial_number: Option<UIntRef<'a>>,
+ pub authority_cert_serial_number: Option<SerialNumber>,
}
-impl<'a> AssociatedOid for AuthorityKeyIdentifier<'a> {
+impl AssociatedOid for AuthorityKeyIdentifier {
const OID: ObjectIdentifier = ID_CE_AUTHORITY_KEY_IDENTIFIER;
}
+
+impl_extension!(AuthorityKeyIdentifier, critical = false);
+impl_key_identifier!(
+ AuthorityKeyIdentifier,
+ (|result: &[u8]| Ok(Self {
+ key_identifier: Some(OctetString::new(result)?),
+ ..Default::default()
+ }))
+);
diff --git a/src/ext/pkix/certpolicy.rs b/src/ext/pkix/certpolicy.rs
index bb2211f..85820c1 100644
--- a/src/ext/pkix/certpolicy.rs
+++ b/src/ext/pkix/certpolicy.rs
@@ -1,11 +1,11 @@
//! PKIX Certificate Policies extension
-use alloc::vec::Vec;
+use alloc::{string::String, vec::Vec};
use const_oid::db::rfc5912::ID_CE_CERTIFICATE_POLICIES;
use const_oid::AssociatedOid;
-use der::asn1::{GeneralizedTime, Ia5StringRef, ObjectIdentifier, UIntRef, Utf8StringRef};
-use der::{AnyRef, Choice, Sequence, ValueOrd};
+use der::asn1::{GeneralizedTime, Ia5String, ObjectIdentifier, Uint};
+use der::{Any, Choice, Sequence, ValueOrd};
/// CertificatePolicies as defined in [RFC 5280 Section 4.2.1.4].
///
@@ -14,14 +14,19 @@ use der::{AnyRef, Choice, Sequence, ValueOrd};
/// ```
///
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
+// If this extension is
+// critical, the path validation software MUST be able to interpret this
+// extension (including the optional qualifier), or MUST reject the
+// certificate.
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct CertificatePolicies<'a>(pub Vec<PolicyInformation<'a>>);
+pub struct CertificatePolicies(pub Vec<PolicyInformation>);
-impl<'a> AssociatedOid for CertificatePolicies<'a> {
+impl AssociatedOid for CertificatePolicies {
const OID: ObjectIdentifier = ID_CE_CERTIFICATE_POLICIES;
}
-impl_newtype!(CertificatePolicies<'a>, Vec<PolicyInformation<'a>>);
+impl_newtype!(CertificatePolicies, Vec<PolicyInformation>);
+impl_extension!(CertificatePolicies);
/// PolicyInformation as defined in [RFC 5280 Section 4.2.1.4].
///
@@ -37,9 +42,9 @@ impl_newtype!(CertificatePolicies<'a>, Vec<PolicyInformation<'a>>);
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct PolicyInformation<'a> {
+pub struct PolicyInformation {
pub policy_identifier: ObjectIdentifier,
- pub policy_qualifiers: Option<Vec<PolicyQualifierInfo<'a>>>,
+ pub policy_qualifiers: Option<Vec<PolicyQualifierInfo>>,
}
/// PolicyQualifierInfo as defined in [RFC 5280 Section 4.2.1.4].
@@ -54,9 +59,9 @@ pub struct PolicyInformation<'a> {
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct PolicyQualifierInfo<'a> {
+pub struct PolicyQualifierInfo {
pub policy_qualifier_id: ObjectIdentifier,
- pub qualifier: Option<AnyRef<'a>>,
+ pub qualifier: Option<Any>,
}
/// CpsUri as defined in [RFC 5280 Section 4.2.1.4].
@@ -66,7 +71,7 @@ pub struct PolicyQualifierInfo<'a> {
/// ```
///
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
-pub type CpsUri<'a> = Ia5StringRef<'a>;
+pub type CpsUri = Ia5String;
/// UserNotice as defined in [RFC 5280 Section 4.2.1.4].
///
@@ -80,9 +85,9 @@ pub type CpsUri<'a> = Ia5StringRef<'a>;
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct UserNotice<'a> {
+pub struct UserNotice {
pub notice_ref: Option<GeneralizedTime>,
- pub explicit_text: Option<DisplayText<'a>>,
+ pub explicit_text: Option<DisplayText>,
}
/// NoticeReference as defined in [RFC 5280 Section 4.2.1.4].
@@ -96,9 +101,9 @@ pub struct UserNotice<'a> {
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct NoticeReference<'a> {
- pub organization: DisplayText<'a>,
- pub notice_numbers: Option<Vec<UIntRef<'a>>>,
+pub struct NoticeReference {
+ pub organization: DisplayText,
+ pub notice_numbers: Option<Vec<Uint>>,
}
/// DisplayText as defined in [RFC 5280 Section 4.2.1.4].
@@ -117,10 +122,10 @@ pub struct NoticeReference<'a> {
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Choice, Clone, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
-pub enum DisplayText<'a> {
+pub enum DisplayText {
#[asn1(type = "IA5String")]
- Ia5String(Ia5StringRef<'a>),
+ Ia5String(Ia5String),
#[asn1(type = "UTF8String")]
- Utf8String(Utf8StringRef<'a>),
+ Utf8String(String),
}
diff --git a/src/ext/pkix/constraints/basic.rs b/src/ext/pkix/constraints/basic.rs
index 5972cc8..c50d8e8 100644
--- a/src/ext/pkix/constraints/basic.rs
+++ b/src/ext/pkix/constraints/basic.rs
@@ -22,3 +22,28 @@ pub struct BasicConstraints {
impl AssociatedOid for BasicConstraints {
const OID: ObjectIdentifier = ID_CE_BASIC_CONSTRAINTS;
}
+
+impl crate::ext::AsExtension for BasicConstraints {
+ fn critical(
+ &self,
+ _subject: &crate::name::Name,
+ _extensions: &[crate::ext::Extension],
+ ) -> bool {
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
+ // Conforming CAs MUST include this extension in all CA certificates
+ // that contain public keys used to validate digital signatures on
+ // certificates and MUST mark the extension as critical in such
+ // certificates. This extension MAY appear as a critical or non-
+ // critical extension in CA certificates that contain public keys used
+ // exclusively for purposes other than validating digital signatures on
+ // certificates. Such CA certificates include ones that contain public
+ // keys used exclusively for validating digital signatures on CRLs and
+ // ones that contain key management public keys used with certificate
+ // enrollment protocols. This extension MAY appear as a critical or
+ // non-critical extension in end entity certificates.
+
+ // NOTE(baloo): from the spec, it doesn't appear to hurt if we force the extension
+ // to be critical.
+ true
+ }
+}
diff --git a/src/ext/pkix/constraints/name.rs b/src/ext/pkix/constraints/name.rs
index 658f1e2..c05d005 100644
--- a/src/ext/pkix/constraints/name.rs
+++ b/src/ext/pkix/constraints/name.rs
@@ -19,18 +19,20 @@ use super::super::name::GeneralName;
/// [RFC 5280 Section 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct NameConstraints<'a> {
+pub struct NameConstraints {
#[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")]
- pub permitted_subtrees: Option<GeneralSubtrees<'a>>,
+ pub permitted_subtrees: Option<GeneralSubtrees>,
#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")]
- pub excluded_subtrees: Option<GeneralSubtrees<'a>>,
+ pub excluded_subtrees: Option<GeneralSubtrees>,
}
-impl<'a> AssociatedOid for NameConstraints<'a> {
+impl AssociatedOid for NameConstraints {
const OID: ObjectIdentifier = ID_CE_NAME_CONSTRAINTS;
}
+impl_extension!(NameConstraints, critical = true);
+
/// GeneralSubtrees as defined in [RFC 5280 Section 4.2.1.10].
///
/// ```text
@@ -38,7 +40,7 @@ impl<'a> AssociatedOid for NameConstraints<'a> {
/// ```
///
/// [RFC 5280 Section 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
-pub type GeneralSubtrees<'a> = Vec<GeneralSubtree<'a>>;
+pub type GeneralSubtrees = Vec<GeneralSubtree>;
/// GeneralSubtree as defined in [RFC 5280 Section 4.2.1.10].
///
@@ -53,8 +55,8 @@ pub type GeneralSubtrees<'a> = Vec<GeneralSubtree<'a>>;
/// [RFC 5280 Section 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct GeneralSubtree<'a> {
- pub base: GeneralName<'a>,
+pub struct GeneralSubtree {
+ pub base: GeneralName,
#[asn1(
context_specific = "0",
diff --git a/src/ext/pkix/constraints/policy.rs b/src/ext/pkix/constraints/policy.rs
index dcb1a8c..c409737 100644
--- a/src/ext/pkix/constraints/policy.rs
+++ b/src/ext/pkix/constraints/policy.rs
@@ -24,3 +24,5 @@ pub struct PolicyConstraints {
impl AssociatedOid for PolicyConstraints {
const OID: ObjectIdentifier = ID_CE_POLICY_CONSTRAINTS;
}
+
+impl_extension!(PolicyConstraints, critical = true);
diff --git a/src/ext/pkix/crl.rs b/src/ext/pkix/crl.rs
index d65b837..f3e9397 100644
--- a/src/ext/pkix/crl.rs
+++ b/src/ext/pkix/crl.rs
@@ -11,7 +11,7 @@ pub use dp::IssuingDistributionPoint;
use alloc::vec::Vec;
-use der::{asn1::UIntRef, Enumerated};
+use der::{asn1::Uint, Enumerated};
/// CrlNumber as defined in [RFC 5280 Section 5.2.3].
///
@@ -20,14 +20,15 @@ use der::{asn1::UIntRef, Enumerated};
/// ```
///
/// [RFC 5280 Section 5.2.3]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.3
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct CrlNumber<'a>(pub UIntRef<'a>);
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct CrlNumber(pub Uint);
-impl<'a> AssociatedOid for CrlNumber<'a> {
+impl AssociatedOid for CrlNumber {
const OID: ObjectIdentifier = ID_CE_CRL_NUMBER;
}
-impl_newtype!(CrlNumber<'a>, UIntRef<'a>);
+impl_newtype!(CrlNumber, Uint);
+impl_extension!(CrlNumber, critical = false);
/// BaseCRLNumber as defined in [RFC 5280 Section 5.2.4].
///
@@ -36,14 +37,15 @@ impl_newtype!(CrlNumber<'a>, UIntRef<'a>);
/// ```
///
/// [RFC 5280 Section 5.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.4
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct BaseCrlNumber<'a>(pub UIntRef<'a>);
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct BaseCrlNumber(pub Uint);
-impl<'a> AssociatedOid for BaseCrlNumber<'a> {
+impl AssociatedOid for BaseCrlNumber {
const OID: ObjectIdentifier = ID_CE_DELTA_CRL_INDICATOR;
}
-impl_newtype!(BaseCrlNumber<'a>, UIntRef<'a>);
+impl_newtype!(BaseCrlNumber, Uint);
+impl_extension!(BaseCrlNumber, critical = true);
/// CrlDistributionPoints as defined in [RFC 5280 Section 4.2.1.13].
///
@@ -53,13 +55,14 @@ impl_newtype!(BaseCrlNumber<'a>, UIntRef<'a>);
///
/// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct CrlDistributionPoints<'a>(pub Vec<dp::DistributionPoint<'a>>);
+pub struct CrlDistributionPoints(pub Vec<dp::DistributionPoint>);
-impl<'a> AssociatedOid for CrlDistributionPoints<'a> {
+impl AssociatedOid for CrlDistributionPoints {
const OID: ObjectIdentifier = ID_CE_CRL_DISTRIBUTION_POINTS;
}
-impl_newtype!(CrlDistributionPoints<'a>, Vec<dp::DistributionPoint<'a>>);
+impl_newtype!(CrlDistributionPoints, Vec<dp::DistributionPoint>);
+impl_extension!(CrlDistributionPoints, critical = false);
/// FreshestCrl as defined in [RFC 5280 Section 5.2.6].
///
@@ -69,13 +72,14 @@ impl_newtype!(CrlDistributionPoints<'a>, Vec<dp::DistributionPoint<'a>>);
///
/// [RFC 5280 Section 5.2.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.6
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct FreshestCrl<'a>(pub Vec<dp::DistributionPoint<'a>>);
+pub struct FreshestCrl(pub Vec<dp::DistributionPoint>);
-impl<'a> AssociatedOid for FreshestCrl<'a> {
+impl AssociatedOid for FreshestCrl {
const OID: ObjectIdentifier = ID_CE_FRESHEST_CRL;
}
-impl_newtype!(FreshestCrl<'a>, Vec<dp::DistributionPoint<'a>>);
+impl_newtype!(FreshestCrl, Vec<dp::DistributionPoint>);
+impl_extension!(FreshestCrl, critical = false);
/// CRLReason as defined in [RFC 5280 Section 5.3.1].
///
@@ -114,3 +118,5 @@ pub enum CrlReason {
impl AssociatedOid for CrlReason {
const OID: ObjectIdentifier = ID_CE_CRL_REASONS;
}
+
+impl_extension!(CrlReason, critical = false);
diff --git a/src/ext/pkix/crl/dp.rs b/src/ext/pkix/crl/dp.rs
index f7d1ab7..7acd2c9 100644
--- a/src/ext/pkix/crl/dp.rs
+++ b/src/ext/pkix/crl/dp.rs
@@ -1,8 +1,8 @@
//! PKIX distribution point types
use const_oid::{db::rfc5280::ID_PE_SUBJECT_INFO_ACCESS, AssociatedOid, ObjectIdentifier};
+use der::flagset::{flags, FlagSet};
use der::{Sequence, ValueOrd};
-use flagset::{flags, FlagSet};
use crate::ext::pkix::name::{DistributionPointName, GeneralNames};
@@ -24,9 +24,9 @@ use crate::ext::pkix::name::{DistributionPointName, GeneralNames};
/// [RFC 5280 Section 5.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5
#[derive(Clone, Debug, Eq, PartialEq, Sequence)]
#[allow(missing_docs)]
-pub struct IssuingDistributionPoint<'a> {
+pub struct IssuingDistributionPoint {
#[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")]
- pub distribution_point: Option<DistributionPointName<'a>>,
+ pub distribution_point: Option<DistributionPointName>,
#[asn1(
context_specific = "1",
@@ -60,10 +60,12 @@ pub struct IssuingDistributionPoint<'a> {
pub only_contains_attribute_certs: bool,
}
-impl<'a> AssociatedOid for IssuingDistributionPoint<'a> {
+impl AssociatedOid for IssuingDistributionPoint {
const OID: ObjectIdentifier = ID_PE_SUBJECT_INFO_ACCESS;
}
+impl_extension!(IssuingDistributionPoint, critical = true);
+
/// DistributionPoint as defined in [RFC 5280 Section 4.2.1.13].
///
/// ```text
@@ -76,15 +78,15 @@ impl<'a> AssociatedOid for IssuingDistributionPoint<'a> {
/// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13
#[derive(Clone, Debug, PartialEq, Eq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct DistributionPoint<'a> {
+pub struct DistributionPoint {
#[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")]
- pub distribution_point: Option<DistributionPointName<'a>>,
+ pub distribution_point: Option<DistributionPointName>,
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")]
pub reasons: Option<ReasonFlags>,
#[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")]
- pub crl_issuer: Option<GeneralNames<'a>>,
+ pub crl_issuer: Option<GeneralNames>,
}
/// ReasonFlags as defined in [RFC 5280 Section 4.2.1.13].
diff --git a/src/ext/pkix/keyusage.rs b/src/ext/pkix/keyusage.rs
index cd051fe..833db69 100644
--- a/src/ext/pkix/keyusage.rs
+++ b/src/ext/pkix/keyusage.rs
@@ -1,12 +1,12 @@
use alloc::vec::Vec;
use const_oid::db::rfc5280::{
- ID_CE_EXT_KEY_USAGE, ID_CE_KEY_USAGE, ID_CE_PRIVATE_KEY_USAGE_PERIOD,
+ ANY_EXTENDED_KEY_USAGE, ID_CE_EXT_KEY_USAGE, ID_CE_KEY_USAGE, ID_CE_PRIVATE_KEY_USAGE_PERIOD,
};
use const_oid::AssociatedOid;
use der::asn1::{GeneralizedTime, ObjectIdentifier};
+use der::flagset::{flags, FlagSet};
use der::Sequence;
-use flagset::{flags, FlagSet};
flags! {
/// Key usage flags as defined in [RFC 5280 Section 4.2.1.3].
@@ -52,6 +52,66 @@ impl AssociatedOid for KeyUsage {
}
impl_newtype!(KeyUsage, FlagSet<KeyUsages>);
+impl_extension!(KeyUsage, critical = true);
+
+impl KeyUsage {
+ /// The subject public key is used for verifying digital signatures
+ pub fn digital_signature(&self) -> bool {
+ self.0.contains(KeyUsages::DigitalSignature)
+ }
+
+ /// When the subject public key is used to verify digital signatures,
+ /// it is asserted as non-repudiation.
+ pub fn non_repudiation(&self) -> bool {
+ self.0.contains(KeyUsages::NonRepudiation)
+ }
+
+ /// The subject public key is used for enciphering private or
+ /// secret keys, i.e., for key transport.
+ pub fn key_encipherment(&self) -> bool {
+ self.0.contains(KeyUsages::KeyEncipherment)
+ }
+
+ /// The subject public key is used for directly enciphering
+ /// raw user data without the use of an intermediate symmetric cipher.
+ pub fn data_encipherment(&self) -> bool {
+ self.0.contains(KeyUsages::DataEncipherment)
+ }
+
+ /// The subject public key is used for key agreement
+ pub fn key_agreement(&self) -> bool {
+ self.0.contains(KeyUsages::KeyAgreement)
+ }
+
+ /// The subject public key is used for enciphering private or
+ /// secret keys, i.e., for key transport.
+ pub fn key_cert_sign(&self) -> bool {
+ self.0.contains(KeyUsages::KeyCertSign)
+ }
+
+ /// The subject public key is used for verifying signatures
+ /// on certificate revocation lists (e.g., CRLs, delta CRLs,
+ /// or ARLs).
+ pub fn crl_sign(&self) -> bool {
+ self.0.contains(KeyUsages::CRLSign)
+ }
+
+ /// The meaning of the `encipher_only` is undefined when `key_agreement`
+ /// returns false. When `encipher_only` returns true and
+ /// `key_agreement` also returns true, the subject public key may be
+ /// used only for enciphering data while performing key agreement.
+ pub fn encipher_only(&self) -> bool {
+ self.0.contains(KeyUsages::EncipherOnly)
+ }
+
+ /// The meaning of the `decipher_only` is undefined when `key_agreement`
+ /// returns false. When `encipher_only` returns true and
+ /// `key_agreement` also returns true, the subject public key may be
+ /// used only for deciphering data while performing key agreement.
+ pub fn decipher_only(&self) -> bool {
+ self.0.contains(KeyUsages::DecipherOnly)
+ }
+}
/// ExtKeyUsageSyntax as defined in [RFC 5280 Section 4.2.1.12].
///
@@ -78,6 +138,30 @@ impl AssociatedOid for ExtendedKeyUsage {
impl_newtype!(ExtendedKeyUsage, Vec<ObjectIdentifier>);
+impl crate::ext::AsExtension for ExtendedKeyUsage {
+ fn critical(
+ &self,
+ _subject: &crate::name::Name,
+ _extensions: &[crate::ext::Extension],
+ ) -> bool {
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12
+ // This extension MAY, at the option of the certificate issuer, be
+ // either critical or non-critical.
+ //
+ // If a CA includes extended key usages to satisfy such applications,
+ // but does not wish to restrict usages of the key, the CA can include
+ // the special KeyPurposeId anyExtendedKeyUsage in addition to the
+ // particular key purposes required by the applications. Conforming CAs
+ // SHOULD NOT mark this extension as critical if the anyExtendedKeyUsage
+ // KeyPurposeId is present. Applications that require the presence of a
+ // particular purpose MAY reject certificates that include the
+ // anyExtendedKeyUsage OID but not the particular OID expected for the
+ // application.
+
+ !self.0.iter().any(|el| *el == ANY_EXTENDED_KEY_USAGE)
+ }
+}
+
/// PrivateKeyUsagePeriod as defined in [RFC 3280 Section 4.2.1.4].
///
/// RFC 5280 states "use of this ISO standard extension is neither deprecated nor recommended for use in the Internet PKI."
@@ -103,3 +187,34 @@ pub struct PrivateKeyUsagePeriod {
impl AssociatedOid for PrivateKeyUsagePeriod {
const OID: ObjectIdentifier = ID_CE_PRIVATE_KEY_USAGE_PERIOD;
}
+
+impl_extension!(PrivateKeyUsagePeriod, critical = false);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn digital_signature_contains_digital_signature() {
+ let key_usage = KeyUsage(KeyUsages::DigitalSignature.into());
+ assert!(key_usage.digital_signature());
+ }
+
+ #[test]
+ fn all_contains_digital_signature() {
+ let key_usage = KeyUsage(FlagSet::full());
+ assert!(key_usage.digital_signature());
+ }
+
+ #[test]
+ fn key_encipherment_not_contains_digital_signature() {
+ let key_usage = KeyUsage(KeyUsages::KeyEncipherment.into());
+ assert!(!key_usage.digital_signature());
+ }
+
+ #[test]
+ fn empty_not_contains_digital_signature() {
+ let key_usage = KeyUsage(None.into());
+ assert!(!key_usage.digital_signature());
+ }
+}
diff --git a/src/ext/pkix/name/dirstr.rs b/src/ext/pkix/name/dirstr.rs
index 2aaa732..a6a0117 100644
--- a/src/ext/pkix/name/dirstr.rs
+++ b/src/ext/pkix/name/dirstr.rs
@@ -1,4 +1,5 @@
-use der::asn1::{PrintableStringRef, TeletexStringRef, Utf8StringRef};
+use alloc::string::String;
+use der::asn1::{PrintableString, TeletexString};
use der::{Choice, ValueOrd};
/// DirectoryString as defined in [RFC 5280 Section 4.2.1.4].
@@ -40,13 +41,13 @@ use der::{Choice, ValueOrd};
/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4
#[derive(Clone, Debug, Eq, PartialEq, Choice, ValueOrd)]
#[allow(missing_docs)]
-pub enum DirectoryString<'a> {
+pub enum DirectoryString {
#[asn1(type = "PrintableString")]
- PrintableString(PrintableStringRef<'a>),
+ PrintableString(PrintableString),
#[asn1(type = "TeletexString")]
- TeletexString(TeletexStringRef<'a>),
+ TeletexString(TeletexString),
#[asn1(type = "UTF8String")]
- Utf8String(Utf8StringRef<'a>),
+ Utf8String(String),
}
diff --git a/src/ext/pkix/name/dp.rs b/src/ext/pkix/name/dp.rs
index e895f88..0965564 100644
--- a/src/ext/pkix/name/dp.rs
+++ b/src/ext/pkix/name/dp.rs
@@ -15,10 +15,10 @@ use der::{Choice, ValueOrd};
/// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13
#[derive(Clone, Debug, Eq, PartialEq, Choice, ValueOrd)]
#[allow(missing_docs)]
-pub enum DistributionPointName<'a> {
+pub enum DistributionPointName {
#[asn1(context_specific = "0", tag_mode = "IMPLICIT", constructed = "true")]
- FullName(GeneralNames<'a>),
+ FullName(GeneralNames),
#[asn1(context_specific = "1", tag_mode = "IMPLICIT", constructed = "true")]
- NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>),
+ NameRelativeToCRLIssuer(RelativeDistinguishedName),
}
diff --git a/src/ext/pkix/name/ediparty.rs b/src/ext/pkix/name/ediparty.rs
index 94af09b..9d10040 100644
--- a/src/ext/pkix/name/ediparty.rs
+++ b/src/ext/pkix/name/ediparty.rs
@@ -27,10 +27,10 @@ use super::DirectoryString;
/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct EdiPartyName<'a> {
+pub struct EdiPartyName {
#[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")]
- pub name_assigner: Option<DirectoryString<'a>>,
+ pub name_assigner: Option<DirectoryString>,
#[asn1(context_specific = "1", tag_mode = "EXPLICIT")]
- pub party_name: DirectoryString<'a>,
+ pub party_name: DirectoryString,
}
diff --git a/src/ext/pkix/name/general.rs b/src/ext/pkix/name/general.rs
index f6861d4..bd0fa7e 100644
--- a/src/ext/pkix/name/general.rs
+++ b/src/ext/pkix/name/general.rs
@@ -3,7 +3,7 @@
use super::{EdiPartyName, OtherName};
use crate::name::Name;
-use der::asn1::{Ia5StringRef, ObjectIdentifier, OctetStringRef};
+use der::asn1::{Ia5String, ObjectIdentifier, OctetString};
use der::{Choice, ValueOrd};
/// GeneralNames as defined in [RFC 5280 Section 4.2.1.6].
@@ -13,7 +13,7 @@ use der::{Choice, ValueOrd};
/// ```
///
/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
-pub type GeneralNames<'a> = alloc::vec::Vec<GeneralName<'a>>;
+pub type GeneralNames = alloc::vec::Vec<GeneralName>;
/// GeneralName as defined in [RFC 5280 Section 4.2.1.6].
///
@@ -36,28 +36,74 @@ pub type GeneralNames<'a> = alloc::vec::Vec<GeneralName<'a>>;
/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
#[derive(Clone, Debug, Eq, PartialEq, Choice, ValueOrd)]
#[allow(missing_docs)]
-pub enum GeneralName<'a> {
+pub enum GeneralName {
#[asn1(context_specific = "0", tag_mode = "IMPLICIT", constructed = "true")]
- OtherName(OtherName<'a>),
+ OtherName(OtherName),
#[asn1(context_specific = "1", tag_mode = "IMPLICIT")]
- Rfc822Name(Ia5StringRef<'a>),
+ Rfc822Name(Ia5String),
#[asn1(context_specific = "2", tag_mode = "IMPLICIT")]
- DnsName(Ia5StringRef<'a>),
+ DnsName(Ia5String),
#[asn1(context_specific = "4", tag_mode = "EXPLICIT", constructed = "true")]
- DirectoryName(Name<'a>),
+ DirectoryName(Name),
#[asn1(context_specific = "5", tag_mode = "IMPLICIT", constructed = "true")]
- EdiPartyName(EdiPartyName<'a>),
+ EdiPartyName(EdiPartyName),
#[asn1(context_specific = "6", tag_mode = "IMPLICIT")]
- UniformResourceIdentifier(Ia5StringRef<'a>),
+ UniformResourceIdentifier(Ia5String),
#[asn1(context_specific = "7", tag_mode = "IMPLICIT")]
- IpAddress(OctetStringRef<'a>),
+ IpAddress(OctetString),
#[asn1(context_specific = "8", tag_mode = "IMPLICIT")]
RegisteredId(ObjectIdentifier),
}
+
+#[cfg(feature = "std")]
+impl From<std::net::IpAddr> for GeneralName {
+ fn from(ip: std::net::IpAddr) -> Self {
+ // Safety: this is unfailable here, OctetString will issue an error if you go
+ // over 256MiB, here the buffer is at most 16 bytes (ipv6). The two `expect`s
+ // below are safe.
+ let buf = match ip {
+ std::net::IpAddr::V4(v) => {
+ let value = v.octets();
+ OctetString::new(&value[..])
+ .expect("OctetString is not expected to fail with a 4 bytes long buffer")
+ }
+ std::net::IpAddr::V6(v) => {
+ let value = v.octets();
+ OctetString::new(&value[..])
+ .expect("OctetString is not expected to fail with a 16 bytes long buffer")
+ }
+ };
+
+ GeneralName::IpAddress(buf)
+ }
+}
+
+#[cfg(all(feature = "std", test))]
+mod tests {
+ use super::*;
+ use der::Encode;
+
+ #[test]
+ fn test_convert() {
+ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
+
+ let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
+ let localhost_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
+
+ assert_eq!(
+ GeneralName::from(localhost_v4).to_der().unwrap(),
+ &[135, 4, 127, 0, 0, 1][..]
+ );
+ assert_eq!(
+ GeneralName::from(localhost_v6).to_der().unwrap(),
+ &[135, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1][..]
+ );
+ }
+}
diff --git a/src/ext/pkix/name/other.rs b/src/ext/pkix/name/other.rs
index a33601c..26b93bc 100644
--- a/src/ext/pkix/name/other.rs
+++ b/src/ext/pkix/name/other.rs
@@ -1,4 +1,4 @@
-use der::{asn1::ObjectIdentifier, AnyRef, Sequence, ValueOrd};
+use der::{asn1::ObjectIdentifier, Any, Sequence, ValueOrd};
/// OtherName as defined in [RFC 5280 Section 4.2.1.6].
///
@@ -12,26 +12,26 @@ use der::{asn1::ObjectIdentifier, AnyRef, Sequence, ValueOrd};
/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6
#[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
#[allow(missing_docs)]
-pub struct OtherName<'a> {
+pub struct OtherName {
pub type_id: ObjectIdentifier,
#[asn1(context_specific = "0", tag_mode = "EXPLICIT")]
- pub value: AnyRef<'a>,
+ pub value: Any,
}
#[test]
#[cfg(test)]
fn test() {
use alloc::string::ToString;
- use der::{Decode, Encode};
+ use der::{asn1::Utf8StringRef, Decode, Encode};
use hex_literal::hex;
let input = hex!("3021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C");
let decoded = OtherName::from_der(&input).unwrap();
- let onval = decoded.value.utf8_string().unwrap();
+ let onval = Utf8StringRef::try_from(&decoded.value).unwrap();
assert_eq!(onval.to_string(), "Upn_214950130@mil");
- let encoded = decoded.to_vec().unwrap();
+ let encoded = decoded.to_der().unwrap();
assert_eq!(&input[..], &encoded);
}
diff --git a/src/ext/pkix/policymap.rs b/src/ext/pkix/policymap.rs
index e6b55ba..1997f54 100644
--- a/src/ext/pkix/policymap.rs
+++ b/src/ext/pkix/policymap.rs
@@ -20,6 +20,7 @@ impl AssociatedOid for PolicyMappings {
}
impl_newtype!(PolicyMappings, Vec<PolicyMapping>);
+impl_extension!(PolicyMappings, critical = true);
/// PolicyMapping as defined in [RFC 5280 Section 4.2.1.5].
///
diff --git a/src/lib.rs b/src/lib.rs
index 1221c3c..dbf3d93 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,5 @@
#![no_std]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
@@ -7,6 +7,8 @@
)]
#![forbid(unsafe_code)]
#![warn(
+ clippy::mod_module_files,
+ clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
@@ -32,8 +34,12 @@ pub mod crl;
pub mod ext;
pub mod name;
pub mod request;
+pub mod serial_number;
pub mod time;
+#[cfg(feature = "builder")]
+pub mod builder;
+
pub use certificate::{Certificate, PkiPath, TbsCertificate, Version};
pub use der;
pub use spki;
diff --git a/src/macros.rs b/src/macros.rs
index 638d516..0333ef7 100644
--- a/src/macros.rs
+++ b/src/macros.rs
@@ -61,7 +61,7 @@ macro_rules! impl_newtype {
#[allow(unused_lifetimes)]
impl<'a> ::der::EncodeValue for $newtype {
- fn encode_value(&self, encoder: &mut dyn ::der::Writer) -> ::der::Result<()> {
+ fn encode_value(&self, encoder: &mut impl ::der::Writer) -> ::der::Result<()> {
self.0.encode_value(encoder)
}
@@ -78,3 +78,61 @@ macro_rules! impl_newtype {
}
};
}
+
+/// Implements the AsExtension traits for every defined Extension paylooad
+macro_rules! impl_extension {
+ ($newtype:ty) => {
+ impl_extension!($newtype, critical = false);
+ };
+ ($newtype:ty, critical = $critical:expr) => {
+ impl crate::ext::AsExtension for $newtype {
+ fn critical(
+ &self,
+ _subject: &crate::name::Name,
+ _extensions: &[crate::ext::Extension],
+ ) -> bool {
+ $critical
+ }
+ }
+ };
+}
+
+/// Implements conversions between [`spki::SubjectPublicKeyInfo`] and [`SubjectKeyIdentifier`] or [`AuthorityKeyIdentifier`]
+macro_rules! impl_key_identifier {
+ ($newtype:ty, $out:expr) => {
+ #[cfg(feature = "builder")]
+ mod builder_key_identifier {
+ use super::*;
+ use der::asn1::OctetString;
+ use sha1::{Digest, Sha1};
+ use spki::SubjectPublicKeyInfoRef;
+
+ impl<'a> TryFrom<SubjectPublicKeyInfoRef<'a>> for $newtype {
+ type Error = der::Error;
+
+ fn try_from(issuer: SubjectPublicKeyInfoRef<'a>) -> Result<Self, Self::Error> {
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2
+ //
+ // For CA certificates, subject key identifiers SHOULD be derived from
+ // the public key or a method that generates unique values. Two common
+ // methods for generating key identifiers from the public key are:
+
+ // (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the
+ // value of the BIT STRING subjectPublicKey (excluding the tag,
+ // length, and number of unused bits).
+
+ // (2) The keyIdentifier is composed of a four-bit type field with
+ // the value 0100 followed by the least significant 60 bits of
+ // the SHA-1 hash of the value of the BIT STRING
+ // subjectPublicKey (excluding the tag, length, and number of
+ // unused bits).
+
+ // Here we're using the first method
+
+ let result = Sha1::digest(issuer.subject_public_key.raw_bytes());
+ $out(result.as_slice())
+ }
+ }
+ }
+ };
+}
diff --git a/src/name.rs b/src/name.rs
index 81ee4aa..ec62249 100644
--- a/src/name.rs
+++ b/src/name.rs
@@ -2,8 +2,8 @@
use crate::attr::AttributeTypeAndValue;
use alloc::vec::Vec;
-use core::fmt;
-use der::{asn1::SetOfVec, Decode, Encode};
+use core::{fmt, str::FromStr};
+use der::{asn1::SetOfVec, Encode};
/// X.501 Name as defined in [RFC 5280 Section 4.1.2.4]. X.501 Name is used to represent distinguished names.
///
@@ -12,7 +12,7 @@ use der::{asn1::SetOfVec, Decode, Encode};
/// ```
///
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
-pub type Name<'a> = RdnSequence<'a>;
+pub type Name = RdnSequence;
/// X.501 RDNSequence as defined in [RFC 5280 Section 4.1.2.4].
///
@@ -21,35 +21,47 @@ pub type Name<'a> = RdnSequence<'a>;
/// ```
///
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct RdnSequence<'a>(pub Vec<RelativeDistinguishedName<'a>>);
-
-impl RdnSequence<'_> {
- /// Converts an RDNSequence string into an encoded RDNSequence
- ///
- /// This function follows the rules in [RFC 4514].
- ///
- /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+pub struct RdnSequence(pub Vec<RelativeDistinguishedName>);
+
+impl RdnSequence {
+ /// Converts an `RDNSequence` string into an encoded `RDNSequence`.
+ #[deprecated(since = "0.2.1", note = "use RdnSequence::from_str(...)?.to_der()")]
pub fn encode_from_string(s: &str) -> Result<Vec<u8>, der::Error> {
- let ders = split(s, b',')
- .map(RelativeDistinguishedName::encode_from_string)
- .collect::<Result<Vec<_>, der::Error>>()?;
+ Self::from_str(s)?.to_der()
+ }
- let mut out = Vec::new();
- for der in ders.iter() {
- out.push(RelativeDistinguishedName::from_der(der)?);
- }
+ /// Is this [`RdnSequence`] empty?
+ pub fn is_empty(&self) -> bool {
+ self.0.is_empty()
+ }
+}
- RdnSequence(out).to_vec()
+/// Parse an [`RdnSequence`] string.
+///
+/// Follows the rules in [RFC 4514].
+///
+/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+impl FromStr for RdnSequence {
+ type Err = der::Error;
+
+ fn from_str(s: &str) -> der::Result<Self> {
+ let mut parts = split(s, b',')
+ .map(RelativeDistinguishedName::from_str)
+ .collect::<der::Result<Vec<_>>>()?;
+ parts.reverse();
+ Ok(Self(parts))
}
}
/// Serializes the structure according to the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
-impl fmt::Display for RdnSequence<'_> {
+impl fmt::Display for RdnSequence {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- for (i, atv) in self.0.iter().enumerate() {
+ // As per RFC 4514 Section 2.1, the elements are reversed
+ for (i, atv) in self.0.iter().rev().enumerate() {
match i {
0 => write!(f, "{}", atv)?,
_ => write!(f, ",{}", atv)?,
@@ -60,7 +72,7 @@ impl fmt::Display for RdnSequence<'_> {
}
}
-impl_newtype!(RdnSequence<'a>, Vec<RelativeDistinguishedName<'a>>);
+impl_newtype!(RdnSequence, Vec<RelativeDistinguishedName>);
/// Find the indices of all non-escaped separators.
fn find(s: &str, b: u8) -> impl '_ + Iterator<Item = usize> {
@@ -98,7 +110,7 @@ fn split(s: &str, b: u8) -> impl '_ + Iterator<Item = &'_ str> {
/// ```
///
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
-pub type DistinguishedName<'a> = RdnSequence<'a>;
+pub type DistinguishedName = RdnSequence;
/// RelativeDistinguishedName as defined in [RFC 5280 Section 4.1.2.4].
///
@@ -124,33 +136,50 @@ pub type DistinguishedName<'a> = RdnSequence<'a>;
/// ```
///
/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
-pub struct RelativeDistinguishedName<'a>(pub SetOfVec<AttributeTypeAndValue<'a>>);
+pub struct RelativeDistinguishedName(pub SetOfVec<AttributeTypeAndValue>);
-impl RelativeDistinguishedName<'_> {
+impl RelativeDistinguishedName {
/// Converts an RelativeDistinguishedName string into an encoded RelativeDistinguishedName
- ///
- /// This function follows the rules in [RFC 4514].
- ///
- /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+ #[deprecated(
+ since = "0.2.1",
+ note = "use RelativeDistinguishedName::from_str(...)?.to_der()"
+ )]
pub fn encode_from_string(s: &str) -> Result<Vec<u8>, der::Error> {
- let ders = split(s, b'+')
- .map(AttributeTypeAndValue::encode_from_string)
- .collect::<Result<Vec<_>, der::Error>>()?;
+ Self::from_str(s)?.to_der()
+ }
+}
+
+/// Parse a [`RelativeDistinguishedName`] string.
+///
+/// This function follows the rules in [RFC 4514].
+///
+/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
+impl FromStr for RelativeDistinguishedName {
+ type Err = der::Error;
+
+ fn from_str(s: &str) -> der::Result<Self> {
+ split(s, b'+')
+ .map(AttributeTypeAndValue::from_str)
+ .collect::<der::Result<Vec<_>>>()?
+ .try_into()
+ .map(Self)
+ }
+}
- let atvs = ders
- .iter()
- .map(|der| AttributeTypeAndValue::from_der(der))
- .collect::<Result<Vec<_>, der::Error>>()?;
+impl TryFrom<Vec<AttributeTypeAndValue>> for RelativeDistinguishedName {
+ type Error = der::Error;
- RelativeDistinguishedName(atvs.try_into()?).to_vec()
+ fn try_from(vec: Vec<AttributeTypeAndValue>) -> der::Result<RelativeDistinguishedName> {
+ Ok(RelativeDistinguishedName(SetOfVec::try_from(vec)?))
}
}
/// Serializes the structure according to the rules in [RFC 4514].
///
/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514
-impl fmt::Display for RelativeDistinguishedName<'_> {
+impl fmt::Display for RelativeDistinguishedName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, atv) in self.0.iter().enumerate() {
match i {
@@ -163,7 +192,4 @@ impl fmt::Display for RelativeDistinguishedName<'_> {
}
}
-impl_newtype!(
- RelativeDistinguishedName<'a>,
- SetOfVec<AttributeTypeAndValue<'a>>
-);
+impl_newtype!(RelativeDistinguishedName, SetOfVec<AttributeTypeAndValue>);
diff --git a/src/request.rs b/src/request.rs
index 6ab16cf..50478d8 100644
--- a/src/request.rs
+++ b/src/request.rs
@@ -1,24 +1,34 @@
//! PKCS#10 Certification Request types
-use crate::ext::Extension;
-use crate::{attr::Attributes, name::Name};
+use crate::{
+ attr::{Attribute, AttributeValue, Attributes},
+ ext::Extension,
+ name::Name,
+};
use alloc::vec::Vec;
use const_oid::db::rfc5912::ID_EXTENSION_REQ;
use const_oid::{AssociatedOid, ObjectIdentifier};
-use der::asn1::BitStringRef;
-use der::{Decode, Enumerated, Sequence};
-use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo};
+use der::asn1::BitString;
+use der::{
+ asn1::{Any, SetOfVec},
+ Decode, Enumerated, Sequence,
+};
+use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned};
+
+#[cfg(feature = "pem")]
+use der::pem::PemLabel;
/// Version identifier for certification request information.
///
/// (RFC 2986 designates `0` as the only valid version)
-#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
+#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated, Default)]
#[asn1(type = "INTEGER")]
#[repr(u8)]
pub enum Version {
/// Denotes PKCS#8 v1
+ #[default]
V1 = 0,
}
@@ -35,27 +45,19 @@ pub enum Version {
///
/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4
#[derive(Clone, Debug, PartialEq, Eq, Sequence)]
-pub struct CertReqInfo<'a> {
+pub struct CertReqInfo {
/// Certification request version.
pub version: Version,
/// Subject name.
- pub subject: Name<'a>,
+ pub subject: Name,
/// Subject public key info.
- pub public_key: SubjectPublicKeyInfo<'a>,
+ pub public_key: SubjectPublicKeyInfoOwned,
/// Request attributes.
#[asn1(context_specific = "0", tag_mode = "IMPLICIT")]
- pub attributes: Attributes<'a>,
-}
-
-impl<'a> TryFrom<&'a [u8]> for CertReqInfo<'a> {
- type Error = der::Error;
-
- fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
- Self::from_der(bytes)
- }
+ pub attributes: Attributes,
}
/// PKCS#10 `CertificationRequest` as defined in [RFC 2986 Section 4].
@@ -70,18 +72,23 @@ impl<'a> TryFrom<&'a [u8]> for CertReqInfo<'a> {
///
/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4
#[derive(Clone, Debug, PartialEq, Eq, Sequence)]
-pub struct CertReq<'a> {
+pub struct CertReq {
/// Certification request information.
- pub info: CertReqInfo<'a>,
+ pub info: CertReqInfo,
/// Signature algorithm identifier.
- pub algorithm: AlgorithmIdentifier<'a>,
+ pub algorithm: AlgorithmIdentifierOwned,
/// Signature.
- pub signature: BitStringRef<'a>,
+ pub signature: BitString,
}
-impl<'a> TryFrom<&'a [u8]> for CertReq<'a> {
+#[cfg(feature = "pem")]
+impl PemLabel for CertReq {
+ const PEM_LABEL: &'static str = "CERTIFICATE REQUEST";
+}
+
+impl<'a> TryFrom<&'a [u8]> for CertReq {
type Error = der::Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
@@ -96,11 +103,89 @@ impl<'a> TryFrom<&'a [u8]> for CertReq<'a> {
/// ```
///
/// [RFC 5272 Section 3.1]: https://datatracker.ietf.org/doc/html/rfc5272#section-3.1
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct ExtensionReq<'a>(pub Vec<Extension<'a>>);
+#[derive(Clone, Debug, PartialEq, Eq, Default)]
+pub struct ExtensionReq(pub Vec<Extension>);
-impl<'a> AssociatedOid for ExtensionReq<'a> {
+impl AssociatedOid for ExtensionReq {
const OID: ObjectIdentifier = ID_EXTENSION_REQ;
}
-impl_newtype!(ExtensionReq<'a>, Vec<Extension<'a>>);
+impl_newtype!(ExtensionReq, Vec<Extension>);
+
+impl TryFrom<ExtensionReq> for Attribute {
+ type Error = der::Error;
+
+ fn try_from(extension_req: ExtensionReq) -> der::Result<Attribute> {
+ let mut values: SetOfVec<AttributeValue> = Default::default();
+ values.insert(Any::encode_from(&extension_req.0)?)?;
+
+ Ok(Attribute {
+ oid: ExtensionReq::OID,
+ values,
+ })
+ }
+}
+
+pub mod attributes {
+ //! Set of attributes that may be associated to a request
+
+ use alloc::vec;
+ use const_oid::AssociatedOid;
+ use der::{
+ asn1::{Any, ObjectIdentifier, SetOfVec},
+ EncodeValue, Length, Result, Tag, Tagged, Writer,
+ };
+
+ use crate::{attr::Attribute, ext::pkix::name::DirectoryString};
+
+ /// Trait to be implement by request attributes
+ pub trait AsAttribute: AssociatedOid + Tagged + EncodeValue + Sized {
+ /// Returns the Attribute with the content encoded.
+ fn to_attribute(&self) -> Result<Attribute> {
+ let inner: Any = der::asn1::Any::encode_from(self)?;
+
+ let values = SetOfVec::try_from(vec![inner])?;
+
+ Ok(Attribute {
+ oid: Self::OID,
+ values,
+ })
+ }
+ }
+
+ /// `ChallengePassword` as defined in [RFC 2985 Section 5.4.1]
+ ///
+ /// ```text
+ /// challengePassword ATTRIBUTE ::= {
+ /// WITH SYNTAX DirectoryString {pkcs-9-ub-challengePassword}
+ /// EQUALITY MATCHING RULE caseExactMatch
+ /// SINGLE VALUE TRUE
+ /// ID pkcs-9-at-challengePassword
+ /// }
+ /// ```
+ ///
+ /// [RFC 2985 Section 5.4.1]: https://www.rfc-editor.org/rfc/rfc2985#page-16
+ pub struct ChallengePassword(pub DirectoryString);
+
+ impl AsAttribute for ChallengePassword {}
+
+ impl AssociatedOid for ChallengePassword {
+ const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.7");
+ }
+
+ impl Tagged for ChallengePassword {
+ fn tag(&self) -> Tag {
+ self.0.tag()
+ }
+ }
+
+ impl EncodeValue for ChallengePassword {
+ fn value_len(&self) -> Result<Length> {
+ self.0.value_len()
+ }
+
+ fn encode_value(&self, encoder: &mut impl Writer) -> Result<()> {
+ self.0.encode_value(encoder)
+ }
+ }
+}
diff --git a/src/serial_number.rs b/src/serial_number.rs
new file mode 100644
index 0000000..b14487a
--- /dev/null
+++ b/src/serial_number.rs
@@ -0,0 +1,195 @@
+//! X.509 serial number
+
+use core::{fmt::Display, marker::PhantomData};
+
+use der::{
+ asn1::{self, Int},
+ DecodeValue, EncodeValue, ErrorKind, FixedTag, Header, Length, Reader, Result, Tag, ValueOrd,
+ Writer,
+};
+
+use crate::certificate::{Profile, Rfc5280};
+
+/// [RFC 5280 Section 4.1.2.2.] Serial Number
+///
+/// The serial number MUST be a positive integer assigned by the CA to
+/// each certificate. It MUST be unique for each certificate issued by a
+/// given CA (i.e., the issuer name and serial number identify a unique
+/// certificate). CAs MUST force the serialNumber to be a non-negative
+/// integer.
+///
+/// Given the uniqueness requirements above, serial numbers can be
+/// expected to contain long integers. Certificate users MUST be able to
+/// handle serialNumber values up to 20 octets. Conforming CAs MUST NOT
+/// use serialNumber values longer than 20 octets.
+///
+/// Note: Non-conforming CAs may issue certificates with serial numbers
+/// that are negative or zero. Certificate users SHOULD be prepared to
+/// gracefully handle such certificates.
+#[derive(Clone, Debug, Eq, PartialEq, ValueOrd, PartialOrd, Ord)]
+pub struct SerialNumber<P: Profile = Rfc5280> {
+ pub(crate) inner: Int,
+ _profile: PhantomData<P>,
+}
+
+impl<P: Profile> SerialNumber<P> {
+ /// Maximum length in bytes for a [`SerialNumber`]
+ pub const MAX_LEN: Length = Length::new(20);
+
+ /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`.
+ pub(crate) const MAX_DECODE_LEN: Length = Length::new(21);
+
+ /// Create a new [`SerialNumber`] from a byte slice.
+ ///
+ /// The byte slice **must** represent a positive integer.
+ pub fn new(bytes: &[u8]) -> Result<Self> {
+ let inner = asn1::Uint::new(bytes)?;
+
+ // The user might give us a 20 byte unsigned integer with a high MSB,
+ // which we'd then encode with 21 octets to preserve the sign bit.
+ // RFC 5280 is ambiguous about whether this is valid, so we limit
+ // `SerialNumber` *encodings* to 20 bytes or fewer while permitting
+ // `SerialNumber` *decodings* to have up to 21 bytes below.
+ if inner.value_len()? > Self::MAX_LEN {
+ return Err(ErrorKind::Overlength.into());
+ }
+
+ Ok(Self {
+ inner: inner.into(),
+ _profile: PhantomData,
+ })
+ }
+
+ /// Borrow the inner byte slice which contains the least significant bytes
+ /// of a big endian integer value with all leading zeros stripped.
+ pub fn as_bytes(&self) -> &[u8] {
+ self.inner.as_bytes()
+ }
+}
+
+impl<P: Profile> EncodeValue for SerialNumber<P> {
+ fn value_len(&self) -> Result<Length> {
+ self.inner.value_len()
+ }
+
+ fn encode_value(&self, writer: &mut impl Writer) -> Result<()> {
+ self.inner.encode_value(writer)
+ }
+}
+
+impl<'a, P: Profile> DecodeValue<'a> for SerialNumber<P> {
+ fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
+ let inner = Int::decode_value(reader, header)?;
+ let serial = Self {
+ inner,
+ _profile: PhantomData,
+ };
+
+ P::check_serial_number(&serial)?;
+
+ Ok(serial)
+ }
+}
+
+impl<P: Profile> FixedTag for SerialNumber<P> {
+ const TAG: Tag = <Int as FixedTag>::TAG;
+}
+
+impl Display for SerialNumber {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ let mut iter = self.as_bytes().iter().peekable();
+
+ while let Some(byte) = iter.next() {
+ match iter.peek() {
+ Some(_) => write!(f, "{:02X}:", byte)?,
+ None => write!(f, "{:02X}", byte)?,
+ }
+ }
+
+ Ok(())
+ }
+}
+
+macro_rules! impl_from {
+ ($source:ty) => {
+ impl From<$source> for SerialNumber {
+ fn from(inner: $source) -> SerialNumber {
+ let serial_number = &inner.to_be_bytes()[..];
+ let serial_number = asn1::Uint::new(serial_number).unwrap();
+
+ // This could only fail if the big endian representation was to be more than 20
+ // bytes long. Because it's only implemented for up to u64 / usize (8 bytes).
+ SerialNumber::new(serial_number.as_bytes()).unwrap()
+ }
+ }
+ };
+}
+
+impl_from!(u8);
+impl_from!(u16);
+impl_from!(u32);
+impl_from!(u64);
+impl_from!(usize);
+
+// Implement by hand because the derive would create invalid values.
+// Use the constructor to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber<P> {
+ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+ let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?;
+
+ Self::new(u.bytes(len as usize)?).map_err(|_| arbitrary::Error::IncorrectFormat)
+ }
+
+ fn size_hint(depth: usize) -> (usize, Option<usize>) {
+ arbitrary::size_hint::and(u32::size_hint(depth), (0, None))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use alloc::string::ToString;
+
+ use super::*;
+
+ #[test]
+ fn serial_number_invariants() {
+ // Creating a new serial with an oversized encoding (due to high MSB) fails.
+ {
+ let too_big = [0x80; 20];
+ assert!(SerialNumber::<Rfc5280>::new(&too_big).is_err());
+ }
+
+ // Creating a new serial with the maximum encoding succeeds.
+ {
+ let just_enough = [
+ 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ ];
+ assert!(SerialNumber::<Rfc5280>::new(&just_enough).is_ok());
+ }
+ }
+
+ #[test]
+ fn serial_number_display() {
+ {
+ let sn = SerialNumber::new(&[0x11, 0x22, 0x33]).unwrap();
+
+ assert_eq!(sn.to_string(), "11:22:33")
+ }
+
+ {
+ let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap();
+
+ // We force the user's serial to be positive if they give us a negative one.
+ assert_eq!(sn.to_string(), "00:AA:BB:CC:01:10:00:11")
+ }
+
+ {
+ let sn = SerialNumber::new(&[0x00, 0x00, 0x01]).unwrap();
+
+ // Leading zeroes are ignored, due to canonicalization.
+ assert_eq!(sn.to_string(), "01")
+ }
+ }
+}
diff --git a/src/time.rs b/src/time.rs
index d1f2ec5..c0ef8bf 100644
--- a/src/time.rs
+++ b/src/time.rs
@@ -3,7 +3,7 @@
use core::fmt;
use core::time::Duration;
use der::asn1::{GeneralizedTime, UtcTime};
-use der::{Choice, DateTime, Decode, Error, Result, Sequence, ValueOrd};
+use der::{Choice, DateTime, Sequence, ValueOrd};
#[cfg(feature = "std")]
use std::time::SystemTime;
@@ -21,9 +21,13 @@ use std::time::SystemTime;
///
/// [RFC 5280 Section 4.1.2.5]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5
/// [RFC 5280 Appendix A]: https://tools.ietf.org/html/rfc5280#page-117
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Choice, Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
pub enum Time {
- /// Legacy UTC time (has 2-digit year, valid only through 2050).
+ /// Legacy UTC time (has 2-digit year, valid from 1970 to 2049).
+ ///
+ /// Note: RFC 5280 specifies 1950-2049, however due to common operations working on
+ /// `UNIX_EPOCH` this implementation's lower bound is 1970.
#[asn1(type = "UTCTime")]
UtcTime(UtcTime),
@@ -33,6 +37,10 @@ pub enum Time {
}
impl Time {
+ /// Time used for Certificate who do not expire.
+ pub const INFINITY: Time =
+ Time::GeneralTime(GeneralizedTime::from_date_time(DateTime::INFINITY));
+
/// Get duration since `UNIX_EPOCH`.
pub fn to_unix_duration(self) -> Duration {
match self {
@@ -51,17 +59,30 @@ impl Time {
/// Convert to [`SystemTime`].
#[cfg(feature = "std")]
- #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn to_system_time(&self) -> SystemTime {
match self {
Time::UtcTime(t) => t.to_system_time(),
Time::GeneralTime(t) => t.to_system_time(),
}
}
+
+ /// Convert time to UTCTime representation
+ /// As per RFC 5280: 4.1.2.5, date through 2049 should be expressed as UTC Time.
+ #[cfg(feature = "builder")]
+ pub(crate) fn rfc5280_adjust_utc_time(&mut self) -> der::Result<()> {
+ if let Time::GeneralTime(t) = self {
+ let date = t.to_date_time();
+ if date.year() <= UtcTime::MAX_YEAR {
+ *self = Time::UtcTime(UtcTime::from_date_time(date)?);
+ }
+ }
+
+ Ok(())
+ }
}
impl fmt::Display for Time {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_date_time())
}
}
@@ -79,7 +100,6 @@ impl From<GeneralizedTime> for Time {
}
#[cfg(feature = "std")]
-#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl From<Time> for SystemTime {
fn from(time: Time) -> SystemTime {
time.to_system_time()
@@ -87,7 +107,6 @@ impl From<Time> for SystemTime {
}
#[cfg(feature = "std")]
-#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl From<&Time> for SystemTime {
fn from(time: &Time) -> SystemTime {
time.to_system_time()
@@ -95,11 +114,10 @@ impl From<&Time> for SystemTime {
}
#[cfg(feature = "std")]
-#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
impl TryFrom<SystemTime> for Time {
- type Error = Error;
+ type Error = der::Error;
- fn try_from(time: SystemTime) -> Result<Time> {
+ fn try_from(time: SystemTime) -> der::Result<Time> {
Ok(GeneralizedTime::try_from(time)?.into())
}
}
@@ -113,6 +131,7 @@ impl TryFrom<SystemTime> for Time {
/// }
/// ```
/// [RFC 5280 Section 4.1.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
pub struct Validity {
/// notBefore value
@@ -125,8 +144,7 @@ pub struct Validity {
impl Validity {
/// Creates a `Validity` which starts now and lasts for `duration`.
#[cfg(feature = "std")]
- #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
- pub fn from_now(duration: Duration) -> Result<Self> {
+ pub fn from_now(duration: Duration) -> der::Result<Self> {
let now = SystemTime::now();
let then = now + duration;
@@ -136,11 +154,3 @@ impl Validity {
})
}
}
-
-impl<'a> TryFrom<&'a [u8]> for Validity {
- type Error = Error;
-
- fn try_from(bytes: &'a [u8]) -> Result<Self> {
- Self::from_der(bytes)
- }
-}
diff --git a/tests/builder.rs b/tests/builder.rs
new file mode 100644
index 0000000..06aae51
--- /dev/null
+++ b/tests/builder.rs
@@ -0,0 +1,315 @@
+#![cfg(all(feature = "builder", feature = "pem"))]
+
+use der::{asn1::PrintableString, pem::LineEnding, Decode, Encode, EncodePem};
+use p256::{ecdsa::DerSignature, pkcs8::DecodePrivateKey, NistP256};
+use rsa::pkcs1::DecodeRsaPrivateKey;
+use rsa::pkcs1v15::SigningKey;
+use sha2::Sha256;
+use spki::SubjectPublicKeyInfoOwned;
+use std::{str::FromStr, time::Duration};
+use x509_cert::{
+ builder::{Builder, CertificateBuilder, Profile, RequestBuilder},
+ ext::pkix::{
+ name::{DirectoryString, GeneralName},
+ SubjectAltName,
+ },
+ name::Name,
+ request,
+ serial_number::SerialNumber,
+ time::Validity,
+};
+use x509_cert_test_support::{openssl, zlint};
+
+const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-pub.der");
+const PKCS8_PUBLIC_KEY_DER: &[u8] = include_bytes!("examples/p256-pub.der");
+
+#[test]
+fn root_ca_certificate() {
+ let serial_number = SerialNumber::from(42u32);
+ let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+ let profile = Profile::Root;
+ let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US")
+ .unwrap()
+ .to_der()
+ .unwrap();
+ let subject = Name::from_der(&subject).unwrap();
+ let pub_key =
+ SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");
+
+ let signer = rsa_signer();
+ let builder =
+ CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
+ .expect("Create certificate");
+
+ let certificate = builder.build().unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ let ignored = &[];
+ zlint::check_certificate(pem.as_bytes(), ignored);
+}
+
+#[test]
+fn root_ca_certificate_ecdsa() {
+ let serial_number = SerialNumber::from(42u32);
+ let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+ let profile = Profile::Root;
+ let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US")
+ .unwrap()
+ .to_der()
+ .unwrap();
+ let subject = Name::from_der(&subject).unwrap();
+ let pub_key =
+ SubjectPublicKeyInfoOwned::try_from(PKCS8_PUBLIC_KEY_DER).expect("get ecdsa pub key");
+
+ let signer = ecdsa_signer();
+ let builder =
+ CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
+ .expect("Create certificate");
+
+ let certificate = builder.build::<DerSignature>().unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ let ignored = &[];
+ zlint::check_certificate(pem.as_bytes(), ignored);
+}
+
+#[test]
+fn sub_ca_certificate() {
+ let serial_number = SerialNumber::from(42u32);
+ let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+
+ let issuer =
+ Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+ let profile = Profile::SubCA {
+ issuer,
+ path_len_constraint: Some(0),
+ };
+
+ let subject =
+ Name::from_str("CN=World domination task force,O=World domination Inc,C=US").unwrap();
+ let pub_key =
+ SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");
+
+ let signer = ecdsa_signer();
+ let builder =
+ CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
+ .expect("Create certificate");
+
+ let certificate = builder.build::<DerSignature>().unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ // TODO(baloo): not too sure we should tackle those in this API.
+ let ignored = &[
+ "w_sub_ca_aia_missing",
+ "e_sub_ca_crl_distribution_points_missing",
+ "e_sub_ca_certificate_policies_missing",
+ "w_sub_ca_aia_does_not_contain_issuing_ca_url",
+ ];
+
+ zlint::check_certificate(pem.as_bytes(), ignored);
+}
+
+#[test]
+fn leaf_certificate() {
+ let serial_number = SerialNumber::from(42u32);
+ let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+
+ let issuer =
+ Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+ let profile = Profile::Leaf {
+ issuer: issuer.clone(),
+ enable_key_agreement: false,
+ enable_key_encipherment: false,
+ #[cfg(feature = "hazmat")]
+ include_subject_key_identifier: true,
+ };
+
+ let subject = Name::from_str("CN=service.domination.world").unwrap();
+ let pub_key =
+ SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");
+
+ let signer = ecdsa_signer();
+ let builder = CertificateBuilder::new(
+ profile,
+ serial_number.clone(),
+ validity.clone(),
+ subject.clone(),
+ pub_key.clone(),
+ &signer,
+ )
+ .expect("Create certificate");
+
+ let certificate = builder.build::<DerSignature>().unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ // TODO(baloo): not too sure we should tackle those in this API.
+ let ignored = vec![
+ "e_sub_cert_aia_missing",
+ "e_sub_cert_crl_distribution_points_missing",
+ "w_sub_cert_aia_does_not_contain_issuing_ca_url",
+ // Missing policies
+ "e_sub_cert_certificate_policies_missing",
+ "e_sub_cert_cert_policy_empty",
+ // Needs to be added by the end-user
+ "e_sub_cert_aia_does_not_contain_ocsp_url",
+ // SAN needs to include DNS name (if used)
+ "e_ext_san_missing",
+ "e_subject_common_name_not_exactly_from_san",
+ // Extended key usage needs to be added by end-user and is use-case dependent
+ "e_sub_cert_eku_missing",
+ ];
+
+ zlint::check_certificate(pem.as_bytes(), &ignored);
+
+ #[cfg(feature = "hazmat")]
+ {
+ let profile = Profile::Leaf {
+ issuer,
+ enable_key_agreement: false,
+ enable_key_encipherment: false,
+ include_subject_key_identifier: false,
+ };
+ let builder =
+ CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
+ .expect("Create certificate");
+
+ let certificate = builder.build::<DerSignature>().unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ // Ignore warning about leaf not having SKI extension (this is a warning not a fail, as
+ // denoted by the `w_` prefix.
+ let mut ignored = ignored;
+ ignored.push("w_ext_subject_key_identifier_missing_sub_cert");
+ zlint::check_certificate(pem.as_bytes(), &ignored);
+ }
+}
+
+#[test]
+fn pss_certificate() {
+ let serial_number = SerialNumber::from(42u32);
+ let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+
+ let issuer =
+ Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+ let profile = Profile::Leaf {
+ issuer,
+ enable_key_agreement: false,
+ enable_key_encipherment: false,
+ #[cfg(feature = "hazmat")]
+ include_subject_key_identifier: true,
+ };
+
+ let subject = Name::from_str("CN=service.domination.world").unwrap();
+ let pub_key =
+ SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER_EXAMPLE).expect("get rsa pub key");
+
+ let signer = rsa_pss_signer();
+ let builder =
+ CertificateBuilder::new(profile, serial_number, validity, subject, pub_key, &signer)
+ .expect("Create certificate");
+
+ let certificate = builder
+ .build_with_rng::<rsa::pss::Signature>(&mut rand::thread_rng())
+ .unwrap();
+
+ let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+ println!("{}", openssl::check_certificate(pem.as_bytes()));
+
+ // TODO(baloo): not too sure we should tackle those in this API.
+ let ignored = &[
+ "e_sub_cert_aia_missing",
+ "e_sub_cert_crl_distribution_points_missing",
+ "w_sub_cert_aia_does_not_contain_issuing_ca_url",
+ // Missing policies
+ "e_sub_cert_certificate_policies_missing",
+ "e_sub_cert_cert_policy_empty",
+ // Needs to be added by the end-user
+ "e_sub_cert_aia_does_not_contain_ocsp_url",
+ // SAN needs to include DNS name (if used)
+ "e_ext_san_missing",
+ "e_subject_common_name_not_exactly_from_san",
+ // Extended key usage needs to be added by end-user and is use-case dependent
+ "e_sub_cert_eku_missing",
+ // zlint warns on RSAPSS signature algorithms
+ "e_signature_algorithm_not_supported",
+ ];
+
+ zlint::check_certificate(pem.as_bytes(), ignored);
+}
+
+const RSA_2048_PRIV_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-priv.der");
+
+fn rsa_signer() -> SigningKey<Sha256> {
+ let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap();
+ let signing_key = SigningKey::<Sha256>::new(private_key);
+ signing_key
+}
+
+fn rsa_pss_signer() -> rsa::pss::SigningKey<Sha256> {
+ let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER_EXAMPLE).unwrap();
+ let signing_key = rsa::pss::SigningKey::<Sha256>::new(private_key);
+ signing_key
+}
+
+const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("examples/p256-priv.der");
+
+fn ecdsa_signer() -> ecdsa::SigningKey<NistP256> {
+ let secret_key = p256::SecretKey::from_pkcs8_der(PKCS8_PRIVATE_KEY_DER).unwrap();
+ ecdsa::SigningKey::from(secret_key)
+}
+
+#[test]
+fn certificate_request() {
+ use std::net::{IpAddr, Ipv4Addr};
+ let subject = Name::from_str("CN=service.domination.world").unwrap();
+
+ let signer = ecdsa_signer();
+ let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request");
+ builder
+ .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4(
+ Ipv4Addr::new(192, 0, 2, 0),
+ ))]))
+ .unwrap();
+
+ let cert_req = builder.build::<DerSignature>().unwrap();
+ let pem = cert_req.to_pem(LineEnding::LF).expect("generate pem");
+ use std::fs::File;
+ use std::io::Write;
+ let mut file = File::create("/tmp/ecdsa.csr").expect("create pem file");
+ file.write_all(pem.as_bytes()).expect("Create pem file");
+ println!("{}", openssl::check_request(pem.as_bytes()));
+}
+
+#[test]
+fn certificate_request_attributes() {
+ let subject = Name::from_str("CN=service.domination.world").unwrap();
+
+ let signer = ecdsa_signer();
+ let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request");
+ builder
+ .add_attribute(&request::attributes::ChallengePassword(
+ DirectoryString::PrintableString(
+ PrintableString::new(b"password1234")
+ .expect("create printable string with password"),
+ ),
+ ))
+ .expect("unable to add attribute");
+
+ let cert_req = builder.build::<DerSignature>().unwrap();
+ let pem = cert_req.to_pem(LineEnding::LF).expect("generate pem");
+ use std::fs::File;
+ use std::io::Write;
+ let mut file = File::create("/tmp/ecdsa.csr").expect("create pem file");
+ file.write_all(pem.as_bytes()).expect("Create pem file");
+ println!("{}", openssl::check_request(pem.as_bytes()));
+}
diff --git a/tests/certificate.rs b/tests/certificate.rs
index 86215e3..e5b3243 100644
--- a/tests/certificate.rs
+++ b/tests/certificate.rs
@@ -1,14 +1,18 @@
//! Certificate tests
use der::{
- asn1::{BitStringRef, ContextSpecific, ObjectIdentifier, UIntRef},
+ asn1::{BitStringRef, ContextSpecific, ObjectIdentifier, PrintableStringRef, Utf8StringRef},
Decode, DecodeValue, Encode, FixedTag, Header, Reader, Tag, Tagged,
};
use hex_literal::hex;
-use spki::AlgorithmIdentifier;
+use spki::AlgorithmIdentifierRef;
+use x509_cert::serial_number::SerialNumber;
use x509_cert::Certificate;
use x509_cert::*;
+#[cfg(feature = "pem")]
+use der::DecodePem;
+
// TODO - parse and compare extension values
const EXTENSIONS: &[(&str, bool)] = &[
("2.5.29.15", true),
@@ -113,15 +117,15 @@ fn reencode_cert() {
let defer_cert = DeferDecodeCertificate::from_der(der_encoded_cert).unwrap();
let parsed_tbs = TbsCertificate::from_der(defer_cert.tbs_certificate).unwrap();
- let reencoded_tbs = parsed_tbs.to_vec().unwrap();
+ let reencoded_tbs = parsed_tbs.to_der().unwrap();
assert_eq!(defer_cert.tbs_certificate, reencoded_tbs);
- let parsed_sigalg = AlgorithmIdentifier::from_der(defer_cert.signature_algorithm).unwrap();
- let reencoded_sigalg = parsed_sigalg.to_vec().unwrap();
+ let parsed_sigalg = AlgorithmIdentifierRef::from_der(defer_cert.signature_algorithm).unwrap();
+ let reencoded_sigalg = parsed_sigalg.to_der().unwrap();
assert_eq!(defer_cert.signature_algorithm, reencoded_sigalg);
let parsed_sig = BitStringRef::from_der(defer_cert.signature).unwrap();
- let reencoded_sig = parsed_sig.to_vec().unwrap();
+ let reencoded_sig = parsed_sig.to_der().unwrap();
assert_eq!(defer_cert.signature, reencoded_sig);
let parsed_coverage_tbs =
@@ -129,22 +133,22 @@ fn reencode_cert() {
// TODO - defer decode then re-encode version field
- let encoded_serial = parsed_tbs.serial_number.to_vec().unwrap();
+ let encoded_serial = parsed_tbs.serial_number.to_der().unwrap();
assert_eq!(parsed_coverage_tbs.serial_number, encoded_serial);
- let encoded_signature = parsed_tbs.signature.to_vec().unwrap();
+ let encoded_signature = parsed_tbs.signature.to_der().unwrap();
assert_eq!(parsed_coverage_tbs.signature, encoded_signature);
- let encoded_issuer = parsed_tbs.issuer.to_vec().unwrap();
+ let encoded_issuer = parsed_tbs.issuer.to_der().unwrap();
assert_eq!(parsed_coverage_tbs.issuer, encoded_issuer);
- let encoded_validity = parsed_tbs.validity.to_vec().unwrap();
+ let encoded_validity = parsed_tbs.validity.to_der().unwrap();
assert_eq!(parsed_coverage_tbs.validity, encoded_validity);
- let encoded_subject = parsed_tbs.subject.to_vec().unwrap();
+ let encoded_subject = parsed_tbs.subject.to_der().unwrap();
assert_eq!(parsed_coverage_tbs.subject, encoded_subject);
- let encoded_subject_public_key_info = parsed_tbs.subject_public_key_info.to_vec().unwrap();
+ let encoded_subject_public_key_info = parsed_tbs.subject_public_key_info.to_der().unwrap();
assert_eq!(
parsed_coverage_tbs.subject_public_key_info,
encoded_subject_public_key_info
@@ -152,7 +156,7 @@ fn reencode_cert() {
// TODO - either encode as context specific or decode to sequence. for know lop off context
// specific tag and length
- let encoded_extensions = parsed_tbs.extensions.to_vec().unwrap();
+ let encoded_extensions = parsed_tbs.extensions.to_der().unwrap();
assert_eq!(&parsed_coverage_tbs.extensions[4..], encoded_extensions);
}
@@ -174,7 +178,7 @@ fn decode_oversized_oids() {
o1.to_string(),
"1.3.6.1.4.1.311.21.8.11672683.15464451.6967228.369088.2847561.77.4994205.11305917"
);
- let enc_oid = o1.to_vec().unwrap();
+ let enc_oid = o1.to_der().unwrap();
assert_eq!(
&hex!("06252B060104018237150885C8B86B87AFF00383A99F3C96C34081ADE6494D82B0E91D85B2873D"),
enc_oid.as_slice()
@@ -207,20 +211,28 @@ fn decode_cert() {
];
assert_eq!(
cert.tbs_certificate.serial_number,
- UIntRef::new(&target_serial).unwrap()
+ SerialNumber::new(&target_serial).unwrap()
);
assert_eq!(
cert.tbs_certificate.signature.oid.to_string(),
"1.2.840.113549.1.1.11"
);
assert_eq!(
- cert.tbs_certificate.signature.parameters.unwrap().tag(),
+ cert.tbs_certificate
+ .signature
+ .parameters
+ .as_ref()
+ .unwrap()
+ .tag(),
Tag::Null
);
- assert_eq!(
- cert.tbs_certificate.signature.parameters.unwrap().is_null(),
- true
- );
+ assert!(cert
+ .tbs_certificate
+ .signature
+ .parameters
+ .as_ref()
+ .unwrap()
+ .is_null());
let mut counter = 0;
let i = cert.tbs_certificate.issuer.0.iter();
@@ -229,20 +241,30 @@ fn decode_cert() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "Mock");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "Mock"
+ );
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.utf8_string().unwrap().to_string(),
+ Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"IdenTrust Services LLC"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.utf8_string().unwrap().to_string(),
+ Utf8StringRef::try_from(&atav.value).unwrap().to_string(),
"PTE IdenTrust Global Common Root CA 1"
);
}
@@ -276,24 +298,35 @@ fn decode_cert() {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Test Federal Bridge CA"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"TestFPKI"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"U.S. Government"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
}
counter += 1;
}
@@ -312,23 +345,23 @@ fn decode_cert() {
.subject_public_key_info
.algorithm
.parameters
+ .as_ref()
.unwrap()
.tag(),
Tag::Null
);
- assert_eq!(
- cert.tbs_certificate
- .subject_public_key_info
- .algorithm
- .parameters
- .unwrap()
- .is_null(),
- true
- );
+ assert!(cert
+ .tbs_certificate
+ .subject_public_key_info
+ .algorithm
+ .parameters
+ .as_ref()
+ .unwrap()
+ .is_null());
// TODO - parse and compare public key
- let exts = cert.tbs_certificate.extensions.unwrap();
+ let exts = cert.tbs_certificate.extensions.as_ref().unwrap();
for (ext, (oid, crit)) in exts.iter().zip(EXTENSIONS) {
assert_eq!(ext.extn_id.to_string(), *oid);
assert_eq!(ext.critical, *crit);
@@ -339,13 +372,96 @@ fn decode_cert() {
"1.2.840.113549.1.1.11"
);
assert_eq!(
- cert.signature_algorithm.parameters.unwrap().tag(),
+ cert.signature_algorithm.parameters.as_ref().unwrap().tag(),
Tag::Null
);
- assert_eq!(cert.signature_algorithm.parameters.unwrap().is_null(), true);
+ assert!(cert
+ .signature_algorithm
+ .parameters
+ .as_ref()
+ .unwrap()
+ .is_null());
assert_eq!(
&hex!("2A892F357BF3EF19E1211986106803FA18E66237802F1B1B0C6756CE678DB01D72CD0A4EB7171C2CDDF110ACD38AA65C35699E869C219AD7550AA4F287BB784F72EF8C9EA0E3DD103EFE5BF182EA36FFBCB45AAE65840263680534789C4F3215AF5454AD48CBC4B7A881E0135401A0BD5A849C11101DD1C66178E762C00DF59DD50F8DE9ED46FC6A0D742AE5697D87DD08DAC5291A75FB13C82FF2865C9E36799EA726137E1814E6A878C9532E8FC3D0A2A942D1CCC668FFCEAC255E6002FDE5ACDF2CE47556BB141C3A797A4BFDB673F6F1C229D7914FFEEF1505EE36F8038137D1B8F90106994BAB3E6FF0F60360A2E32F7A30B7ECEC1502DF3CC725BD6E436BA8F96A1847C9CEBB3F5A5906472292501D59BE1A98475BB1F30B677FAA8A45E351640C85B1B22661D33BD23EC6C0CA33DDD79E1120C7FC869EC4D0175ADB4A258AEAC5E8D2F0F578B8BF4B2C5DCC3269768AAA5B9E26D0592C5BB09C702C72E0A60F66D3EEB2B4983279634D59B0A2011B0E26AE796CC95D3243DF49615434E5CC06C374C3F936C005D360CAE6101F3AE7E97E29A157F5020770D4648D7877EBF8248CF3F3E68F9957A36F92D50616F2C60D3842327EF9BC0312CFF03A48C78E97254C2ADEADCA05069168443D833831FF66295A2EED685F164F1DBE01F8C897E1F63D42851682CBEE7B5A64D7BA2923D33644DBF1F7B3EDCE996F9928F043"),
cert.signature.raw_bytes()
);
+
+ #[cfg(feature = "pem")]
+ {
+ let pem_encoded_cert =
+ include_bytes!("examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.pem");
+ let result = Certificate::from_pem(pem_encoded_cert);
+ let pem_cert: Certificate = result.unwrap();
+
+ assert_eq!(pem_cert, cert);
+ }
+}
+
+#[test]
+fn decode_cert_negative_serial_number() {
+ let der_encoded_cert = include_bytes!("examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.der");
+
+ let cert = Certificate::from_der(der_encoded_cert).unwrap();
+ assert_eq!(
+ cert.tbs_certificate.serial_number.as_bytes(),
+ // INTEGER (125 bit) -2.370157924795571e+37
+ &[238, 43, 61, 235, 212, 33, 222, 20, 168, 98, 172, 4, 243, 221, 196, 1]
+ );
+
+ let reencoded = cert.to_der().unwrap();
+ assert_eq!(der_encoded_cert, reencoded.as_slice());
+}
+
+#[cfg(all(feature = "pem", feature = "hazmat"))]
+#[test]
+fn decode_cert_overlength_serial_number() {
+ use der::{pem::LineEnding, DecodePem, EncodePem};
+ use x509_cert::certificate::CertificateInner;
+
+ let pem_encoded_cert = include_bytes!("examples/qualcomm.pem");
+
+ assert!(Certificate::from_pem(pem_encoded_cert).is_err());
+
+ let cert = CertificateInner::<x509_cert::certificate::Raw>::from_pem(pem_encoded_cert).unwrap();
+ assert_eq!(
+ cert.tbs_certificate.serial_number.as_bytes(),
+ &[
+ 0, 132, 206, 11, 246, 160, 254, 130, 78, 229, 229, 6, 202, 168, 157, 120, 198, 21, 1,
+ 98, 87, 113
+ ]
+ );
+ assert_eq!(cert.tbs_certificate.serial_number.as_bytes().len(), 22);
+
+ let reencoded = cert.to_pem(LineEnding::LF).unwrap();
+ assert_eq!(pem_encoded_cert, reencoded.as_bytes());
+}
+
+#[cfg(all(feature = "pem"))]
+#[test]
+fn load_certificate_chains() {
+ let pem_encoded_chain = include_bytes!("examples/crates.io-chain.pem");
+
+ let chain = Certificate::load_pem_chain(pem_encoded_chain).expect("parse certificate chain");
+
+ assert_eq!(chain.len(), 4, "4 certificates are expected in this chain");
+}
+
+#[cfg(feature = "arbitrary")]
+#[test]
+// Purpose of this check is to ensure the arbitraty trait is provided for certificate variants
+#[allow(unused)]
+fn certificate_arbitrary() {
+ fn check_arbitrary<'a>(_arbitrary: impl arbitrary::Arbitrary<'a>) {}
+
+ fn check_certificate(certificate: x509_cert::Certificate) {
+ check_arbitrary(certificate);
+ }
+
+ #[cfg(feature = "hazmat")]
+ fn check_raw_certificate(
+ certificate: x509_cert::certificate::CertificateInner<x509_cert::certificate::Raw>,
+ ) {
+ check_arbitrary(certificate);
+ }
}
diff --git a/tests/certreq.rs b/tests/certreq.rs
index 547524c..82ce41c 100644
--- a/tests/certreq.rs
+++ b/tests/certreq.rs
@@ -1,6 +1,7 @@
//! Certification request (`CertReq`) tests
-use der::{Encode, Tag, Tagged};
+use der::asn1::{PrintableStringRef, Utf8StringRef};
+use der::{Decode, Encode, Tag, Tagged};
use hex_literal::hex;
use x509_cert::request::{CertReq, Version};
@@ -28,7 +29,7 @@ const EXTENSIONS: &[(&str, &[u8])] = &[
#[test]
fn decode_rsa_2048_der() {
- let cr = CertReq::try_from(RSA_2048_DER_EXAMPLE).unwrap();
+ let cr = CertReq::from_der(RSA_2048_DER_EXAMPLE).unwrap();
// Check the version.
assert_eq!(cr.info.version, Version::V1);
@@ -38,8 +39,8 @@ fn decode_rsa_2048_der() {
for (name, (oid, val)) in cr.info.subject.0.iter().zip(NAMES) {
let kind = name.0.get(0).unwrap();
let value = match kind.value.tag() {
- Tag::Utf8String => kind.value.utf8_string().unwrap().as_str(),
- Tag::PrintableString => kind.value.printable_string().unwrap().as_str(),
+ Tag::Utf8String => Utf8StringRef::try_from(&kind.value).unwrap().as_str(),
+ Tag::PrintableString => PrintableStringRef::try_from(&kind.value).unwrap().as_str(),
_ => panic!("unexpected tag"),
};
@@ -52,7 +53,7 @@ fn decode_rsa_2048_der() {
let alg = cr.info.public_key.algorithm;
assert_eq!(alg.oid, "1.2.840.113549.1.1.1".parse().unwrap());
assert!(alg.parameters.unwrap().is_null());
- assert_eq!(cr.info.public_key.subject_public_key, RSA_KEY);
+ assert_eq!(cr.info.public_key.subject_public_key.raw_bytes(), RSA_KEY);
// Check the attributes (just one; contains extensions).
assert_eq!(cr.info.attributes.len(), 1);
@@ -62,10 +63,10 @@ fn decode_rsa_2048_der() {
// Check the extensions.
let extensions: x509_cert::ext::Extensions =
- attribute.values.get(0).unwrap().decode_into().unwrap();
+ attribute.values.get(0).unwrap().decode_as().unwrap();
for (ext, (oid, val)) in extensions.iter().zip(EXTENSIONS) {
assert_eq!(ext.extn_id, oid.parse().unwrap());
- assert_eq!(ext.extn_value, *val);
+ assert_eq!(ext.extn_value.as_bytes(), *val);
assert!(!ext.critical);
}
@@ -80,7 +81,7 @@ fn decode_rsa_2048_der() {
#[test]
fn encode_rsa_2048_der() {
- let cr = CertReq::try_from(RSA_2048_DER_EXAMPLE).unwrap();
- let cr_encoded = cr.to_vec().unwrap();
+ let cr = CertReq::from_der(RSA_2048_DER_EXAMPLE).unwrap();
+ let cr_encoded = cr.to_der().unwrap();
assert_eq!(RSA_2048_DER_EXAMPLE, cr_encoded.as_slice());
}
diff --git a/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.pem b/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.pem
new file mode 100644
index 0000000..f13043d
--- /dev/null
+++ b/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.pem
@@ -0,0 +1,61 @@
+-----BEGIN CERTIFICATE-----
+MIILDDCCCPSgAwIBAgIQfwAAAQAAAUnPcGZNAAAAAjANBgkqhkiG9w0BAQsFADBt
+MQswCQYDVQQGEwJVUzENMAsGA1UEChMETW9jazEfMB0GA1UECgwWSWRlblRydXN0
+IFNlcnZpY2VzIExMQzEuMCwGA1UEAwwlUFRFIElkZW5UcnVzdCBHbG9iYWwgQ29t
+bW9uIFJvb3QgQ0EgMTAeFw0xNDExMjAyMzAxMzBaFw0xODAxMjIxMzQzMTNaMFsx
+HzAdBgNVBAMTFlRlc3QgRmVkZXJhbCBCcmlkZ2UgQ0ExETAPBgNVBAsTCFRlc3RG
+UEtJMRgwFgYDVQQKEw9VLlMuIEdvdmVybm1lbnQxCzAJBgNVBAYTAlVTMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBZsJer07n0I60VOfHzZ3yk4oPAM
+2Qh8pZFT6Dp8OSOxFouoib7EhoElJ66AMTO+2SUXP8DgiUOQ7qR7AdKBipqaZP1B
+T+3sjvlLr1faiCrPLmGB+GmbaORGeIwezH3J5dmVY4UBKoH4GIZvo65w99OiSf4N
+tSqE4RDqZr+KM8LoE7FnJIpCrWk1lScs7WcoLWul8KKMGhf9JcEkl1yEzrYOxRcY
+T2rilBBED07TLaTEj7vb/nq6K8rewszjmPAvI/TVoW1mXE1pzfSxVqzQ+jfPgDAN
+BEJGWwP3+qHxp3FfFAgc1QpC05366xFxbwPaMfFF6kfzSZTdIXQOtKkY+QIDAQAB
+o4IGuDCCBrQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wggIOBgNV
+HSEEggIFMIICATAZBgtghkgBhvkvAGQCAQYKYIZIAWUDAgEwAjAZBgtghkgBhvkv
+AGQCAgYKYIZIAWUDAgEwAjAZBgtghkgBhvkvAGQDAQYKYIZIAWUDAgEwAzAZBgtg
+hkgBhvkvAGQDAgYKYIZIAWUDAgEwAzAZBgtghkgBhvkvAGQOAQYKYIZIAWUDAgEw
+BTAZBgtghkgBhvkvAGQOAgYKYIZIAWUDAgEwBTAZBgtghkgBhvkvAGQMAQYKYIZI
+AWUDAgEwBDAZBgtghkgBhvkvAGQMAgYKYIZIAWUDAgEwBDAZBgtghkgBhvkvAGQP
+AQYKYIZIAWUDAgEwBjAZBgtghkgBhvkvAGQPAgYKYIZIAWUDAgEwBjAZBgtghkgB
+hvkvAGQSAAYKYIZIAWUDAgEwTjAZBgtghkgBhvkvAGQSAQYKYIZIAWUDAgEwTjAZ
+BgtghkgBhvkvAGQSAgYKYIZIAWUDAgEwTjAZBgtghkgBhvkvAGQTAQYKYIZIAWUD
+AgEwTzAZBgtghkgBhvkvAGQUAQYKYIZIAWUDAgEwUDAZBgtghkgBhvkvAGQlAQYK
+YIZIAWUDAgEwYzAZBgtghkgBhvkvAGQlAgYKYIZIAWUDAgEwYzAZBgtghkgBhvkv
+AGQmAQYKYIZIAWUDAgEwZDAZBgtghkgBhvkvAGQmAgYKYIZIAWUDAgEwZDCCAnAG
+A1UdIASCAmcwggJjMA0GC2CGSAGG+S8AZAIBMA0GC2CGSAGG+S8AZAICMA0GC2CG
+SAGG+S8AZAMBMA0GC2CGSAGG+S8AZAMCMA0GC2CGSAGG+S8AZA4BMA0GC2CGSAGG
++S8AZA4CMA0GC2CGSAGG+S8AZAwBMA0GC2CGSAGG+S8AZAwCMA0GC2CGSAGG+S8A
+ZA8BMA0GC2CGSAGG+S8AZA8CMA0GC2CGSAGG+S8AZBIAMA0GC2CGSAGG+S8AZBIB
+MA0GC2CGSAGG+S8AZBICMA0GC2CGSAGG+S8AZBMBMA0GC2CGSAGG+S8AZBQBMA0G
+C2CGSAGG+S8AZCUBMA0GC2CGSAGG+S8AZCUCMA0GC2CGSAGG+S8AZCYBMIIBUQYL
+YIZIAYb5LwBkJgIwggFAMEsGCCsGAQUFBwIBFj9odHRwczovL3NlY3VyZS5pZGVu
+dHJ1c3QuY29tL2NlcnRpZmljYXRlcy9wb2xpY3kvSUdDL2luZGV4Lmh0bWwwgfAG
+CCsGAQUFBwICMIHjGoHgVGhpcyBpcyBhIHRlc3QgY2VydGlmaWNhdGUuIERvIE5v
+dCBSZWx5LiBDZXJ0aWZpY2F0ZSB1c2UgcmVzdHJpY3RlZCB0byBSZWx5aW5nIFBh
+cnR5KHMpIGluIGFjY29yZGFuY2Ugd2l0aCBJR0MtQ1AgKHNlZSBodHRwczovL3Nl
+Y3VyZS5pZGVudHJ1c3QuY29tL2NlcnRpZmljYXRlcy9wb2xpY3kvSUdDL2luZGV4
+Lmh0bWwpLiBJc3N1ZXIncyBDUFMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZS4w
+HgYDVR0OBBcEFdvT3r8Ne2FbMoA7wCBs16rdObis/zCBxQYDVR0fBIG9MIG6MDqg
+OKA2hjRodHRwOi8vY3JsLXB0ZS5pZGVudHJ1c3QuY29tLnRlc3QvY3JsL0lHQ1Jv
+b3RjYTEuY3JsMHygeqB4hnZsZGFwOi8vbGRhcC1wdGUuaWRlbnRydXN0LmNvbS50
+ZXN0L2NuJTNESUdDJTIwUm9vdCUyMENBMSUyQ28lM0RJZGVuVHJ1c3QlMkNjJTNE
+VVMlM0ZjZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0JTNCYmluYXJ5MGEGCCsGAQUF
+BwELBFUwUzBRBggrBgEFBQcwBYZFaHR0cDovL2h0dHAuY2l0ZS5mcGtpLWxhYi5n
+b3YudGVzdC9icmlkZ2UvY2FDZXJ0c0lzc3VlZEJ5dGVzdEZCQ0EucDdjMIGRBggr
+BgEFBQcBAQSBhDCBgTBDBggrBgEFBQcwAoY3aHR0cDovL2FwcHMtc3RnLmlkZW50
+cnVzdC5jb20udGVzdC9yb290cy9JR0NSb290Y2ExLnA3YzA6BggrBgEFBQcwAYYu
+aHR0cDovL2lnY3Jvb3RwdGUub2NzcC5pZGVudHJ1c3QuY29tLnRlc3Q6ODEyNTAK
+BgNVHTYEAwIBADAgBgNVHSMEGTAXgBV8TIY6uAvViYcL7bfhG70qCLs9I/8wDQYJ
+KoZIhvcNAQELBQADggIBACqJLzV78+8Z4SEZhhBoA/oY5mI3gC8bGwxnVs5njbAd
+cs0KTrcXHCzd8RCs04qmXDVpnoacIZrXVQqk8oe7eE9y74yeoOPdED7+W/GC6jb/
+vLRarmWEAmNoBTR4nE8yFa9UVK1Iy8S3qIHgE1QBoL1ahJwREB3RxmF452LADfWd
+1Q+N6e1G/GoNdCrlaX2H3QjaxSkadfsTyC/yhlyeNnmepyYTfhgU5qh4yVMuj8PQ
+oqlC0czGaP/OrCVeYAL95azfLOR1VrsUHDp5ekv9tnP28cIp15FP/u8VBe42+AOB
+N9G4+QEGmUurPm/w9gNgouMvejC37OwVAt88xyW9bkNrqPlqGEfJzrs/WlkGRyKS
+UB1ZvhqYR1ux8wtnf6qKReNRZAyFsbImYdM70j7GwMoz3deeESDH/IaexNAXWttK
+JYrqxejS8PV4uL9LLF3MMml2iqpbnibQWSxbsJxwLHLgpg9m0+6ytJgyeWNNWbCi
+ARsOJq55bMldMkPfSWFUNOXMBsN0w/k2wAXTYMrmEB865+l+KaFX9QIHcNRkjXh3
+6/gkjPPz5o+ZV6NvktUGFvLGDThCMn75vAMSz/A6SMeOlyVMKt6tygUGkWhEPYM4
+Mf9mKVou7WhfFk8dvgH4yJfh9j1ChRaCy+57WmTXuikj0zZE2/H3s+3OmW+ZKPBD
+-----END CERTIFICATE-----
diff --git a/tests/examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.der b/tests/examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.der
new file mode 100644
index 0000000..f1317bf
--- /dev/null
+++ b/tests/examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.der
Binary files differ
diff --git a/tests/examples/crates.io-chain.pem b/tests/examples/crates.io-chain.pem
new file mode 100644
index 0000000..1927099
--- /dev/null
+++ b/tests/examples/crates.io-chain.pem
@@ -0,0 +1,122 @@
+This is an X.509 certificate chain serialized as PEM, with a preamble.
+
+-----BEGIN CERTIFICATE-----
+MIIF/jCCBOagAwIBAgIQC6Hm8WViUmt88+VrEPNELzANBgkqhkiG9w0BAQsFADA8
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRwwGgYDVQQDExNBbWF6b24g
+UlNBIDIwNDggTTAyMB4XDTIzMDEyNTAwMDAwMFoXDTI0MDIyMzIzNTk1OVowFDES
+MBAGA1UEAxMJY3JhdGVzLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEA5ivp2b8HKoaSyAq/Gt4ZJbELL/FhVNXhnHmPvdhvkD6vtGSxJQhbACftNBB4
+Kor8zK+6o1mQSpR1SNnQ5OKJIko4s+e7p4tuFV3b4LSxh+nNBZmBTvEmb5nCVs/c
+78hXtLPu48Ws4rd+Ujz6ZEcGfK15X+XT04sJFoQRSnkTwhO3DsRXwUXxhckvNJen
+R6i0dH3Um5I7sIfzgxMFh66akg2PE8s7/oM0VCY4+9hwvWRTqpz+Byi3XO1Ph+BZ
+cvJhLwn4t/KdGaSU7EV8+qhNu4mP6dsjCY03Gpha1wCvlh4Vh/TsqR3R4oPhhohh
++W3YdrTe8Deqs5nW9XrtB98F/QIDAQABo4IDIjCCAx4wHwYDVR0jBBgwFoAUwDFS
+zVpQw4J8dHHOy+mc+XrrguIwHQYDVR0OBBYEFB2jpSCo7Y3rXtxOmQPni/vQ8Ihd
+MFQGA1UdEQRNMEuCCWNyYXRlcy5pb4IbY2xvdWRmcm9udC1zdGF0aWMuY3JhdGVz
+Lmlvgg9pbmRleC5jcmF0ZXMuaW+CEHN0YXRpYy5jcmF0ZXMuaW8wDgYDVR0PAQH/
+BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA7BgNVHR8ENDAy
+MDCgLqAshipodHRwOi8vY3JsLnIybTAyLmFtYXpvbnRydXN0LmNvbS9yMm0wMi5j
+cmwwEwYDVR0gBAwwCjAIBgZngQwBAgEwdQYIKwYBBQUHAQEEaTBnMC0GCCsGAQUF
+BzABhiFodHRwOi8vb2NzcC5yMm0wMi5hbWF6b250cnVzdC5jb20wNgYIKwYBBQUH
+MAKGKmh0dHA6Ly9jcnQucjJtMDIuYW1hem9udHJ1c3QuY29tL3IybTAyLmNlcjAM
+BgNVHRMBAf8EAjAAMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdwDuzdBk1dsa
+zsVct520zROiModGfLzs3sNRSFlGcR+1mwAAAYXoOwzkAAAEAwBIMEYCIQDx3bii
+KZKB7CKHMUq25cNLF5RGhfqjRl0TfA1EUJw8ngIhANii/IksRi+gF3VZuqf+NpE6
+hamc+2sTJYmG/ICe7H3UAHUAc9meiRtMlnigIH1HneayxhzQUV5xGSqMa4AQesF3
+crUAAAGF6DsNTgAABAMARjBEAiBGBvFbTLCHkRTM71r0wslHjcary8UmGQ7rd7ws
+mZcoBAIgPZYVni2CIK0+U7aijle1LqEPDI2UtgnFtrKGefXhdAUAdgBIsONr2qZH
+NA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAYXoOw0UAAAEAwBHMEUCIE39dFmq
+xAUUmiJiGW9HS0/VbEIt0wC9VI0jCli9T28kAiEAx19Sl2Hzh62E0tISm5p1vmGr
+K3J+m3u3fm5+YcuCdUUwDQYJKoZIhvcNAQELBQADggEBAI3+jl3AGFqBVq3Amo97
+u86v/zHykGQQFvb9LyFdhIhw8OmI/C89YmUy+ZEVsc0GBO5jf3Xr/tUDidggbRva
+ajaR1lv5hWC6HjASg4dX/88ew5eKxV9ddjcNb15bzuxWPLw+X2NAq9S+QRnkDZkr
+/VW0rYSnu3YOEwxys56JSDzHx/UWqkUHb1bsO6ixxN8sbKjM4UDeB9WHvLHO85B8
+b5rgtqrT2vIqO1wtxxo3TY72eDEqu8N1MfatTEaOaJEQu86meGO7Wbx6QQX3OJc3
+ulPbBw+/o2sXvYxy251skJRwvyi00dGhxGSlRmbGGmXpWY97CPkY1iJwQ73uJ4qx
+Uj0=
+-----END CERTIFICATE-----
+
+
+-----BEGIN CERTIFICATE-----
+MIIEXjCCA0agAwIBAgITB3MSSkvL1E7HtTvq8ZSELToPoTANBgkqhkiG9w0BAQsF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAxMB4XDTIyMDgyMzIyMjUzMFoXDTMwMDgyMzIyMjUzMFowPDEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEcMBoGA1UEAxMTQW1hem9uIFJT
+QSAyMDQ4IE0wMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALtDGMZa
+qHneKei1by6+pUPPLljTB143Si6VpEWPc6mSkFhZb/6qrkZyoHlQLbDYnI2D7hD0
+sdzEqfnuAjIsuXQLG3A8TvX6V3oFNBFVe8NlLJHvBseKY88saLwufxkZVwk74g4n
+WlNMXzla9Y5F3wwRHwMVH443xGz6UtGSZSqQ94eFx5X7Tlqt8whi8qCaKdZ5rNak
++r9nUThOeClqFd4oXych//Rc7Y0eX1KNWHYSI1Nk31mYgiK3JvH063g+K9tHA63Z
+eTgKgndlh+WI+zv7i44HepRZjA1FYwYZ9Vv/9UkC5Yz8/yU65fgjaE+wVHM4e/Yy
+C2osrPWE7gJ+dXMCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD
+VR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV
+HQ4EFgQUwDFSzVpQw4J8dHHOy+mc+XrrguIwHwYDVR0jBBgwFoAUhBjMhTTsvAyU
+lC4IWZzHshBOCggwewYIKwYBBQUHAQEEbzBtMC8GCCsGAQUFBzABhiNodHRwOi8v
+b2NzcC5yb290Y2ExLmFtYXpvbnRydXN0LmNvbTA6BggrBgEFBQcwAoYuaHR0cDov
+L2NydC5yb290Y2ExLmFtYXpvbnRydXN0LmNvbS9yb290Y2ExLmNlcjA/BgNVHR8E
+ODA2MDSgMqAwhi5odHRwOi8vY3JsLnJvb3RjYTEuYW1hem9udHJ1c3QuY29tL3Jv
+b3RjYTEuY3JsMBMGA1UdIAQMMAowCAYGZ4EMAQIBMA0GCSqGSIb3DQEBCwUAA4IB
+AQAtTi6Fs0Azfi+iwm7jrz+CSxHH+uHl7Law3MQSXVtR8RV53PtR6r/6gNpqlzdo
+Zq4FKbADi1v9Bun8RY8D51uedRfjsbeodizeBB8nXmeyD33Ep7VATj4ozcd31YFV
+fgRhvTSxNrrTlNpWkUk0m3BMPv8sg381HhA6uEYokE5q9uws/3YkKqRiEz3TsaWm
+JqIRZhMbgAfp7O7FUwFIb7UIspogZSKxPIWJpxiPo3TcBambbVtQOcNRWz5qCQdD
+slI2yayq0n2TXoHyNCLEH8rpsJRVILFsg0jc7BaFrMnF462+ajSehgj12IidNeRN
+4zl+EoNaWdpnWndvSpAEkq2P
+-----END CERTIFICATE-----
+
+
+
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgITBn+USionzfP6wq4rAfkI7rnExjANBgkqhkiG9w0BAQsF
+ADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNj
+b3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4x
+OzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1
+dGhvcml0eSAtIEcyMB4XDTE1MDUyNTEyMDAwMFoXDTM3MTIzMTAxMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
+ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
+9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
+IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
+VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
+93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
+jgSubJrIqg0CAwEAAaOCATEwggEtMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBSEGMyFNOy8DJSULghZnMeyEE4KCDAfBgNVHSMEGDAW
+gBScXwDfqgHXMCs4iKK4bUqc8hGRgzB4BggrBgEFBQcBAQRsMGowLgYIKwYBBQUH
+MAGGImh0dHA6Ly9vY3NwLnJvb3RnMi5hbWF6b250cnVzdC5jb20wOAYIKwYBBQUH
+MAKGLGh0dHA6Ly9jcnQucm9vdGcyLmFtYXpvbnRydXN0LmNvbS9yb290ZzIuY2Vy
+MD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly9jcmwucm9vdGcyLmFtYXpvbnRydXN0
+LmNvbS9yb290ZzIuY3JsMBEGA1UdIAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQsF
+AAOCAQEAYjdCXLwQtT6LLOkMm2xF4gcAevnFWAu5CIw+7bMlPLVvUOTNNWqnkzSW
+MiGpSESrnO09tKpzbeR/FoCJbM8oAxiDR3mjEH4wW6w7sGDgd9QIpuEdfF7Au/ma
+eyKdpwAJfqxGF4PcnCZXmTA5YpaP7dreqsXMGz7KQ2hsVxa81Q4gLv7/wmpdLqBK
+bRRYh5TmOTFffHPLkIhqhBGWJ6bt2YFGpn6jcgAKUj6DiAdjd4lpFw85hdKrCEVN
+0FE6/V1dN2RMfjCyVSRCnTawXZwXgWHxyvkQAiSr6w10kY17RSlQOYiypok1JR4U
+akcjMS9cmvqtmg5iUaQqqcT5NJ0hGA==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEdTCCA12gAwIBAgIJAKcOSkw0grd/MA0GCSqGSIb3DQEBCwUAMGgxCzAJBgNV
+BAYTAlVTMSUwIwYDVQQKExxTdGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTIw
+MAYDVQQLEylTdGFyZmllbGQgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
+eTAeFw0wOTA5MDIwMDAwMDBaFw0zNDA2MjgxNzM5MTZaMIGYMQswCQYDVQQGEwJV
+UzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTElMCMGA1UE
+ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjE7MDkGA1UEAxMyU3RhcmZp
+ZWxkIFNlcnZpY2VzIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IC0gRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVDDrEKvlO4vW+GZdfjohTsR8/
+y8+fIBNtKTrID30892t2OGPZNmCom15cAICyL1l/9of5JUOG52kbUpqQ4XHj2C0N
+Tm/2yEnZtvMaVq4rtnQU68/7JuMauh2WLmo7WJSJR1b/JaCTcFOD2oR0FMNnngRo
+Ot+OQFodSk7PQ5E751bWAHDLUu57fa4657wx+UX2wmDPE1kCK4DMNEffud6QZW0C
+zyyRpqbn3oUYSXxmTqM6bam17jQuug0DuDPfR+uxa40l2ZvOgdFFRjKWcIfeAg5J
+Q4W2bHO7ZOphQazJ1FTfhy/HIrImzJ9ZVGif/L4qL8RVHHVAYBeFAlU5i38FAgMB
+AAGjgfAwge0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0O
+BBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMB8GA1UdIwQYMBaAFL9ft9HO3R+G9FtV
+rNzXEMIOqYjnME8GCCsGAQUFBwEBBEMwQTAcBggrBgEFBQcwAYYQaHR0cDovL28u
+c3MyLnVzLzAhBggrBgEFBQcwAoYVaHR0cDovL3guc3MyLnVzL3guY2VyMCYGA1Ud
+HwQfMB0wG6AZoBeGFWh0dHA6Ly9zLnNzMi51cy9yLmNybDARBgNVHSAECjAIMAYG
+BFUdIAAwDQYJKoZIhvcNAQELBQADggEBACMd44pXyn3pF3lM8R5V/cxTbj5HD9/G
+VfKyBDbtgB9TxF00KGu+x1X8Z+rLP3+QsjPNG1gQggL4+C/1E2DUBc7xgQjB3ad1
+l08YuW3e95ORCLp+QCztweq7dp4zBncdDQh/U90bZKuCJ/Fp1U1ervShw3WnWEQt
+8jxwmKy6abaVd38PMV4s/KCHOkdp8Hlf9BRUpJVeEXgSYCfOn8J3/yNTd126/+pZ
+59vPr5KW7ySaNRB6nJHGDn2Z9j8Z3/VyVOEVqQdZe4O/Ui5GjLIAZHYcSNPYeehu
+VsyuLAOQ1xk4meTKCRlb/weWsKh/NEnfVqn3sF/tM+2MR7cwA130A4w=
+-----END CERTIFICATE-----
+
diff --git a/tests/examples/p256-priv.der b/tests/examples/p256-priv.der
new file mode 100644
index 0000000..c0de45e
--- /dev/null
+++ b/tests/examples/p256-priv.der
Binary files differ
diff --git a/tests/examples/p256-pub.der b/tests/examples/p256-pub.der
new file mode 100644
index 0000000..67c719c
--- /dev/null
+++ b/tests/examples/p256-pub.der
Binary files differ
diff --git a/tests/examples/qualcomm.pem b/tests/examples/qualcomm.pem
new file mode 100644
index 0000000..5226062
--- /dev/null
+++ b/tests/examples/qualcomm.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAiGgAwIBAgIWAITOC/ag/oJO5eUGyqideMYVAWJXcTAKBggqhkjOPQQD
+AzB2MSQwIgYDVQQKDBtRdWFsY29tbSBUZWNobm9sb2dpZXMsIEluYy4xKjAoBgNV
+BAsMIVF1YWxjb21tIENyeXB0b2dyYXBoaWMgT3BlcmF0aW9uczEiMCAGA1UEAwwZ
+UU1DIEF0dGVzdGF0aW9uIFJvb3QgQ0EgNDAeFw0xNzA4MDEyMjE2MzJaFw0yNzA4
+MDEyMjE2MzJaMH4xJDAiBgNVBAoMG1F1YWxjb21tIFRlY2hub2xvZ2llcywgSW5j
+LjEqMCgGA1UECwwhUXVhbGNvbW0gQ3J5cHRvZ3JhcGhpYyBPcGVyYXRpb25zMSow
+KAYDVQQDDCFRTUMgQXR0ZXN0YXRpb24gUm9vdCBDQSA0IFN1YkNBIDEwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQDsjssSUEFLyyBe17UmO3pMzqKS+V1jfQkhq7a7zmH
+LCrPFmfaKLm0/szdzZxn+zwhoYen3fgJIuZUaip8wAQxLe4550c1ZBl3iSTvYUbe
+J+gBz2DiJHRBOtY1bQH35NWjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0P
+AQH/BAQDAgEGMB0GA1UdDgQWBBTrVYStHPbaTn4k7bPerqZAmJcuXzAfBgNVHSME
+GDAWgBQBBnkODO3o7rgWy996xOf1BxR4VTAKBggqhkjOPQQDAwNpADBmAjEAmpM/
+Xvfawl4/A3jd0VVb6lOBh0Jy+zFz1Jz/hw+Xpm9G4XJCscBE7r7lbe2Xc1DHAjEA
+psnskI8pLJQwL80QzAwP3HvgyDUeedNpxnYNK797vqJu6uRMLsZBVHatLM1R4gyE
+-----END CERTIFICATE-----
diff --git a/tests/examples/rsa2048-priv.der b/tests/examples/rsa2048-priv.der
new file mode 100644
index 0000000..bbf1876
--- /dev/null
+++ b/tests/examples/rsa2048-priv.der
Binary files differ
diff --git a/tests/examples/rsa2048-pub.der b/tests/examples/rsa2048-pub.der
new file mode 100644
index 0000000..4148aaa
--- /dev/null
+++ b/tests/examples/rsa2048-pub.der
Binary files differ
diff --git a/tests/general_name.rs b/tests/general_name.rs
index 5447c5f..f63b4a2 100644
--- a/tests/general_name.rs
+++ b/tests/general_name.rs
@@ -50,7 +50,7 @@ fn singular(#[case] idx: usize, #[case] value: &[u8]) {
_ => panic!("unexpected decoded value"),
}
- let encoded = decoded.to_vec().unwrap();
+ let encoded = decoded.to_der().unwrap();
assert_eq!(value, encoded);
}
@@ -75,6 +75,6 @@ fn plural(#[case] idx: usize, #[case] value: &[u8]) {
_ => panic!("unexpected decoded value"),
}
- let encoded = decoded.to_vec().unwrap();
+ let encoded = decoded.to_der().unwrap();
assert_eq!(value, encoded);
}
diff --git a/tests/name.rs b/tests/name.rs
index e52b2c7..27af2b5 100644
--- a/tests/name.rs
+++ b/tests/name.rs
@@ -2,7 +2,7 @@
use const_oid::ObjectIdentifier;
use der::asn1::{Ia5StringRef, OctetStringRef, PrintableStringRef, SetOfVec, Utf8StringRef};
-use der::{AnyRef, Decode, Encode, Tag, Tagged};
+use der::{Any, Decode, Encode, Tag, Tagged};
use hex_literal::hex;
use x509_cert::attr::AttributeTypeAndValue;
use x509_cert::name::{Name, RdnSequence, RelativeDistinguishedName};
@@ -40,23 +40,53 @@ fn decode_name() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Test Certificates 2011"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Good CA"
);
}
counter += 1;
}
}
+
+ #[cfg(feature = "std")]
+ {
+ // https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1
+ // If the RDNSequence is an empty sequence, the result is the empty or
+ // zero-length string.
+ // Otherwise, the output consists of the string encodings of each
+ // RelativeDistinguishedName in the RDNSequence (according to Section 2.2),
+ // starting with the last element of the sequence and moving backwards
+ // toward the first.
+ // The encodings of adjoining RelativeDistinguishedNames are separated by
+ // a comma (',' U+002C) character.
+ let name = rdn1a.to_string();
+ assert_eq!(name, "CN=Good CA,O=Test Certificates 2011,C=US");
+
+ // https://github.com/RustCrypto/formats/issues/1121
+ let rdn1 = Name::from_der(&hex!("3081c0310b30090603550406130255533113301106035504080c0a43616c69666f726e69613116301406035504070c0d4d6f756e7461696e205669657731133011060355040a0c0a476f6f676c65204c4c43311e301c06035504030c154f51464176444e4457732e676f6f676c652e636f6d31243022060355040b0c1b6d616e6167656d656e743a64732e67726f75702e3338393131313131293027060a0992268993f22c6401010c196964656e746974793a64732e67726f75702e33383931313131")[..]);
+ let rdn1a = rdn1.unwrap();
+ let name = rdn1a.to_string();
+ assert_eq!(name, "UID=identity:ds.group.3891111,OU=management:ds.group.3891111,CN=OQFAvDNDWs.google.com,O=Google LLC,L=Mountain View,ST=California,C=US");
+ }
}
#[test]
@@ -73,9 +103,9 @@ fn decode_rdn() {
for atav in i {
let oid = atav.oid;
assert_eq!(oid.to_string(), "2.5.4.6");
- let value = atav.value;
+ let value = &atav.value;
assert_eq!(value.tag(), Tag::PrintableString);
- let ps = value.printable_string().unwrap();
+ let ps = PrintableStringRef::try_from(value).unwrap();
assert_eq!(ps.to_string(), "US");
}
@@ -99,32 +129,32 @@ fn decode_rdn() {
let atav1a = i.next().unwrap();
let oid2 = atav1a.oid;
assert_eq!(oid2.to_string(), "2.5.4.10");
- let value2 = atav1a.value;
+ let value2 = &atav1a.value;
assert_eq!(value2.tag(), Tag::Utf8String);
- let utf8b = value2.utf8_string().unwrap();
+ let utf8b = Utf8StringRef::try_from(value2).unwrap();
assert_eq!(utf8b.to_string(), "123");
let atav2a = i.next().unwrap();
let oid1 = atav2a.oid;
assert_eq!(oid1.to_string(), "2.5.4.3");
- let value1 = atav2a.value;
+ let value1 = &atav2a.value;
assert_eq!(value1.tag(), Tag::Utf8String);
- let utf8a = value1.utf8_string().unwrap();
+ let utf8a = Utf8StringRef::try_from(value1).unwrap();
assert_eq!(utf8a.to_string(), "JOHN SMITH");
let mut from_scratch = RelativeDistinguishedName::default();
- assert!(from_scratch.0.add(*atav1a).is_ok());
- assert!(from_scratch.0.add(*atav2a).is_ok());
- let reencoded = from_scratch.to_vec().unwrap();
+ assert!(from_scratch.0.insert(atav1a.clone()).is_ok());
+ assert!(from_scratch.0.insert(atav2a.clone()).is_ok());
+ let reencoded = from_scratch.to_der().unwrap();
assert_eq!(
reencoded,
&hex!("311F300A060355040A0C03313233301106035504030C0A4A4F484E20534D495448")
);
let mut from_scratch2 = RelativeDistinguishedName::default();
- assert!(from_scratch2.0.add(*atav2a).is_ok());
+ assert!(from_scratch2.0.insert_ordered(atav2a.clone()).is_ok());
// fails when caller adds items not in DER lexicographical order
- assert!(from_scratch2.0.add(*atav1a).is_err());
+ assert!(from_scratch2.0.insert_ordered(atav1a.clone()).is_err());
// allow out-of-order RDNs (see: RustCrypto/formats#625)
assert!(RelativeDistinguishedName::from_der(
@@ -196,24 +226,24 @@ fn rdns_serde() {
],
"CN=foo,SN=bar,C=baz+L=bat",
&[
- &[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::CN,
- value: AnyRef::from(Utf8StringRef::new("foo").unwrap()),
- }],
- &[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::SN,
- value: AnyRef::from(Utf8StringRef::new("bar").unwrap()),
- }],
&[
AttributeTypeAndValue {
oid: const_oid::db::rfc4519::C,
- value: AnyRef::from(PrintableStringRef::new("baz").unwrap()),
+ value: Any::from(PrintableStringRef::new("baz").unwrap()),
},
AttributeTypeAndValue {
oid: const_oid::db::rfc4519::L,
- value: AnyRef::from(Utf8StringRef::new("bat").unwrap()),
+ value: Any::from(Utf8StringRef::new("bat").unwrap()),
},
],
+ &[AttributeTypeAndValue {
+ oid: const_oid::db::rfc4519::SN,
+ value: Any::from(Utf8StringRef::new("bar").unwrap()),
+ }],
+ &[AttributeTypeAndValue {
+ oid: const_oid::db::rfc4519::CN,
+ value: Any::from(Utf8StringRef::new("foo").unwrap()),
+ }],
],
),
(
@@ -221,16 +251,16 @@ fn rdns_serde() {
"UID=jsmith,DC=example,DC=net",
&[
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::UID,
- value: AnyRef::from(Utf8StringRef::new("jsmith").unwrap()),
+ oid: const_oid::db::rfc4519::DC,
+ value: Any::from(Ia5StringRef::new("net").unwrap()),
}],
&[AttributeTypeAndValue {
oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("example").unwrap()),
+ value: Any::from(Ia5StringRef::new("example").unwrap()),
}],
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("net").unwrap()),
+ oid: const_oid::db::rfc4519::UID,
+ value: Any::from(Utf8StringRef::new("jsmith").unwrap()),
}],
],
),
@@ -238,24 +268,24 @@ fn rdns_serde() {
&["OU=Sales+CN=J. Smith,DC=example,DC=net"],
"OU=Sales+CN=J. Smith,DC=example,DC=net",
&[
+ &[AttributeTypeAndValue {
+ oid: const_oid::db::rfc4519::DC,
+ value: Any::from(Ia5StringRef::new("net").unwrap()),
+ }],
+ &[AttributeTypeAndValue {
+ oid: const_oid::db::rfc4519::DC,
+ value: Any::from(Ia5StringRef::new("example").unwrap()),
+ }],
&[
AttributeTypeAndValue {
oid: const_oid::db::rfc4519::OU,
- value: AnyRef::from(Utf8StringRef::new("Sales").unwrap()),
+ value: Any::from(Utf8StringRef::new("Sales").unwrap()),
},
AttributeTypeAndValue {
oid: const_oid::db::rfc4519::CN,
- value: AnyRef::from(Utf8StringRef::new("J. Smith").unwrap()),
+ value: Any::from(Utf8StringRef::new("J. Smith").unwrap()),
},
],
- &[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("example").unwrap()),
- }],
- &[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("net").unwrap()),
- }],
],
),
(
@@ -263,16 +293,16 @@ fn rdns_serde() {
"CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net",
&[
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::CN,
- value: AnyRef::from(Utf8StringRef::new(r#"James "Jim" Smith, III"#).unwrap()),
+ oid: const_oid::db::rfc4519::DC,
+ value: Any::from(Ia5StringRef::new("net").unwrap()),
}],
&[AttributeTypeAndValue {
oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("example").unwrap()),
+ value: Any::from(Ia5StringRef::new("example").unwrap()),
}],
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("net").unwrap()),
+ oid: const_oid::db::rfc4519::CN,
+ value: Any::from(Utf8StringRef::new(r#"James "Jim" Smith, III"#).unwrap()),
}],
],
),
@@ -281,16 +311,16 @@ fn rdns_serde() {
"CN=Before\\0dAfter,DC=example,DC=net",
&[
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::CN,
- value: AnyRef::from(Utf8StringRef::new("Before\rAfter").unwrap()),
+ oid: const_oid::db::rfc4519::DC,
+ value: Any::from(Ia5StringRef::new("net").unwrap()),
}],
&[AttributeTypeAndValue {
oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("example").unwrap()),
+ value: Any::from(Ia5StringRef::new("example").unwrap()),
}],
&[AttributeTypeAndValue {
- oid: const_oid::db::rfc4519::DC,
- value: AnyRef::from(Ia5StringRef::new("net").unwrap()),
+ oid: const_oid::db::rfc4519::CN,
+ value: Any::from(Utf8StringRef::new("Before\rAfter").unwrap()),
}],
],
),
@@ -299,7 +329,7 @@ fn rdns_serde() {
"1.3.6.1.4.1.1466.0=#04024869",
&[&[AttributeTypeAndValue {
oid: ObjectIdentifier::new("1.3.6.1.4.1.1466.0").unwrap(),
- value: AnyRef::from(OctetStringRef::new(&[b'H', b'i']).unwrap()),
+ value: Any::from(OctetStringRef::new(&[b'H', b'i']).unwrap()),
}]],
),
];
@@ -319,7 +349,11 @@ fn rdns_serde() {
for input in inputs.iter() {
eprintln!("input: {}", input);
- let der = RdnSequence::encode_from_string(input).unwrap();
+ let der = input
+ .parse::<RdnSequence>()
+ .and_then(|rdn| rdn.to_der())
+ .unwrap();
+
let rdns = RdnSequence::from_der(&der).unwrap();
for (l, r) in brdns.0.iter().zip(rdns.0.iter()) {
diff --git a/tests/pkix_extensions.rs b/tests/pkix_extensions.rs
index c08fcb8..108eeb5 100644
--- a/tests/pkix_extensions.rs
+++ b/tests/pkix_extensions.rs
@@ -1,6 +1,6 @@
//! Certificate tests
use const_oid::AssociatedOid;
-use der::asn1::UIntRef;
+use der::asn1::{Ia5StringRef, OctetString, PrintableStringRef, Utf8StringRef};
use der::{Decode, Encode, ErrorKind, Length, Tag, Tagged};
use hex_literal::hex;
use x509_cert::ext::pkix::crl::dp::{DistributionPoint, ReasonFlags, Reasons};
@@ -8,7 +8,7 @@ use x509_cert::ext::pkix::name::{DistributionPointName, GeneralName, GeneralName
use x509_cert::ext::pkix::*;
use x509_cert::ext::Extensions;
use x509_cert::name::Name;
-use x509_cert::{Certificate, Version};
+use x509_cert::{serial_number::SerialNumber, Certificate, Version};
use const_oid::db::rfc5280::*;
use const_oid::db::rfc5912::ID_CE_CERTIFICATE_POLICIES;
@@ -17,93 +17,149 @@ fn spin_over_exts(exts: Extensions) {
for ext in exts {
match ext.extn_id {
SubjectDirectoryAttributes::OID => {
- let decoded = SubjectDirectoryAttributes::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded =
+ SubjectDirectoryAttributes::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
SubjectKeyIdentifier::OID => {
- let decoded = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = SubjectKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
KeyUsage::OID => {
- let decoded = KeyUsage::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = KeyUsage::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
PrivateKeyUsagePeriod::OID => {
- let decoded = PrivateKeyUsagePeriod::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = PrivateKeyUsagePeriod::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
SubjectAltName::OID => {
- let decoded = SubjectAltName::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = SubjectAltName::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
IssuerAltName::OID => {
- let decoded = IssuerAltName::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = IssuerAltName::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
BasicConstraints::OID => {
- let decoded = BasicConstraints::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = BasicConstraints::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
NameConstraints::OID => {
- let decoded = NameConstraints::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = NameConstraints::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
CrlDistributionPoints::OID => {
- let decoded = CrlDistributionPoints::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = CrlDistributionPoints::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
CertificatePolicies::OID => {
- let decoded = CertificatePolicies::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = CertificatePolicies::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
PolicyMappings::OID => {
- let decoded = PolicyMappings::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = PolicyMappings::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
AuthorityKeyIdentifier::OID => {
- let decoded = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = AuthorityKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
PolicyConstraints::OID => {
- let decoded = PolicyConstraints::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = PolicyConstraints::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
ExtendedKeyUsage::OID => {
- let decoded = ExtendedKeyUsage::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = ExtendedKeyUsage::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
FreshestCrl::OID => {
- let decoded = FreshestCrl::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = FreshestCrl::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
InhibitAnyPolicy::OID => {
- let decoded = InhibitAnyPolicy::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = InhibitAnyPolicy::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
AuthorityInfoAccessSyntax::OID => {
- let decoded = AuthorityInfoAccessSyntax::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded =
+ AuthorityInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
SubjectInfoAccessSyntax::OID => {
- let decoded = SubjectInfoAccessSyntax::from_der(ext.extn_value).unwrap();
- assert_eq!(ext.extn_value, decoded.to_vec().unwrap());
+ let decoded = SubjectInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).unwrap();
+ assert_eq!(
+ ext.extn_value,
+ decoded.to_der().and_then(OctetString::new).unwrap()
+ );
}
_ => {
@@ -138,7 +194,7 @@ fn decode_general_name() {
let bytes = hex!("A021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C");
match GeneralName::from_der(&bytes).unwrap() {
GeneralName::OtherName(other_name) => {
- let onval = other_name.value.utf8_string().unwrap();
+ let onval = Utf8StringRef::try_from(&other_name.value).unwrap();
assert_eq!(onval.to_string(), "Upn_214950130@mil");
}
_ => panic!("Failed to parse OtherName from GeneralName"),
@@ -162,27 +218,27 @@ fn decode_cert() {
assert_eq!(ext.extn_id.to_string(), ID_CE_KEY_USAGE.to_string());
assert_eq!(ext.critical, true);
- let ku = KeyUsage::from_der(ext.extn_value).unwrap();
+ let ku = KeyUsage::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku);
- let reencoded = ku.to_vec().unwrap();
+ let reencoded = ku.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
} else if 1 == counter {
assert_eq!(ext.extn_id.to_string(), ID_CE_BASIC_CONSTRAINTS.to_string());
assert_eq!(ext.critical, true);
- let bc = BasicConstraints::from_der(ext.extn_value).unwrap();
+ let bc = BasicConstraints::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(true, bc.ca);
assert!(bc.path_len_constraint.is_none());
- let reencoded = bc.to_vec().unwrap();
+ let reencoded = bc.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
} else if 2 == counter {
assert_eq!(ext.extn_id.to_string(), ID_CE_POLICY_MAPPINGS.to_string());
assert_eq!(ext.critical, false);
- let pm = PolicyMappings::from_der(ext.extn_value).unwrap();
+ let pm = PolicyMappings::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(19, pm.0.len());
- let reencoded = pm.to_vec().unwrap();
+ let reencoded = pm.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
let subject_domain_policy: [&str; 19] = [
@@ -247,10 +303,10 @@ fn decode_cert() {
ID_CE_CERTIFICATE_POLICIES.to_string()
);
assert_eq!(ext.critical, false);
- let cps = CertificatePolicies::from_der(ext.extn_value).unwrap();
+ let cps = CertificatePolicies::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(19, cps.0.len());
- let reencoded = cps.to_vec().unwrap();
+ let reencoded = cps.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
let ids: [&str; 19] = [
@@ -285,7 +341,8 @@ fn decode_cert() {
for pqi in pq.iter() {
if 0 == counter_pq {
assert_eq!("1.3.6.1.5.5.7.2.1", pqi.policy_qualifier_id.to_string());
- let cpsval = pqi.qualifier.unwrap().ia5_string().unwrap();
+ let cpsval =
+ Ia5StringRef::try_from(pqi.qualifier.as_ref().unwrap()).unwrap();
assert_eq!(
"https://secure.identrust.com/certificates/policy/IGC/index.html",
cpsval.to_string()
@@ -308,14 +365,14 @@ fn decode_cert() {
ID_CE_SUBJECT_KEY_IDENTIFIER.to_string()
);
assert_eq!(ext.critical, false);
- let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap();
+ let skid = SubjectKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(Length::new(21), skid.0.len());
assert_eq!(
&hex!("DBD3DEBF0D7B615B32803BC0206CD7AADD39B8ACFF"),
skid.0.as_bytes()
);
- let reencoded = skid.to_vec().unwrap();
+ let reencoded = skid.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
} else if 5 == counter {
assert_eq!(
@@ -323,10 +380,10 @@ fn decode_cert() {
ID_CE_CRL_DISTRIBUTION_POINTS.to_string()
);
assert_eq!(ext.critical, false);
- let crl_dps = CrlDistributionPoints::from_der(ext.extn_value).unwrap();
+ let crl_dps = CrlDistributionPoints::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(2, crl_dps.0.len());
- let reencoded = crl_dps.to_vec().unwrap();
+ let reencoded = crl_dps.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
let mut crldp_counter = 0;
@@ -381,10 +438,10 @@ fn decode_cert() {
ID_PE_SUBJECT_INFO_ACCESS.to_string()
);
assert_eq!(ext.critical, false);
- let sias = SubjectInfoAccessSyntax::from_der(ext.extn_value).unwrap();
+ let sias = SubjectInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(1, sias.0.len());
- let reencoded = sias.to_vec().unwrap();
+ let reencoded = sias.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
for sia in sias.0 {
@@ -408,11 +465,11 @@ fn decode_cert() {
ID_PE_AUTHORITY_INFO_ACCESS.to_string()
);
assert_eq!(ext.critical, false);
- let aias = AuthorityInfoAccessSyntax::from_der(ext.extn_value).unwrap();
+ let aias = AuthorityInfoAccessSyntax::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(2, aias.0.len());
let mut aia_counter = 0;
- let reencoded = aias.to_vec().unwrap();
+ let reencoded = aias.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
for aia in aias.0 {
@@ -454,10 +511,10 @@ fn decode_cert() {
ID_CE_INHIBIT_ANY_POLICY.to_string()
);
assert_eq!(ext.critical, false);
- let iap = InhibitAnyPolicy::from_der(ext.extn_value).unwrap();
+ let iap = InhibitAnyPolicy::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(0, iap.0);
- let reencoded = iap.to_vec().unwrap();
+ let reencoded = iap.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
} else if 9 == counter {
assert_eq!(
@@ -465,13 +522,13 @@ fn decode_cert() {
ID_CE_AUTHORITY_KEY_IDENTIFIER.to_string()
);
assert_eq!(ext.critical, false);
- let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap();
+ let akid = AuthorityKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(
&hex!("7C4C863AB80BD589870BEDB7E11BBD2A08BB3D23FF"),
- akid.key_identifier.unwrap().as_bytes()
+ akid.key_identifier.as_ref().unwrap().as_bytes()
);
- let reencoded = akid.to_vec().unwrap();
+ let reencoded = akid.to_der().and_then(OctetString::new).unwrap();
assert_eq!(ext.extn_value, reencoded);
}
@@ -486,18 +543,28 @@ fn decode_cert() {
let target_serial: [u8; 1] = [2];
assert_eq!(
cert.tbs_certificate.serial_number,
- UIntRef::new(&target_serial).unwrap()
+ SerialNumber::new(&target_serial).unwrap()
);
assert_eq!(
cert.tbs_certificate.signature.oid.to_string(),
"1.2.840.113549.1.1.11"
);
assert_eq!(
- cert.tbs_certificate.signature.parameters.unwrap().tag(),
+ cert.tbs_certificate
+ .signature
+ .parameters
+ .as_ref()
+ .unwrap()
+ .tag(),
Tag::Null
);
assert_eq!(
- cert.tbs_certificate.signature.parameters.unwrap().is_null(),
+ cert.tbs_certificate
+ .signature
+ .parameters
+ .as_ref()
+ .unwrap()
+ .is_null(),
true
);
@@ -508,17 +575,26 @@ fn decode_cert() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Test Certificates 2011"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Trust Anchor"
);
}
@@ -550,17 +626,26 @@ fn decode_cert() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Test Certificates 2011"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Good CA"
);
}
@@ -581,6 +666,7 @@ fn decode_cert() {
.subject_public_key_info
.algorithm
.parameters
+ .as_ref()
.unwrap()
.tag(),
Tag::Null
@@ -590,6 +676,7 @@ fn decode_cert() {
.subject_public_key_info
.algorithm
.parameters
+ .as_ref()
.unwrap()
.is_null(),
true
@@ -607,7 +694,7 @@ fn decode_cert() {
ID_CE_AUTHORITY_KEY_IDENTIFIER.to_string()
);
assert_eq!(ext.critical, false);
- let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap();
+ let akid = AuthorityKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(
akid.key_identifier.unwrap().as_bytes(),
&hex!("E47D5FD15C9586082C05AEBE75B665A7D95DA866")[..]
@@ -618,7 +705,7 @@ fn decode_cert() {
ID_CE_SUBJECT_KEY_IDENTIFIER.to_string()
);
assert_eq!(ext.critical, false);
- let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap();
+ let skid = SubjectKeyIdentifier::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(
skid.0.as_bytes(),
&hex!("580184241BBC2B52944A3DA510721451F5AF3AC9")[..]
@@ -626,7 +713,7 @@ fn decode_cert() {
} else if 2 == counter {
assert_eq!(ext.extn_id.to_string(), ID_CE_KEY_USAGE.to_string());
assert_eq!(ext.critical, true);
- let ku = KeyUsage::from_der(ext.extn_value).unwrap();
+ let ku = KeyUsage::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku);
} else if 3 == counter {
assert_eq!(
@@ -634,7 +721,7 @@ fn decode_cert() {
ID_CE_CERTIFICATE_POLICIES.to_string()
);
assert_eq!(ext.critical, false);
- let r = CertificatePolicies::from_der(ext.extn_value);
+ let r = CertificatePolicies::from_der(ext.extn_value.as_bytes());
let cp = r.unwrap();
let i = cp.0.iter();
for p in i {
@@ -643,7 +730,7 @@ fn decode_cert() {
} else if 4 == counter {
assert_eq!(ext.extn_id.to_string(), ID_CE_BASIC_CONSTRAINTS.to_string());
assert_eq!(ext.critical, true);
- let bc = BasicConstraints::from_der(ext.extn_value).unwrap();
+ let bc = BasicConstraints::from_der(ext.extn_value.as_bytes()).unwrap();
assert_eq!(bc.ca, true);
assert_eq!(bc.path_len_constraint, Option::None);
}
@@ -655,7 +742,7 @@ fn decode_cert() {
"1.2.840.113549.1.1.11"
);
assert_eq!(
- cert.signature_algorithm.parameters.unwrap().tag(),
+ cert.signature_algorithm.parameters.as_ref().unwrap().tag(),
Tag::Null
);
assert_eq!(cert.signature_algorithm.parameters.unwrap().is_null(), true);
diff --git a/tests/trust_anchor_format.rs b/tests/trust_anchor_format.rs
index bcc3e69..a1bdc69 100644
--- a/tests/trust_anchor_format.rs
+++ b/tests/trust_anchor_format.rs
@@ -1,4 +1,7 @@
-use der::{Decode, Encode, SliceReader};
+use der::{
+ asn1::{Ia5StringRef, PrintableStringRef},
+ Decode, Encode, SliceReader,
+};
use hex_literal::hex;
use x509_cert::anchor::{CertPolicies, TrustAnchorChoice};
use x509_cert::ext::pkix::name::GeneralName;
@@ -12,7 +15,7 @@ fn decode_ta1() {
let mut decoder = SliceReader::new(der_encoded_tac).unwrap();
let tac = TrustAnchorChoice::decode(&mut decoder).unwrap();
- let reencoded_tac = tac.to_vec().unwrap();
+ let reencoded_tac = tac.to_der().unwrap();
println!("Original : {:02X?}", der_encoded_cert);
println!("Reencoded: {:02X?}", reencoded_tac);
assert_eq!(der_encoded_tac, reencoded_tac.as_slice());
@@ -90,20 +93,34 @@ fn decode_ta1() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"U.S. Government"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "ECA");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "ECA"
+ );
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"ECA Root CA 4"
);
}
@@ -111,7 +128,7 @@ fn decode_ta1() {
}
}
- let reencoded_cert = cert_path.certificate.to_vec().unwrap();
+ let reencoded_cert = cert_path.certificate.to_der().unwrap();
assert_eq!(der_encoded_cert, reencoded_cert.as_slice());
}
_ => panic!("Unexpected TrustAnchorChoice contents"),
@@ -127,7 +144,7 @@ fn decode_ta2() {
let mut decoder = SliceReader::new(der_encoded_tac).unwrap();
let tac = TrustAnchorChoice::decode(&mut decoder).unwrap();
- let reencoded_tac = tac.to_vec().unwrap();
+ let reencoded_tac = tac.to_der().unwrap();
println!("Original : {:02X?}", der_encoded_cert);
println!("Reencoded: {:02X?}", reencoded_tac);
assert_eq!(der_encoded_tac, reencoded_tac.as_slice());
@@ -153,23 +170,34 @@ fn decode_ta2() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Entrust"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Certification Authorities"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Entrust Managed Services NFI Root CA"
);
}
@@ -190,19 +218,25 @@ fn decode_ta2() {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"US"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"U.S. Government"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"DoD"
);
}
@@ -214,7 +248,7 @@ fn decode_ta2() {
}
}
- let reencoded_cert = cert_path.certificate.to_vec().unwrap();
+ let reencoded_cert = cert_path.certificate.to_der().unwrap();
assert_eq!(der_encoded_cert, reencoded_cert.as_slice());
}
_ => panic!("Unexpected TrustAnchorChoice contents"),
@@ -230,7 +264,7 @@ fn decode_ta3() {
let mut decoder = SliceReader::new(der_encoded_tac).unwrap();
let tac = TrustAnchorChoice::decode(&mut decoder).unwrap();
- let reencoded_tac = tac.to_vec().unwrap();
+ let reencoded_tac = tac.to_der().unwrap();
println!("Original : {:02X?}", der_encoded_cert);
println!("Reencoded: {:02X?}", reencoded_tac);
assert_eq!(der_encoded_tac, reencoded_tac.as_slice());
@@ -263,23 +297,34 @@ fn decode_ta3() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "US");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "US"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Exostar LLC"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Certification Authorities"
);
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.3");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"Exostar Federated Identity Service Root CA 1"
);
}
@@ -300,19 +345,25 @@ fn decode_ta3() {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.6");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"US"
);
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"U.S. Government"
);
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"DoD"
);
}
@@ -324,7 +375,7 @@ fn decode_ta3() {
}
}
- let reencoded_cert = cert_path.certificate.to_vec().unwrap();
+ let reencoded_cert = cert_path.certificate.to_der().unwrap();
assert_eq!(der_encoded_cert, reencoded_cert.as_slice());
}
_ => panic!("Unexpected TrustAnchorChoice contents"),
@@ -340,7 +391,7 @@ fn decode_ta4() {
let mut decoder = SliceReader::new(der_encoded_tac).unwrap();
let tac = TrustAnchorChoice::decode(&mut decoder).unwrap();
- let reencoded_tac = tac.to_vec().unwrap();
+ let reencoded_tac = tac.to_der().unwrap();
println!("Original : {:02X?}", der_encoded_cert);
println!("Reencoded: {:02X?}", reencoded_tac);
assert_eq!(der_encoded_tac, reencoded_tac.as_slice());
@@ -366,17 +417,30 @@ fn decode_ta4() {
for atav in i1 {
if 0 == counter {
assert_eq!(atav.oid.to_string(), "0.9.2342.19200300.100.1.25");
- assert_eq!(atav.value.ia5_string().unwrap().to_string(), "com");
+ assert_eq!(
+ Ia5StringRef::try_from(&atav.value).unwrap().to_string(),
+ "com"
+ );
} else if 1 == counter {
assert_eq!(atav.oid.to_string(), "0.9.2342.19200300.100.1.25");
- assert_eq!(atav.value.ia5_string().unwrap().to_string(), "raytheon");
+ assert_eq!(
+ Ia5StringRef::try_from(&atav.value).unwrap().to_string(),
+ "raytheon"
+ );
} else if 2 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.10");
- assert_eq!(atav.value.printable_string().unwrap().to_string(), "CAs");
+ assert_eq!(
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
+ "CAs"
+ );
} else if 3 == counter {
assert_eq!(atav.oid.to_string(), "2.5.4.11");
assert_eq!(
- atav.value.printable_string().unwrap().to_string(),
+ PrintableStringRef::try_from(&atav.value)
+ .unwrap()
+ .to_string(),
"RaytheonRoot"
);
}
@@ -389,7 +453,7 @@ fn decode_ta4() {
panic!("Wrong path length constraint");
}
- let reencoded_cert = cert_path.certificate.to_vec().unwrap();
+ let reencoded_cert = cert_path.certificate.to_der().unwrap();
assert_eq!(der_encoded_cert, reencoded_cert.as_slice());
}
_ => panic!("Unexpected TrustAnchorChoice contents"),
diff --git a/tests/validity.rs b/tests/validity.rs
index d243773..b2e676c 100644
--- a/tests/validity.rs
+++ b/tests/validity.rs
@@ -1,6 +1,6 @@
//! Validity tests
-use der::Encode;
+use der::{Decode, Encode};
use hex_literal::hex;
use x509_cert::time::Validity;
@@ -11,7 +11,7 @@ fn decode_validity() {
// 104 13: UTCTime 01/01/2010 08:30:00 GMT
// 119 13: UTCTime 31/12/2030 08:30:00 GMT
// : }
- let val1 = Validity::try_from(
+ let val1 = Validity::from_der(
&hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..],
)
.unwrap();
@@ -21,7 +21,7 @@ fn decode_validity() {
// 99 13: UTCTime 01/01/2010 08:30:00 GMT
// 114 13: UTCTime 01/01/2011 08:30:00 GMT
// : }
- let val2 = Validity::try_from(
+ let val2 = Validity::from_der(
&hex!("301E170D3130303130313038333030305A170D3131303130313038333030305A")[..],
)
.unwrap();
@@ -51,7 +51,7 @@ fn decode_validity() {
// 99 13: UTCTime 01/01/2010 08:30:00 GMT
// 114 15: GeneralizedTime 01/01/2050 12:01:00 GMT
// : }
- let val3 = Validity::try_from(
+ let val3 = Validity::from_der(
&hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..],
)
.unwrap();
@@ -71,7 +71,7 @@ fn decode_validity() {
// 99 15: GeneralizedTime 01/01/2002 12:01:00 GMT
// 116 13: UTCTime 31/12/2030 08:30:00 GMT
// : }
- let val4 = Validity::try_from(
+ let val4 = Validity::from_der(
&hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..],
)
.unwrap();
@@ -94,11 +94,11 @@ fn encode_validity() {
// 104 13: UTCTime 01/01/2010 08:30:00 GMT
// 119 13: UTCTime 31/12/2030 08:30:00 GMT
// : }
- let val1 = Validity::try_from(
+ let val1 = Validity::from_der(
&hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..],
)
.unwrap();
- let b1 = val1.to_vec().unwrap();
+ let b1 = val1.to_der().unwrap();
assert_eq!(
b1,
&hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..]
@@ -109,11 +109,11 @@ fn encode_validity() {
// 99 13: UTCTime 01/01/2010 08:30:00 GMT
// 114 15: GeneralizedTime 01/01/2050 12:01:00 GMT
// : }
- let val3 = Validity::try_from(
+ let val3 = Validity::from_der(
&hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..],
)
.unwrap();
- let b3 = val3.to_vec().unwrap();
+ let b3 = val3.to_der().unwrap();
assert_eq!(
b3,
&hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..]
@@ -124,11 +124,11 @@ fn encode_validity() {
// 99 15: GeneralizedTime 01/01/2002 12:01:00 GMT
// 116 13: UTCTime 31/12/2030 08:30:00 GMT
// : }
- let val4 = Validity::try_from(
+ let val4 = Validity::from_der(
&hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..],
)
.unwrap();
- let b4 = val4.to_vec().unwrap();
+ let b4 = val4.to_der().unwrap();
assert_eq!(
b4,
&hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..]