diff options
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 @@ -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]) @@ -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 @@ -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" } } @@ -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) + } +} @@ -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>, } @@ -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]. /// @@ -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 Binary files differnew file mode 100644 index 0000000..f1317bf --- /dev/null +++ b/tests/examples/28903a635b5280fae6774c0b6da7d6baa64af2e8.der 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 Binary files differnew file mode 100644 index 0000000..c0de45e --- /dev/null +++ b/tests/examples/p256-priv.der diff --git a/tests/examples/p256-pub.der b/tests/examples/p256-pub.der Binary files differnew file mode 100644 index 0000000..67c719c --- /dev/null +++ b/tests/examples/p256-pub.der 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 Binary files differnew file mode 100644 index 0000000..bbf1876 --- /dev/null +++ b/tests/examples/rsa2048-priv.der diff --git a/tests/examples/rsa2048-pub.der b/tests/examples/rsa2048-pub.der Binary files differnew file mode 100644 index 0000000..4148aaa --- /dev/null +++ b/tests/examples/rsa2048-pub.der 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")[..] |