diff options
author | Hasini Gunasinghe <hasinitg@google.com> | 2022-10-05 19:53:13 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-10-05 19:53:13 +0000 |
commit | 9fe68a5c615550bf7b2a3a50268cfd3632e1eff0 (patch) | |
tree | a3d3d1d923c1bd6c7e1c6c9aa05adb6e20105ecf | |
parent | eb51940f214233e36ff403999045b84e56c50f16 (diff) | |
parent | 144d1492237be1c5428418c6537db7d32beb1e36 (diff) | |
download | x509-cert-9fe68a5c615550bf7b2a3a50268cfd3632e1eff0.tar.gz |
Import platform/external/rust/crates/x509-cert am: 1c2edd9381 am: 8bbb2e6a1f am: 144d149223
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/x509-cert/+/2240715
Change-Id: Id53aaf1b035c634d93d4a0a90db233e030664b24
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
82 files changed, 5357 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..10ff1a0 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "e89ac245b40bb044b9d9babdae6ea4ffadb59af2" + }, + "path_in_vcs": "x509-cert" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..835368d --- /dev/null +++ b/Android.bp @@ -0,0 +1,25 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +rust_library_host { + name: "libx509_cert", + crate_name: "x509_cert", + cargo_env_compat: true, + cargo_pkg_version: "0.1.0", + srcs: ["src/lib.rs"], + edition: "2021", + features: ["alloc"], + rustlibs: [ + "libconst_oid", + "libder", + "libflagset", + "libspki", + ], + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], + vendor_available: true, +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..db80bf4 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog +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.1.0 (2022-07-23) +- Initial release diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d563ecd --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,74 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.56" +name = "x509-cert" +version = "0.1.0" +authors = ["RustCrypto Developers"] +description = """ +Pure Rust implementation of the X.509 Public Key Infrastructure Certificate +format as described in RFC 5280 +""" +readme = "README.md" +keywords = ["crypto"] +categories = [ + "cryptography", + "data-structures", + "encoding", + "no-std", +] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/formats/tree/master/x509" +resolver = "2" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = [ + "--cfg", + "docsrs", +] + +[dependencies.const-oid] +version = "0.9" +features = ["db"] + +[dependencies.der] +version = "0.6" +features = [ + "derive", + "alloc", + "flagset", +] + +[dependencies.flagset] +version = "0.4.3" + +[dependencies.spki] +version = "0.6" + +[dev-dependencies.hex-literal] +version = "0.3" + +[dev-dependencies.rstest] +version = "0.12.0" + +[features] +alloc = ["der/alloc"] +pem = [ + "alloc", + "der/pem", +] +std = [ + "der/std", + "spki/std", +] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..c2b650f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,34 @@ +[package] +name = "x509-cert" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +description = """ +Pure Rust implementation of the X.509 Public Key Infrastructure Certificate +format as described in RFC 5280 +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/formats/tree/master/x509" +categories = ["cryptography", "data-structures", "encoding", "no-std"] +keywords = ["crypto"] +readme = "README.md" +edition = "2021" +rust-version = "1.56" + +[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" } + +[dev-dependencies] +hex-literal = "0.3" +rstest = "0.12.0" + +[features] +alloc = ["der/alloc"] +std = ["der/std", "spki/std"] +pem = ["alloc", "der/pem"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] @@ -0,0 +1 @@ +LICENSE-APACHE
\ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..78173fa --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..5bf432c --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +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.0.crate" + } + version: "0.1.0" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2022 + month: 9 + day: 6 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1 @@ +include platform/prebuilts/rust:master:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..e439c07 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# [RustCrypto]: X.509 Certificates + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Pure Rust implementation of the X.509 Public Key Infrastructure Certificate +format as described in [RFC 5280]. + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +This crate requires **Rust 1.57** at a minimum. + +We may change the MSRV in the future, but it will be accompanied by a minor +version bump. + +## License + +Licensed under either of: + +- [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +- [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://buildstats.info/crate/x509-cert +[crate-link]: https://crates.io/crates/x509-cert +[docs-image]: https://docs.rs/x509-cert/badge.svg +[docs-link]: https://docs.rs/x509-cert/ +[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 +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats + +[//]: # (links) + +[RustCrypto]: https://github.com/rustcrypto +[RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 diff --git a/cargo2android.json b/cargo2android.json new file mode 100644 index 0000000..e26e552 --- /dev/null +++ b/cargo2android.json @@ -0,0 +1,9 @@ +{ + "apex-available": [ + "//apex_available:platform", + "com.android.virt" + ], + "run": true, + "vendor-available": true, + "features": "alloc" +} diff --git a/patches/std.diff b/patches/std.diff new file mode 100644 index 0000000..6933bc7 --- /dev/null +++ b/patches/std.diff @@ -0,0 +1,15 @@ +diff --git a/src/lib.rs b/src/lib.rs +index 49d888c..651bdc7 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -13,6 +13,10 @@ + unused_qualifications + )] + ++/// Local Android change: Use std to allow building as a dylib. ++#[cfg(android_dylib)] ++extern crate std; ++ + extern crate alloc; + + #[cfg(feature = "std")] diff --git a/src/anchor.rs b/src/anchor.rs new file mode 100644 index 0000000..ad7ef08 --- /dev/null +++ b/src/anchor.rs @@ -0,0 +1,139 @@ +//! Trust anchor-related structures as defined in RFC 5914 + +use crate::ext::pkix::{certpolicy::CertificatePolicies, NameConstraints}; +use crate::{ext::Extensions, name::Name}; +use crate::{Certificate, TbsCertificate}; + +use der::asn1::{OctetStringRef, Utf8StringRef}; +use der::{Choice, Enumerated, Sequence}; +use flagset::{flags, FlagSet}; +use spki::SubjectPublicKeyInfo; + +/// Version identifier for TrustAnchorInfo +#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)] +#[asn1(type = "INTEGER")] +#[repr(u8)] +pub enum Version { + /// Version 1 (default) + V1 = 0, +} + +impl Default for Version { + fn default() -> Self { + Version::V1 + } +} + +/// ```text +/// TrustAnchorInfo ::= SEQUENCE { +/// version TrustAnchorInfoVersion DEFAULT v1, +/// pubKey SubjectPublicKeyInfo, +/// keyId KeyIdentifier, +/// taTitle TrustAnchorTitle OPTIONAL, +/// certPath CertPathControls OPTIONAL, +/// exts [1] EXPLICIT Extensions OPTIONAL, +/// taTitleLangTag [2] UTF8String OPTIONAL +/// } +/// +/// TrustAnchorInfoVersion ::= INTEGER { v1(1) } +/// +/// TrustAnchorTitle ::= UTF8String (SIZE (1..64)) +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Sequence)] +#[allow(missing_docs)] +pub struct TrustAnchorInfo<'a> { + #[asn1(default = "Default::default")] + pub version: Version, + + pub pub_key: SubjectPublicKeyInfo<'a>, + + pub key_id: OctetStringRef<'a>, + + #[asn1(optional = "true")] + pub ta_title: Option<Utf8StringRef<'a>>, + + #[asn1(optional = "true")] + pub cert_path: Option<CertPathControls<'a>>, + + #[asn1(context_specific = "1", tag_mode = "EXPLICIT", optional = "true")] + pub extensions: Option<Extensions<'a>>, + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] + pub ta_title_lang_tag: Option<Utf8StringRef<'a>>, +} + +/// ```text +/// CertPathControls ::= SEQUENCE { +/// taName Name, +/// certificate [0] Certificate OPTIONAL, +/// policySet [1] CertificatePolicies OPTIONAL, +/// policyFlags [2] CertPolicyFlags OPTIONAL, +/// nameConstr [3] NameConstraints OPTIONAL, +/// pathLenConstraint [4] INTEGER (0..MAX) OPTIONAL +/// } +/// ``` +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct CertPathControls<'a> { + pub ta_name: Name<'a>, + + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")] + pub certificate: Option<Certificate<'a>>, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub policy_set: Option<CertificatePolicies<'a>>, + + #[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>>, + + #[asn1(context_specific = "4", tag_mode = "IMPLICIT", optional = "true")] + pub path_len_constraint: Option<u32>, +} + +flags! { + /// Certificate policies as defined in [RFC 5280 Section 4.2.1.13]. + /// + /// ```text + /// CertPolicyFlags ::= BIT STRING { + /// inhibitPolicyMapping (0), + /// requireExplicitPolicy (1), + /// inhibitAnyPolicy (2) + /// } + /// ``` + /// + /// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 + #[allow(missing_docs)] + pub enum CertPolicies: u8 { + InhibitPolicyMapping = 1 << 0, + RequireExplicitPolicy = 1 << 1, + InhibitAnyPolicy = 1 << 2, + } +} + +/// Certificate policy flags as defined in [RFC 5280 Section 4.2.1.13]. +/// +/// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 +pub type CertPolicyFlags = FlagSet<CertPolicies>; + +/// ```text +/// TrustAnchorChoice ::= CHOICE { +/// certificate Certificate, +/// tbsCert [1] EXPLICIT TBSCertificate, +/// taInfo [2] EXPLICIT TrustAnchorInfo +/// } +/// ``` +#[derive(Clone, Debug, PartialEq, Eq, Choice)] +#[allow(clippy::large_enum_variant)] +#[allow(missing_docs)] +pub enum TrustAnchorChoice<'a> { + Certificate(Certificate<'a>), + + #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] + TbsCertificate(TbsCertificate<'a>), + + #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] + TaInfo(TrustAnchorInfo<'a>), +} diff --git a/src/attr.rs b/src/attr.rs new file mode 100644 index 0000000..9a0d2d2 --- /dev/null +++ b/src/attr.rs @@ -0,0 +1,250 @@ +//! Attribute-related definitions as defined in X.501 (and updated by RFC 5280). + +use alloc::vec::Vec; +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}; + +/// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1]. +/// +/// ```text +/// AttributeType ::= OBJECT IDENTIFIER +/// ``` +/// +/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 +pub type AttributeType = ObjectIdentifier; + +/// X.501 `AttributeValue` as defined in [RFC 5280 Appendix A.1]. +/// +/// ```text +/// AttributeValue ::= ANY +/// ``` +/// +/// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 +pub type AttributeValue<'a> = AnyRef<'a>; + +/// X.501 `Attribute` as defined in [RFC 5280 Appendix A.1]. +/// +/// ```text +/// Attribute ::= SEQUENCE { +/// type AttributeType, +/// values SET OF AttributeValue -- at least one value is required +/// } +/// ``` +/// +/// Note that [RFC 2986 Section 4] defines a constrained version of this type: +/// +/// ```text +/// Attribute { ATTRIBUTE:IOSet } ::= SEQUENCE { +/// type ATTRIBUTE.&id({IOSet}), +/// values SET SIZE(1..MAX) OF ATTRIBUTE.&Type({IOSet}{@type}) +/// } +/// ``` +/// +/// The unconstrained version should be preferred. +/// +/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 +/// [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 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) + } +} + +/// X.501 `Attributes` as defined in [RFC 2986 Section 4]. +/// +/// ```text +/// Attributes { ATTRIBUTE:IOSet } ::= SET OF Attribute{{ IOSet }} +/// ``` +/// +/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 +pub type Attributes<'a> = SetOfVec<Attribute<'a>>; + +/// X.501 `AttributeTypeAndValue` as defined in [RFC 5280 Appendix A.1]. +/// +/// ```text +/// AttributeTypeAndValue ::= SEQUENCE { +/// type AttributeType, +/// value AttributeValue +/// } +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub struct AttributeTypeAndValue<'a> { + pub oid: AttributeType, + pub value: AnyRef<'a>, +} + +#[derive(Copy, Clone)] +enum Escape { + None, + Some, + Hex(u8), +} + +struct Parser { + state: Escape, + bytes: Vec<u8>, +} + +impl Parser { + pub fn new() -> Self { + Self { + state: Escape::None, + bytes: Vec::new(), + } + } + + fn push(&mut self, c: u8) { + self.state = Escape::None; + self.bytes.push(c); + } + + pub fn add(&mut self, c: u8) -> Result<(), Error> { + match (self.state, c) { + (Escape::Hex(p), b'0'..=b'9') => self.push(p | (c - b'0')), + (Escape::Hex(p), b'a'..=b'f') => self.push(p | (c - b'a' + 10)), + (Escape::Hex(p), b'A'..=b'F') => self.push(p | (c - b'A' + 10)), + + (Escape::Some, b'0'..=b'9') => self.state = Escape::Hex((c - b'0') << 4), + (Escape::Some, b'a'..=b'f') => self.state = Escape::Hex((c - b'a' + 10) << 4), + (Escape::Some, b'A'..=b'F') => self.state = Escape::Hex((c - b'A' + 10) << 4), + + (Escape::Some, b' ' | b'"' | b'#' | b'=' | b'\\') => self.push(c), + (Escape::Some, b'+' | b',' | b';' | b'<' | b'>') => self.push(c), + + (Escape::None, b'\\') => self.state = Escape::Some, + (Escape::None, ..) => self.push(c), + + _ => return Err(ErrorKind::Failed.into()), + } + + Ok(()) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } +} + +impl AttributeTypeAndValue<'_> { + /// Parses the hex value in the `OID=#HEX` format. + fn encode_hex(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> { + // Ensure an even number of hex bytes. + let mut iter = match val.len() % 2 { + 0 => [].iter().cloned().chain(val.bytes()), + 1 => [0u8].iter().cloned().chain(val.bytes()), + _ => unreachable!(), + }; + + // 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; + + for (half, shift) in [(h, 4), (l, 0)] { + match half { + b'0'..=b'9' => byte |= (half - b'0') << shift, + b'a'..=b'f' => byte |= (half - b'a' + 10) << shift, + b'A'..=b'F' => byte |= (half - b'A' + 10) << shift, + _ => return Err(ErrorKind::Failed.into()), + } + } + + bytes.push(byte); + } + + // Serialize. + let value = AnyRef::from_der(&bytes)?; + let atv = AttributeTypeAndValue { oid, value }; + atv.to_vec() + } + + /// Parses the string value in the `NAME=STRING` format. + fn encode_str(oid: ObjectIdentifier, val: &str) -> Result<Vec<u8>, Error> { + // Undo escaping. + let mut parser = Parser::new(); + for c in val.bytes() { + parser.add(c)?; + } + + // Serialize. + let value = AnyRef::new(Tag::Utf8String, parser.as_bytes())?; + let atv = AttributeTypeAndValue { oid, value }; + atv.to_vec() + } + + /// Converts an AttributeTypeAndValue string into an encoded AttributeTypeAndValue + /// + /// This function follows the rules in [RFC 4514]. + /// + /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 + pub fn encode_from_string(s: &str) -> Result<Vec<u8>, Error> { + let idx = s.find('=').ok_or_else(|| Error::from(ErrorKind::Failed))?; + let (key, val) = s.split_at(idx); + let val = &val[1..]; + + // Either decode or lookup the OID for the given key. + let oid = match DB.by_name(key) { + Some(oid) => *oid, + None => ObjectIdentifier::new(key)?, + }; + + // If the value is hex-encoded DER... + match val.strip_prefix('#') { + Some(val) => Self::encode_hex(oid, val), + None => Self::encode_str(oid, val), + } + } +} + +/// Serializes the structure according to the rules in [RFC 4514]. +/// +/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 +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()), + _ => None, + }; + + if let (Some(key), Some(val)) = (DB.by_oid(&self.oid), val) { + write!(f, "{}=", key.to_ascii_uppercase())?; + + let mut iter = val.char_indices().peekable(); + while let Some((i, c)) = iter.next() { + match c { + '#' if i == 0 => write!(f, "\\#")?, + ' ' if i == 0 || iter.peek().is_none() => write!(f, "\\ ")?, + '"' | '+' | ',' | ';' | '<' | '>' | '\\' => write!(f, "\\{}", c)?, + '\x00'..='\x1f' | '\x7f' => write!(f, "\\{:02x}", c as u8)?, + _ => f.write_char(c)?, + } + } + } else { + let value = self.value.to_vec().or(Err(fmt::Error))?; + + write!(f, "{}=#", self.oid)?; + for c in value { + write!(f, "{:02x}", c)?; + } + } + + Ok(()) + } +} diff --git a/src/certificate.rs b/src/certificate.rs new file mode 100644 index 0000000..cca7f83 --- /dev/null +++ b/src/certificate.rs @@ -0,0 +1,157 @@ +//! Certificate types + +use crate::{name::Name, time::Validity}; + +use alloc::vec::Vec; + +use const_oid::AssociatedOid; +use der::asn1::{BitStringRef, UIntRef}; +use der::{Decode, Enumerated, Error, ErrorKind, Sequence}; +use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; + +/// Certificate `Version` as defined in [RFC 5280 Section 4.1]. +/// +/// ```text +/// Version ::= INTEGER { v1(0), v2(1), v3(2) } +/// ``` +/// +/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)] +#[asn1(type = "INTEGER")] +#[repr(u8)] +pub enum Version { + /// Version 1 (default) + V1 = 0, + + /// Version 2 + V2 = 1, + + /// Version 3 + V3 = 2, +} + +impl Default for Version { + fn default() -> Self { + Self::V1 + } +} + +/// 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 +/// information. +/// +/// ```text +/// TBSCertificate ::= SEQUENCE { +/// version [0] EXPLICIT Version DEFAULT v1, +/// serialNumber CertificateSerialNumber, +/// signature AlgorithmIdentifier, +/// issuer Name, +/// validity Validity, +/// subject Name, +/// subjectPublicKeyInfo SubjectPublicKeyInfo, +/// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, +/// -- If present, version MUST be v2 or v3 +/// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, +/// -- If present, version MUST be v2 or v3 +/// extensions [3] Extensions OPTIONAL +/// -- If present, version MUST be v3 -- +/// } +/// ``` +/// +/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct TbsCertificate<'a> { + /// The certificate version + /// + /// Note that this value defaults to Version 1 per the RFC. However, + /// fields such as `issuer_unique_id`, `subject_unique_id` and `extensions` + /// require later versions. Care should be taken in order to ensure + /// standards compliance. + #[asn1(context_specific = "0", default = "Default::default")] + pub version: Version, + + pub serial_number: UIntRef<'a>, + pub signature: AlgorithmIdentifier<'a>, + pub issuer: Name<'a>, + pub validity: Validity, + pub subject: Name<'a>, + pub subject_public_key_info: SubjectPublicKeyInfo<'a>, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub issuer_unique_id: Option<BitStringRef<'a>>, + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] + pub subject_unique_id: Option<BitStringRef<'a>>, + + #[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")] + pub extensions: Option<crate::ext::Extensions<'a>>, +} + +impl<'a> TbsCertificate<'a> { + /// 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> { + let mut iter = self.filter::<T>().peekable(); + match iter.next() { + None => Ok(None), + Some(item) => match iter.peek() { + Some(..) => Err(ErrorKind::Failed.into()), + None => Ok(Some(item?)), + }, + } + } + + /// 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>> { + 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)?))) + } +} + +/// X.509 certificates are defined in [RFC 5280 Section 4.1]. +/// +/// ```text +/// Certificate ::= SEQUENCE { +/// tbsCertificate TBSCertificate, +/// signatureAlgorithm AlgorithmIdentifier, +/// signature BIT STRING +/// } +/// ``` +/// +/// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct Certificate<'a> { + pub tbs_certificate: TbsCertificate<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature: BitStringRef<'a>, +} + +/// `PkiPath` as defined by X.509 and referenced by [RFC 6066]. +/// +/// This contains a series of certificates in validation order from the +/// top-most certificate to the bottom-most certificate. This means that +/// the first certificate signs the second certificate and so on. +/// +/// ```text +/// PkiPath ::= SEQUENCE OF Certificate +/// ``` +/// +/// [RFC 6066]: https://datatracker.ietf.org/doc/html/rfc6066#section-10.1 +pub type PkiPath<'a> = Vec<Certificate<'a>>; diff --git a/src/crl.rs b/src/crl.rs new file mode 100644 index 0000000..3e63043 --- /dev/null +++ b/src/crl.rs @@ -0,0 +1,86 @@ +//! Certificate Revocation List types + +use crate::ext::Extensions; +use crate::name::Name; +use crate::time::Time; +use crate::Version; + +use alloc::vec::Vec; + +use der::asn1::{BitStringRef, UIntRef}; +use der::Sequence; +use spki::AlgorithmIdentifier; + +/// `CertificateList` as defined in [RFC 5280 Section 5.1]. +/// +///```text +/// CertificateList ::= SEQUENCE { +/// tbsCertList TBSCertList, +/// signatureAlgorithm AlgorithmIdentifier, +/// signatureValue BIT STRING +/// } +/// ``` +/// +/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct CertificateList<'a> { + pub tbs_cert_list: TbsCertList<'a>, + pub signature_algorithm: AlgorithmIdentifier<'a>, + pub signature: BitStringRef<'a>, +} + +/// Implicit intermediate structure from the ASN.1 definition of `TBSCertList`. +/// +/// This type is used for the `revoked_certificates` field of `TbsCertList`. +/// See [RFC 5280 Section 5.1]. +/// +///```text +/// RevokedCert ::= SEQUENCE { +/// userCertificate CertificateSerialNumber, +/// revocationDate Time, +/// crlEntryExtensions Extensions OPTIONAL +/// } +/// ``` +/// +/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct RevokedCert<'a> { + pub serial_number: UIntRef<'a>, + pub revocation_date: Time, + pub crl_entry_extensions: Option<Extensions<'a>>, +} + +/// `TbsCertList` as defined in [RFC 5280 Section 5.1]. +/// +/// ```text +/// TBSCertList ::= SEQUENCE { +/// version Version OPTIONAL, -- if present, MUST be v2 +/// signature AlgorithmIdentifier, +/// issuer Name, +/// thisUpdate Time, +/// nextUpdate Time OPTIONAL, +/// revokedCertificates SEQUENCE OF SEQUENCE { +/// userCertificate CertificateSerialNumber, +/// revocationDate Time, +/// crlEntryExtensions Extensions OPTIONAL -- if present, version MUST be v2 +/// } OPTIONAL, +/// crlExtensions [0] EXPLICIT Extensions OPTIONAL -- if present, version MUST be v2 +/// } +/// ``` +/// +/// [RFC 5280 Section 5.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.1 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct TbsCertList<'a> { + pub version: Version, + pub signature: AlgorithmIdentifier<'a>, + pub issuer: Name<'a>, + pub this_update: Time, + pub next_update: Option<Time>, + pub revoked_certificates: Option<Vec<RevokedCert<'a>>>, + + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub crl_extensions: Option<Extensions<'a>>, +} diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 0000000..d717a7c --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,44 @@ +//! Standardized X.509 Certificate Extensions + +use der::Sequence; +use spki::ObjectIdentifier; + +pub mod pkix; + +/// Extension as defined in [RFC 5280 Section 4.1.2.9]. +/// +/// The ASN.1 definition for Extension objects is below. The extnValue type +/// may be further parsed using a decoder corresponding to the extnID value. +/// +/// ```text +/// Extension ::= SEQUENCE { +/// extnID OBJECT IDENTIFIER, +/// critical BOOLEAN DEFAULT FALSE, +/// extnValue OCTET STRING +/// -- contains the DER encoding of an ASN.1 value +/// -- corresponding to the extension type identified +/// -- by extnID +/// } +/// ``` +/// +/// [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct Extension<'a> { + pub extn_id: ObjectIdentifier, + + #[asn1(default = "Default::default")] + pub critical: bool, + + #[asn1(type = "OCTET STRING")] + pub extn_value: &'a [u8], +} + +/// Extensions as defined in [RFC 5280 Section 4.1.2.9]. +/// +/// ```text +/// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension +/// ``` +/// +/// [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>>; diff --git a/src/ext/pkix.rs b/src/ext/pkix.rs new file mode 100644 index 0000000..dec0659 --- /dev/null +++ b/src/ext/pkix.rs @@ -0,0 +1,117 @@ +//! PKIX X.509 Certificate Extensions (RFC 5280) + +pub mod certpolicy; +pub mod constraints; +pub mod crl; +pub mod name; + +mod access; +mod authkeyid; +mod keyusage; +mod policymap; + +use crate::attr::AttributeTypeAndValue; + +pub use access::{AccessDescription, AuthorityInfoAccessSyntax, SubjectInfoAccessSyntax}; +pub use authkeyid::AuthorityKeyIdentifier; +pub use certpolicy::CertificatePolicies; +use const_oid::{AssociatedOid, ObjectIdentifier}; +pub use constraints::{BasicConstraints, NameConstraints, PolicyConstraints}; +pub use crl::{ + BaseCrlNumber, CrlDistributionPoints, CrlNumber, CrlReason, FreshestCrl, + IssuingDistributionPoint, +}; +pub use keyusage::{ExtendedKeyUsage, KeyUsage, KeyUsages, PrivateKeyUsagePeriod}; +pub use policymap::{PolicyMapping, PolicyMappings}; + +pub use const_oid::db::rfc5280::{ + ID_CE_INHIBIT_ANY_POLICY, ID_CE_ISSUER_ALT_NAME, ID_CE_SUBJECT_ALT_NAME, + ID_CE_SUBJECT_DIRECTORY_ATTRIBUTES, ID_CE_SUBJECT_KEY_IDENTIFIER, +}; + +use alloc::vec::Vec; + +use der::asn1::OctetStringRef; + +/// SubjectKeyIdentifier as defined in [RFC 5280 Section 4.2.1.2]. +/// +/// ```text +/// SubjectKeyIdentifier ::= KeyIdentifier +/// ``` +/// +/// [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>); + +impl<'a> AssociatedOid for SubjectKeyIdentifier<'a> { + const OID: ObjectIdentifier = ID_CE_SUBJECT_KEY_IDENTIFIER; +} + +impl_newtype!(SubjectKeyIdentifier<'a>, OctetStringRef<'a>); + +/// SubjectAltName as defined in [RFC 5280 Section 4.2.1.6]. +/// +/// ```text +/// SubjectAltName ::= GeneralNames +/// ``` +/// +/// [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>); + +impl<'a> AssociatedOid for SubjectAltName<'a> { + const OID: ObjectIdentifier = ID_CE_SUBJECT_ALT_NAME; +} + +impl_newtype!(SubjectAltName<'a>, name::GeneralNames<'a>); + +/// IssuerAltName as defined in [RFC 5280 Section 4.2.1.7]. +/// +/// ```text +/// IssuerAltName ::= GeneralNames +/// ``` +/// +/// [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>); + +impl<'a> AssociatedOid for IssuerAltName<'a> { + const OID: ObjectIdentifier = ID_CE_ISSUER_ALT_NAME; +} + +impl_newtype!(IssuerAltName<'a>, name::GeneralNames<'a>); + +/// SubjectDirectoryAttributes as defined in [RFC 5280 Section 4.2.1.8]. +/// +/// ```text +/// SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF AttributeSet +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for SubjectDirectoryAttributes<'a> { + const OID: ObjectIdentifier = ID_CE_SUBJECT_DIRECTORY_ATTRIBUTES; +} + +impl_newtype!( + SubjectDirectoryAttributes<'a>, + Vec<AttributeTypeAndValue<'a>> +); + +/// InhibitAnyPolicy as defined in [RFC 5280 Section 4.2.1.14]. +/// +/// ```text +/// InhibitAnyPolicy ::= SkipCerts +/// ``` +/// +/// [RFC 5280 Section 4.2.1.14]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.14 +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct InhibitAnyPolicy(pub u32); + +impl AssociatedOid for InhibitAnyPolicy { + const OID: ObjectIdentifier = ID_CE_INHIBIT_ANY_POLICY; +} + +impl_newtype!(InhibitAnyPolicy, u32); diff --git a/src/ext/pkix/access.rs b/src/ext/pkix/access.rs new file mode 100644 index 0000000..5b74b6f --- /dev/null +++ b/src/ext/pkix/access.rs @@ -0,0 +1,58 @@ +use super::name::GeneralName; + +use alloc::vec::Vec; + +use const_oid::{ + db::rfc5280::{ID_PE_AUTHORITY_INFO_ACCESS, ID_PE_SUBJECT_INFO_ACCESS}, + AssociatedOid, +}; +use der::{asn1::ObjectIdentifier, Sequence}; + +/// AuthorityInfoAccessSyntax as defined in [RFC 5280 Section 4.2.2.1]. +/// +/// ```text +/// AuthorityInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF AccessDescription +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for AuthorityInfoAccessSyntax<'a> { + const OID: ObjectIdentifier = ID_PE_AUTHORITY_INFO_ACCESS; +} + +impl_newtype!(AuthorityInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>); + +/// SubjectInfoAccessSyntax as defined in [RFC 5280 Section 4.2.2.2]. +/// +/// ```text +/// SubjectInfoAccessSyntax ::= SEQUENCE SIZE (1..MAX) OF AccessDescription +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for SubjectInfoAccessSyntax<'a> { + const OID: ObjectIdentifier = ID_PE_SUBJECT_INFO_ACCESS; +} + +impl_newtype!(SubjectInfoAccessSyntax<'a>, Vec<AccessDescription<'a>>); + +/// AccessDescription as defined in [RFC 5280 Section 4.2.2.1]. +/// +/// ```text +/// AccessDescription ::= SEQUENCE { +/// accessMethod OBJECT IDENTIFIER, +/// accessLocation GeneralName +/// } +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub struct AccessDescription<'a> { + pub access_method: ObjectIdentifier, + pub access_location: GeneralName<'a>, +} diff --git a/src/ext/pkix/authkeyid.rs b/src/ext/pkix/authkeyid.rs new file mode 100644 index 0000000..e7644f5 --- /dev/null +++ b/src/ext/pkix/authkeyid.rs @@ -0,0 +1,36 @@ +use super::name::GeneralNames; + +use const_oid::db::rfc5280::ID_CE_AUTHORITY_KEY_IDENTIFIER; +use const_oid::{AssociatedOid, ObjectIdentifier}; +use der::asn1::{OctetStringRef, UIntRef}; +use der::Sequence; + +/// AuthorityKeyIdentifier as defined in [RFC 5280 Section 4.2.1.1]. +/// +/// ```text +/// AuthorityKeyIdentifier ::= SEQUENCE { +/// keyIdentifier [0] KeyIdentifier OPTIONAL, +/// authorityCertIssuer [1] GeneralNames OPTIONAL, +/// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL +/// } +/// +/// KeyIdentifier ::= OCTET STRING +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub struct AuthorityKeyIdentifier<'a> { + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")] + pub key_identifier: Option<OctetStringRef<'a>>, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub authority_cert_issuer: Option<GeneralNames<'a>>, + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] + pub authority_cert_serial_number: Option<UIntRef<'a>>, +} + +impl<'a> AssociatedOid for AuthorityKeyIdentifier<'a> { + const OID: ObjectIdentifier = ID_CE_AUTHORITY_KEY_IDENTIFIER; +} diff --git a/src/ext/pkix/certpolicy.rs b/src/ext/pkix/certpolicy.rs new file mode 100644 index 0000000..3821427 --- /dev/null +++ b/src/ext/pkix/certpolicy.rs @@ -0,0 +1,126 @@ +//! PKIX Certificate Policies extension + +use alloc::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}; + +/// CertificatePolicies as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation +/// ``` +/// +/// [RFC 5280 Section 4.2.1.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.4 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CertificatePolicies<'a>(pub Vec<PolicyInformation<'a>>); + +impl<'a> AssociatedOid for CertificatePolicies<'a> { + const OID: ObjectIdentifier = ID_CE_CERTIFICATE_POLICIES; +} + +impl_newtype!(CertificatePolicies<'a>, Vec<PolicyInformation<'a>>); + +/// PolicyInformation as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// PolicyInformation ::= SEQUENCE { +/// policyIdentifier CertPolicyId, +/// policyQualifiers SEQUENCE SIZE (1..MAX) OF PolicyQualifierInfo OPTIONAL +/// } +/// +/// CertPolicyId ::= OBJECT IDENTIFIER +/// ``` +/// +/// [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 PolicyInformation<'a> { + pub policy_identifier: ObjectIdentifier, + pub policy_qualifiers: Option<Vec<PolicyQualifierInfo<'a>>>, +} + +/// PolicyQualifierInfo as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// PolicyQualifierInfo ::= SEQUENCE { +/// policyQualifierId PolicyQualifierId, +/// qualifier ANY DEFINED BY policyQualifierId +/// } +/// ``` +/// +/// [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 PolicyQualifierInfo<'a> { + pub policy_qualifier_id: ObjectIdentifier, + pub qualifier: Option<AnyRef<'a>>, +} + +/// CpsUri as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// CPSuri ::= IA5String +/// ``` +/// +/// [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>; + +/// UserNotice as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// UserNotice ::= SEQUENCE { +/// noticeRef NoticeReference OPTIONAL, +/// explicitText DisplayText OPTIONAL +/// } +/// ``` +/// +/// [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 notice_ref: Option<GeneralizedTime>, + pub explicit_text: Option<DisplayText<'a>>, +} + +/// NoticeReference as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// NoticeReference ::= SEQUENCE { +/// organization DisplayText, +/// noticeNumbers SEQUENCE OF INTEGER } +/// ``` +/// +/// [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>>>, +} + +/// DisplayText as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ```text +/// DisplayText ::= CHOICE { +/// ia5String IA5String (SIZE (1..200)), +/// visibleString VisibleString (SIZE (1..200)), +/// bmpString BMPString (SIZE (1..200)), +/// utf8String UTF8String (SIZE (1..200)) +/// } +/// ``` +/// +/// Only the ia5String and utf8String options are currently supported. +/// +/// [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> { + #[asn1(type = "IA5String")] + Ia5String(Ia5StringRef<'a>), + + #[asn1(type = "UTF8String")] + Utf8String(Utf8StringRef<'a>), +} diff --git a/src/ext/pkix/constraints.rs b/src/ext/pkix/constraints.rs new file mode 100644 index 0000000..abe4c3f --- /dev/null +++ b/src/ext/pkix/constraints.rs @@ -0,0 +1,10 @@ +//! PKIX Constraint Extensions + +mod basic; +mod policy; + +pub mod name; + +pub use basic::BasicConstraints; +pub use name::NameConstraints; +pub use policy::PolicyConstraints; diff --git a/src/ext/pkix/constraints/basic.rs b/src/ext/pkix/constraints/basic.rs new file mode 100644 index 0000000..5972cc8 --- /dev/null +++ b/src/ext/pkix/constraints/basic.rs @@ -0,0 +1,24 @@ +use const_oid::{db::rfc5280::ID_CE_BASIC_CONSTRAINTS, AssociatedOid, ObjectIdentifier}; +use der::Sequence; + +/// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9]. +/// +/// ```text +/// BasicConstraints ::= SEQUENCE { +/// cA BOOLEAN DEFAULT FALSE, +/// pathLenConstraint INTEGER (0..MAX) OPTIONAL +/// } +/// ``` +/// +/// [RFC 5280 Section 4.2.1.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct BasicConstraints { + #[asn1(default = "Default::default")] + pub ca: bool, + pub path_len_constraint: Option<u8>, +} + +impl AssociatedOid for BasicConstraints { + const OID: ObjectIdentifier = ID_CE_BASIC_CONSTRAINTS; +} diff --git a/src/ext/pkix/constraints/name.rs b/src/ext/pkix/constraints/name.rs new file mode 100644 index 0000000..658f1e2 --- /dev/null +++ b/src/ext/pkix/constraints/name.rs @@ -0,0 +1,68 @@ +//! PKIX Name Constraint extension + +use alloc::vec::Vec; + +use const_oid::{db::rfc5280::ID_CE_NAME_CONSTRAINTS, AssociatedOid, ObjectIdentifier}; +use der::Sequence; + +use super::super::name::GeneralName; + +/// NameConstraints extension as defined in [RFC 5280 Section 4.2.1.10]. +/// +/// ```text +/// NameConstraints ::= SEQUENCE { +/// permittedSubtrees [0] GeneralSubtrees OPTIONAL, +/// excludedSubtrees [1] GeneralSubtrees OPTIONAL +/// } +/// ``` +/// +/// [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> { + #[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] + pub permitted_subtrees: Option<GeneralSubtrees<'a>>, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] + pub excluded_subtrees: Option<GeneralSubtrees<'a>>, +} + +impl<'a> AssociatedOid for NameConstraints<'a> { + const OID: ObjectIdentifier = ID_CE_NAME_CONSTRAINTS; +} + +/// GeneralSubtrees as defined in [RFC 5280 Section 4.2.1.10]. +/// +/// ```text +/// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree +/// ``` +/// +/// [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>>; + +/// GeneralSubtree as defined in [RFC 5280 Section 4.2.1.10]. +/// +/// ```text +/// GeneralSubtree ::= SEQUENCE { +/// base GeneralName, +/// minimum [0] BaseDistance DEFAULT 0, +/// maximum [1] BaseDistance OPTIONAL +/// } +/// ``` +/// +/// [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>, + + #[asn1( + context_specific = "0", + tag_mode = "IMPLICIT", + default = "Default::default" + )] + pub minimum: u32, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub maximum: Option<u32>, +} diff --git a/src/ext/pkix/constraints/policy.rs b/src/ext/pkix/constraints/policy.rs new file mode 100644 index 0000000..dcb1a8c --- /dev/null +++ b/src/ext/pkix/constraints/policy.rs @@ -0,0 +1,26 @@ +use const_oid::{db::rfc5280::ID_CE_POLICY_CONSTRAINTS, AssociatedOid, ObjectIdentifier}; +use der::Sequence; + +/// Policy constraints extension as defined in [RFC 5280 Section 4.2.1.11]. +/// +/// ```text +/// PolicyConstraints ::= SEQUENCE { +/// requireExplicitPolicy [0] SkipCerts OPTIONAL, +/// inhibitPolicyMapping [1] SkipCerts OPTIONAL +/// } +/// ``` +/// +/// [RFC 5280 Section 4.2.1.11]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.11 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct PolicyConstraints { + #[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] + pub require_explicit_policy: Option<u32>, + + #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] + pub inhibit_policy_mapping: Option<u32>, +} + +impl AssociatedOid for PolicyConstraints { + const OID: ObjectIdentifier = ID_CE_POLICY_CONSTRAINTS; +} diff --git a/src/ext/pkix/crl.rs b/src/ext/pkix/crl.rs new file mode 100644 index 0000000..d65b837 --- /dev/null +++ b/src/ext/pkix/crl.rs @@ -0,0 +1,116 @@ +//! PKIX Certificate Revocation List extensions + +pub mod dp; + +use const_oid::db::rfc5280::{ + ID_CE_CRL_DISTRIBUTION_POINTS, ID_CE_CRL_NUMBER, ID_CE_CRL_REASONS, ID_CE_DELTA_CRL_INDICATOR, + ID_CE_FRESHEST_CRL, +}; +use const_oid::{AssociatedOid, ObjectIdentifier}; +pub use dp::IssuingDistributionPoint; + +use alloc::vec::Vec; + +use der::{asn1::UIntRef, Enumerated}; + +/// CrlNumber as defined in [RFC 5280 Section 5.2.3]. +/// +/// ```text +/// CRLNumber ::= INTEGER (0..MAX) +/// ``` +/// +/// [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>); + +impl<'a> AssociatedOid for CrlNumber<'a> { + const OID: ObjectIdentifier = ID_CE_CRL_NUMBER; +} + +impl_newtype!(CrlNumber<'a>, UIntRef<'a>); + +/// BaseCRLNumber as defined in [RFC 5280 Section 5.2.4]. +/// +/// ```text +/// BaseCRLNumber ::= CRLNumber +/// ``` +/// +/// [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>); + +impl<'a> AssociatedOid for BaseCrlNumber<'a> { + const OID: ObjectIdentifier = ID_CE_DELTA_CRL_INDICATOR; +} + +impl_newtype!(BaseCrlNumber<'a>, UIntRef<'a>); + +/// CrlDistributionPoints as defined in [RFC 5280 Section 4.2.1.13]. +/// +/// ```text +/// CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for CrlDistributionPoints<'a> { + const OID: ObjectIdentifier = ID_CE_CRL_DISTRIBUTION_POINTS; +} + +impl_newtype!(CrlDistributionPoints<'a>, Vec<dp::DistributionPoint<'a>>); + +/// FreshestCrl as defined in [RFC 5280 Section 5.2.6]. +/// +/// ```text +/// FreshestCRL ::= CRLDistributionPoints +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for FreshestCrl<'a> { + const OID: ObjectIdentifier = ID_CE_FRESHEST_CRL; +} + +impl_newtype!(FreshestCrl<'a>, Vec<dp::DistributionPoint<'a>>); + +/// CRLReason as defined in [RFC 5280 Section 5.3.1]. +/// +/// ```text +/// CRLReason ::= ENUMERATED { +/// unspecified (0), +/// keyCompromise (1), +/// cACompromise (2), +/// affiliationChanged (3), +/// superseded (4), +/// cessationOfOperation (5), +/// certificateHold (6), +/// removeFromCRL (8), +/// privilegeWithdrawn (9), +/// aACompromise (10) +/// } +/// ``` +/// +/// [RFC 5280 Section 5.3.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1 +#[derive(Copy, Clone, Debug, Eq, PartialEq, Enumerated)] +#[allow(missing_docs)] +#[repr(u32)] +pub enum CrlReason { + Unspecified = 0, + KeyCompromise = 1, + CaCompromise = 2, + AffiliationChanged = 3, + Superseded = 4, + CessationOfOperation = 5, + CertificateHold = 6, + RemoveFromCRL = 8, + PrivilegeWithdrawn = 9, + AaCompromise = 10, +} + +impl AssociatedOid for CrlReason { + const OID: ObjectIdentifier = ID_CE_CRL_REASONS; +} diff --git a/src/ext/pkix/crl/dp.rs b/src/ext/pkix/crl/dp.rs new file mode 100644 index 0000000..7aa8a2d --- /dev/null +++ b/src/ext/pkix/crl/dp.rs @@ -0,0 +1,125 @@ +//! PKIX distribution point types + +use const_oid::{db::rfc5280::ID_PE_SUBJECT_INFO_ACCESS, AssociatedOid, ObjectIdentifier}; +use der::Sequence; +use flagset::{flags, FlagSet}; + +use crate::ext::pkix::name::{DistributionPointName, GeneralNames}; + +/// IssuingDistributionPoint as defined in [RFC 5280 Section 5.2.5]. +/// +/// ```text +/// IssuingDistributionPoint ::= SEQUENCE { +/// distributionPoint [0] DistributionPointName OPTIONAL, +/// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, +/// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, +/// onlySomeReasons [3] ReasonFlags OPTIONAL, +/// indirectCRL [4] BOOLEAN DEFAULT FALSE, +/// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE +/// -- at most one of onlyContainsUserCerts, onlyContainsCACerts, +/// -- and onlyContainsAttributeCerts may be set to TRUE. +/// } +/// ``` +/// +/// [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> { + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub distribution_point: Option<DistributionPointName<'a>>, + + #[asn1( + context_specific = "1", + tag_mode = "IMPLICIT", + default = "Default::default" + )] + pub only_contains_user_certs: bool, + + #[asn1( + context_specific = "2", + tag_mode = "IMPLICIT", + default = "Default::default" + )] + pub only_contains_ca_certs: bool, + + #[asn1(context_specific = "3", tag_mode = "IMPLICIT", optional = "true")] + pub only_some_reasons: Option<ReasonFlags>, + + #[asn1( + context_specific = "4", + tag_mode = "IMPLICIT", + default = "Default::default" + )] + pub indirect_crl: bool, + + #[asn1( + context_specific = "5", + tag_mode = "IMPLICIT", + default = "Default::default" + )] + pub only_contains_attribute_certs: bool, +} + +impl<'a> AssociatedOid for IssuingDistributionPoint<'a> { + const OID: ObjectIdentifier = ID_PE_SUBJECT_INFO_ACCESS; +} + +/// DistributionPoint as defined in [RFC 5280 Section 4.2.1.13]. +/// +/// ```text +/// DistributionPoint ::= SEQUENCE { +/// distributionPoint [0] DistributionPointName OPTIONAL, +/// reasons [1] ReasonFlags OPTIONAL, +/// cRLIssuer [2] GeneralNames OPTIONAL } +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub struct DistributionPoint<'a> { + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub distribution_point: Option<DistributionPointName<'a>>, + + #[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>>, +} + +/// ReasonFlags as defined in [RFC 5280 Section 4.2.1.13]. +/// +/// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 +pub type ReasonFlags = FlagSet<Reasons>; + +flags! { + /// ReasonFlags values as defined in [RFC 5280 Section 4.2.1.13]. + /// + /// ```text + /// ReasonFlags ::= BIT STRING { + /// unused (0), + /// keyCompromise (1), + /// cACompromise (2), + /// affiliationChanged (3), + /// superseded (4), + /// cessationOfOperation (5), + /// certificateHold (6), + /// privilegeWithdrawn (7), + /// aACompromise (8) + /// } + /// ``` + /// + /// [RFC 5280 Section 4.2.1.13]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.13 + #[allow(missing_docs)] + pub enum Reasons: u16 { + Unused = 1 << 0, + KeyCompromise = 1 << 1, + CaCompromise = 1 << 2, + AffiliationChanged = 1 << 3, + Superseded = 1 << 4, + CessationOfOperation = 1 << 5, + CertificateHold = 1 << 6, + PrivilegeWithdrawn = 1 << 7, + AaCompromise = 1 << 8, + } +} diff --git a/src/ext/pkix/keyusage.rs b/src/ext/pkix/keyusage.rs new file mode 100644 index 0000000..cd051fe --- /dev/null +++ b/src/ext/pkix/keyusage.rs @@ -0,0 +1,105 @@ +use alloc::vec::Vec; + +use const_oid::db::rfc5280::{ + 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::Sequence; +use flagset::{flags, FlagSet}; + +flags! { + /// Key usage flags as defined in [RFC 5280 Section 4.2.1.3]. + /// + /// ```text + /// KeyUsage ::= BIT STRING { + /// digitalSignature (0), + /// nonRepudiation (1), -- recent editions of X.509 have + /// -- renamed this bit to contentCommitment + /// keyEncipherment (2), + /// dataEncipherment (3), + /// keyAgreement (4), + /// keyCertSign (5), + /// cRLSign (6), + /// encipherOnly (7), + /// decipherOnly (8) + /// } + /// ``` + /// + /// [RFC 5280 Section 4.2.1.3]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 + #[allow(missing_docs)] + pub enum KeyUsages: u16 { + DigitalSignature = 1 << 0, + NonRepudiation = 1 << 1, + KeyEncipherment = 1 << 2, + DataEncipherment = 1 << 3, + KeyAgreement = 1 << 4, + KeyCertSign = 1 << 5, + CRLSign = 1 << 6, + EncipherOnly = 1 << 7, + DecipherOnly = 1 << 8, + } +} + +/// KeyUsage as defined in [RFC 5280 Section 4.2.1.3]. +/// +/// [RFC 5280 Section 4.2.1.3]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.3 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct KeyUsage(pub FlagSet<KeyUsages>); + +impl AssociatedOid for KeyUsage { + const OID: ObjectIdentifier = ID_CE_KEY_USAGE; +} + +impl_newtype!(KeyUsage, FlagSet<KeyUsages>); + +/// ExtKeyUsageSyntax as defined in [RFC 5280 Section 4.2.1.12]. +/// +/// Many extended key usage values include: +/// - [`PKIX_CE_ANYEXTENDEDKEYUSAGE`](constant.PKIX_CE_ANYEXTENDEDKEYUSAGE.html), +/// - [`PKIX_KP_SERVERAUTH`](constant.PKIX_KP_SERVERAUTH.html), +/// - [`PKIX_KP_CLIENTAUTH`](constant.PKIX_KP_CLIENTAUTH.html), +/// - [`PKIX_KP_CODESIGNING`](constant.PKIX_KP_CODESIGNING.html), +/// - [`PKIX_KP_EMAILPROTECTION`](constant.PKIX_KP_EMAILPROTECTION.html), +/// - [`PKIX_KP_TIMESTAMPING`](constant.PKIX_KP_TIMESTAMPING.html), +/// +/// ```text +/// ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId +/// KeyPurposeId ::= OBJECT IDENTIFIER +/// ``` +/// +/// [RFC 5280 Section 4.2.1.12]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.12 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ExtendedKeyUsage(pub Vec<ObjectIdentifier>); + +impl AssociatedOid for ExtendedKeyUsage { + const OID: ObjectIdentifier = ID_CE_EXT_KEY_USAGE; +} + +impl_newtype!(ExtendedKeyUsage, Vec<ObjectIdentifier>); + +/// 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." +/// +/// ```text +/// PrivateKeyUsagePeriod ::= SEQUENCE { +/// notBefore [0] GeneralizedTime OPTIONAL, +/// notAfter [1] GeneralizedTime OPTIONAL } +/// -- either notBefore or notAfter MUST be present +/// ``` +/// +/// [RFC 3280 Section 4.2.1.12]: https://datatracker.ietf.org/doc/html/rfc3280#section-4.2.1.4 +#[derive(Clone, Debug, PartialEq, Eq, Sequence)] +#[allow(missing_docs)] +pub struct PrivateKeyUsagePeriod { + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")] + pub not_before: Option<GeneralizedTime>, + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] + pub not_after: Option<GeneralizedTime>, +} + +impl AssociatedOid for PrivateKeyUsagePeriod { + const OID: ObjectIdentifier = ID_CE_PRIVATE_KEY_USAGE_PERIOD; +} diff --git a/src/ext/pkix/name.rs b/src/ext/pkix/name.rs new file mode 100644 index 0000000..4e0112b --- /dev/null +++ b/src/ext/pkix/name.rs @@ -0,0 +1,13 @@ +//! PKIX Name types + +mod dirstr; +mod dp; +mod ediparty; +mod general; +mod other; + +pub use dirstr::DirectoryString; +pub use dp::DistributionPointName; +pub use ediparty::EdiPartyName; +pub use general::{GeneralName, GeneralNames}; +pub use other::OtherName; diff --git a/src/ext/pkix/name/dirstr.rs b/src/ext/pkix/name/dirstr.rs new file mode 100644 index 0000000..e2af698 --- /dev/null +++ b/src/ext/pkix/name/dirstr.rs @@ -0,0 +1,49 @@ +use der::asn1::{PrintableStringRef, Utf8StringRef}; +use der::Choice; + +/// DirectoryString as defined in [RFC 5280 Section 4.2.1.4]. +/// +/// ASN.1 structure for DirectoryString is below. +/// +/// ```text +/// DirectoryString ::= CHOICE { +/// teletexString TeletexString (SIZE (1..MAX)), +/// printableString PrintableString (SIZE (1..MAX)), +/// universalString UniversalString (SIZE (1..MAX)), +/// utf8String UTF8String (SIZE (1..MAX)), +/// bmpString BMPString (SIZE (1..MAX)) +/// } +/// ``` +/// +/// Further, [RFC 5280 Section 4.2.1.4] states: +/// +/// ```text +/// The DirectoryString type is defined as a choice of PrintableString, +/// TeletexString, BMPString, UTF8String, and UniversalString. CAs +/// conforming to this profile MUST use either the PrintableString or +/// UTF8String encoding of DirectoryString, with two exceptions. When +/// CAs have previously issued certificates with issuer fields with +/// attributes encoded using TeletexString, BMPString, or +/// UniversalString, then the CA MAY continue to use these encodings of +/// the DirectoryString to preserve backward compatibility. Also, new +/// CAs that are added to a domain where existing CAs issue certificates +/// with issuer fields with attributes encoded using TeletexString, +/// BMPString, or UniversalString MAY encode attributes that they share +/// with the existing CAs using the same encodings as the existing CAs +/// use. +/// ``` +/// +/// The implication of the above paragraph is that `PrintableString` and +/// `UTF8String` are the new types and the other types are legacy. Until +/// the need arises, we only support `PrintableString` and `UTF8String`. +/// +/// [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)] +#[allow(missing_docs)] +pub enum DirectoryString<'a> { + #[asn1(type = "PrintableString")] + PrintableString(PrintableStringRef<'a>), + + #[asn1(type = "UTF8String")] + Utf8String(Utf8StringRef<'a>), +} diff --git a/src/ext/pkix/name/dp.rs b/src/ext/pkix/name/dp.rs new file mode 100644 index 0000000..3cfdf96 --- /dev/null +++ b/src/ext/pkix/name/dp.rs @@ -0,0 +1,24 @@ +use super::GeneralNames; +use crate::name::RelativeDistinguishedName; + +use der::Choice; + +/// DistributionPointName as defined in [RFC 5280 Section 4.2.1.13]. +/// +/// ```text +/// DistributionPointName ::= CHOICE { +/// fullName [0] GeneralNames, +/// nameRelativeToCRLIssuer [1] RelativeDistinguishedName +/// } +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub enum DistributionPointName<'a> { + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", constructed = "true")] + FullName(GeneralNames<'a>), + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT", constructed = "true")] + NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>), +} diff --git a/src/ext/pkix/name/ediparty.rs b/src/ext/pkix/name/ediparty.rs new file mode 100644 index 0000000..06ee320 --- /dev/null +++ b/src/ext/pkix/name/ediparty.rs @@ -0,0 +1,36 @@ +use der::Sequence; + +use super::DirectoryString; + +/// EDIPartyName as defined in [RFC 5280 Section 4.2.1.6]. +/// +/// ```text +/// EDIPartyName ::= SEQUENCE { +/// nameAssigner [0] DirectoryString OPTIONAL, +/// partyName [1] DirectoryString +/// } +/// ``` +/// +/// Note that although the module uses `IMPLICIT` tagging, these tags are +/// `EXPLICIT` because of `X.680-2015 31.2.7 (c)`: +/// +/// ```text +/// c) the "Tag Type" alternative is used and the value of "TagDefault" for +/// the module is IMPLICIT TAGS or AUTOMATIC TAGS, but the type defined by +/// "Type" is an untagged choice type, an untagged open type, or an untagged +/// "DummyReference" (see Rec. ITU-T X.683 | ISO/IEC 8824-4, 8.3). +/// ``` +/// +/// See [this OpenSSL bug] for more details. +/// +/// [this OpenSSL bug]: https://github.com/openssl/openssl/issues/6859 +/// [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)] +#[allow(missing_docs)] +pub struct EdiPartyName<'a> { + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub name_assigner: Option<DirectoryString<'a>>, + + #[asn1(context_specific = "1", tag_mode = "EXPLICIT")] + pub party_name: DirectoryString<'a>, +} diff --git a/src/ext/pkix/name/general.rs b/src/ext/pkix/name/general.rs new file mode 100644 index 0000000..0daa368 --- /dev/null +++ b/src/ext/pkix/name/general.rs @@ -0,0 +1,63 @@ +//! GeneralNames as defined in [RFC 5280 Section 4.2.1.6]. + +use super::{EdiPartyName, OtherName}; +use crate::name::Name; + +use der::asn1::{Ia5StringRef, ObjectIdentifier, OctetStringRef}; +use der::Choice; + +/// GeneralNames as defined in [RFC 5280 Section 4.2.1.6]. +/// +/// ```text +/// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName +/// ``` +/// +/// [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>>; + +/// GeneralName as defined in [RFC 5280 Section 4.2.1.6]. +/// +/// ```text +/// GeneralName ::= CHOICE { +/// otherName [0] OtherName, +/// rfc822Name [1] IA5String, +/// dNSName [2] IA5String, +/// x400Address [3] ORAddress, +/// directoryName [4] Name, +/// ediPartyName [5] EDIPartyName, +/// uniformResourceIdentifier [6] IA5String, +/// iPAddress [7] OCTET STRING, +/// registeredID [8] OBJECT IDENTIFIER +/// } +/// ``` +/// +/// This implementation does not currently support the `x400Address` choice. +/// +/// [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)] +#[allow(missing_docs)] +pub enum GeneralName<'a> { + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", constructed = "true")] + OtherName(OtherName<'a>), + + #[asn1(context_specific = "1", tag_mode = "IMPLICIT")] + Rfc822Name(Ia5StringRef<'a>), + + #[asn1(context_specific = "2", tag_mode = "IMPLICIT")] + DnsName(Ia5StringRef<'a>), + + #[asn1(context_specific = "4", tag_mode = "EXPLICIT", constructed = "true")] + DirectoryName(Name<'a>), + + #[asn1(context_specific = "5", tag_mode = "IMPLICIT", constructed = "true")] + EdiPartyName(EdiPartyName<'a>), + + #[asn1(context_specific = "6", tag_mode = "IMPLICIT")] + UniformResourceIdentifier(Ia5StringRef<'a>), + + #[asn1(context_specific = "7", tag_mode = "IMPLICIT")] + IpAddress(OctetStringRef<'a>), + + #[asn1(context_specific = "8", tag_mode = "IMPLICIT")] + RegisteredId(ObjectIdentifier), +} diff --git a/src/ext/pkix/name/other.rs b/src/ext/pkix/name/other.rs new file mode 100644 index 0000000..4a250bb --- /dev/null +++ b/src/ext/pkix/name/other.rs @@ -0,0 +1,37 @@ +use der::{asn1::ObjectIdentifier, AnyRef, Sequence}; + +/// OtherName as defined in [RFC 5280 Section 4.2.1.6]. +/// +/// ```text +/// OtherName ::= SEQUENCE { +/// type-id OBJECT IDENTIFIER, +/// value [0] EXPLICIT ANY DEFINED BY type-id +/// } +/// ``` +/// +/// [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)] +#[allow(missing_docs)] +pub struct OtherName<'a> { + pub type_id: ObjectIdentifier, + + #[asn1(context_specific = "0", tag_mode = "EXPLICIT")] + pub value: AnyRef<'a>, +} + +#[test] +#[cfg(test)] +fn test() { + use alloc::string::ToString; + use der::{Decode, Encode}; + use hex_literal::hex; + + let input = hex!("3021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C"); + let decoded = OtherName::from_der(&input).unwrap(); + + let onval = decoded.value.utf8_string().unwrap(); + assert_eq!(onval.to_string(), "Upn_214950130@mil"); + + let encoded = decoded.to_vec().unwrap(); + assert_eq!(&input[..], &encoded); +} diff --git a/src/ext/pkix/policymap.rs b/src/ext/pkix/policymap.rs new file mode 100644 index 0000000..35b1d49 --- /dev/null +++ b/src/ext/pkix/policymap.rs @@ -0,0 +1,39 @@ +use alloc::vec::Vec; + +use const_oid::db::rfc5280::ID_CE_POLICY_MAPPINGS; +use const_oid::AssociatedOid; +use der::asn1::ObjectIdentifier; +use der::Sequence; + +/// PolicyMappings as defined in [RFC 5280 Section 4.2.1.5]. +/// +/// ```text +/// PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { +/// ``` +/// +/// [RFC 5280 Section 4.2.1.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.5 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PolicyMappings(pub Vec<PolicyMapping>); + +impl AssociatedOid for PolicyMappings { + const OID: ObjectIdentifier = ID_CE_POLICY_MAPPINGS; +} + +impl_newtype!(PolicyMappings, Vec<PolicyMapping>); + +/// PolicyMapping as defined in [RFC 5280 Section 4.2.1.5]. +/// +/// ```text +/// PolicyMapping ::= SEQUENCE { +/// issuerDomainPolicy CertPolicyId, +/// subjectDomainPolicy CertPolicyId +/// } +/// ``` +/// +/// [RFC 5280 Section 4.2.1.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.5 +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[allow(missing_docs)] +pub struct PolicyMapping { + pub issuer_domain_policy: ObjectIdentifier, + pub subject_domain_policy: ObjectIdentifier, +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..651bdc7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,38 @@ +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg" +)] +#![forbid(unsafe_code)] +#![warn( + missing_docs, + rust_2018_idioms, + unused_lifetimes, + unused_qualifications +)] + +/// Local Android change: Use std to allow building as a dylib. +#[cfg(android_dylib)] +extern crate std; + +extern crate alloc; + +#[cfg(feature = "std")] +extern crate std; + +#[macro_use] +mod macros; + +pub mod anchor; +pub mod attr; +pub mod certificate; +pub mod crl; +pub mod ext; +pub mod name; +pub mod request; +pub mod time; + +pub use certificate::{Certificate, PkiPath, TbsCertificate, Version}; +pub use der; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..3256c24 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,73 @@ +//! Macros used by this crate + +/// Implements the following traits for a newtype of a `der` decodable/encodable type: +/// +/// - `From` conversions to/from the inner type +/// - `AsRef` and `AsMut` +/// - `DecodeValue` and `EncodeValue` +/// - `FixedTag` mapping to the inner value's `FixedTag::TAG` +/// +/// The main case is simplifying newtypes which need an `AssociatedOid` +#[macro_export] +macro_rules! impl_newtype { + ($newtype:ty, $inner:ty) => { + #[allow(unused_lifetimes)] + impl<'a> From<$inner> for $newtype { + #[inline] + fn from(value: $inner) -> Self { + Self(value) + } + } + + #[allow(unused_lifetimes)] + impl<'a> From<$newtype> for $inner { + #[inline] + fn from(value: $newtype) -> Self { + value.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a> AsRef<$inner> for $newtype { + #[inline] + fn as_ref(&self) -> &$inner { + &self.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a> AsMut<$inner> for $newtype { + #[inline] + fn as_mut(&mut self) -> &mut $inner { + &mut self.0 + } + } + + #[allow(unused_lifetimes)] + impl<'a> ::der::FixedTag for $newtype { + const TAG: ::der::Tag = <$inner as ::der::FixedTag>::TAG; + } + + impl<'a> ::der::DecodeValue<'a> for $newtype { + fn decode_value<R: ::der::Reader<'a>>( + decoder: &mut R, + header: ::der::Header, + ) -> ::der::Result<Self> { + Ok(Self(<$inner as ::der::DecodeValue>::decode_value( + decoder, header, + )?)) + } + } + + #[allow(unused_lifetimes)] + impl<'a> ::der::EncodeValue for $newtype { + fn encode_value(&self, encoder: &mut dyn ::der::Writer) -> ::der::Result<()> { + self.0.encode_value(encoder) + } + + fn value_len(&self) -> ::der::Result<::der::Length> { + self.0.value_len() + } + } + }; +} diff --git a/src/name.rs b/src/name.rs new file mode 100644 index 0000000..81ee4aa --- /dev/null +++ b/src/name.rs @@ -0,0 +1,169 @@ +//! Name-related definitions as defined in X.501 (and updated by RFC 5280). + +use crate::attr::AttributeTypeAndValue; +use alloc::vec::Vec; +use core::fmt; +use der::{asn1::SetOfVec, Decode, Encode}; + +/// X.501 Name as defined in [RFC 5280 Section 4.1.2.4]. X.501 Name is used to represent distinguished names. +/// +/// ```text +/// Name ::= CHOICE { rdnSequence RDNSequence } +/// ``` +/// +/// [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>; + +/// X.501 RDNSequence as defined in [RFC 5280 Section 4.1.2.4]. +/// +/// ```text +/// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName +/// ``` +/// +/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4 +#[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 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>>()?; + + let mut out = Vec::new(); + for der in ders.iter() { + out.push(RelativeDistinguishedName::from_der(der)?); + } + + RdnSequence(out).to_vec() + } +} + +/// Serializes the structure according to the rules in [RFC 4514]. +/// +/// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 +impl fmt::Display for RdnSequence<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, atv) in self.0.iter().enumerate() { + match i { + 0 => write!(f, "{}", atv)?, + _ => write!(f, ",{}", atv)?, + } + } + + Ok(()) + } +} + +impl_newtype!(RdnSequence<'a>, Vec<RelativeDistinguishedName<'a>>); + +/// Find the indices of all non-escaped separators. +fn find(s: &str, b: u8) -> impl '_ + Iterator<Item = usize> { + (0..s.len()) + .filter(move |i| s.as_bytes()[*i] == b) + .filter(|i| { + let x = i + .checked_sub(2) + .map(|i| s.as_bytes()[i]) + .unwrap_or_default(); + + let y = i + .checked_sub(1) + .map(|i| s.as_bytes()[i]) + .unwrap_or_default(); + + y != b'\\' || x == b'\\' + }) +} + +/// Split a string at all non-escaped separators. +fn split(s: &str, b: u8) -> impl '_ + Iterator<Item = &'_ str> { + let mut prev = 0; + find(s, b).chain([s.len()].into_iter()).map(move |i| { + let x = &s[prev..i]; + prev = i + 1; + x + }) +} + +/// X.501 DistinguishedName as defined in [RFC 5280 Section 4.1.2.4]. +/// +/// ```text +/// DistinguishedName ::= RDNSequence +/// ``` +/// +/// [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>; + +/// RelativeDistinguishedName as defined in [RFC 5280 Section 4.1.2.4]. +/// +/// ```text +/// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue +/// ``` +/// +/// Note that we follow the more common definition above. This technically +/// differs from the definition in X.501, which is: +/// +/// ```text +/// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndDistinguishedValue +/// +/// AttributeTypeAndDistinguishedValue ::= SEQUENCE { +/// type ATTRIBUTE.&id ({SupportedAttributes}), +/// value ATTRIBUTE.&Type({SupportedAttributes}{@type}), +/// primaryDistinguished BOOLEAN DEFAULT TRUE, +/// valuesWithContext SET SIZE (1..MAX) OF SEQUENCE { +/// distingAttrValue [0] ATTRIBUTE.&Type ({SupportedAttributes}{@type}) OPTIONAL, +/// contextList SET SIZE (1..MAX) OF Context +/// } OPTIONAL +/// } +/// ``` +/// +/// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4 +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RelativeDistinguishedName<'a>(pub SetOfVec<AttributeTypeAndValue<'a>>); + +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 + 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>>()?; + + let atvs = ders + .iter() + .map(|der| AttributeTypeAndValue::from_der(der)) + .collect::<Result<Vec<_>, der::Error>>()?; + + RelativeDistinguishedName(atvs.try_into()?).to_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<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, atv) in self.0.iter().enumerate() { + match i { + 0 => write!(f, "{}", atv)?, + _ => write!(f, "+{}", atv)?, + } + } + + Ok(()) + } +} + +impl_newtype!( + RelativeDistinguishedName<'a>, + SetOfVec<AttributeTypeAndValue<'a>> +); diff --git a/src/request.rs b/src/request.rs new file mode 100644 index 0000000..6ab16cf --- /dev/null +++ b/src/request.rs @@ -0,0 +1,106 @@ +//! PKCS#10 Certification Request types + +use crate::ext::Extension; +use crate::{attr::Attributes, 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}; + +/// Version identifier for certification request information. +/// +/// (RFC 2986 designates `0` as the only valid version) +#[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)] +#[asn1(type = "INTEGER")] +#[repr(u8)] +pub enum Version { + /// Denotes PKCS#8 v1 + V1 = 0, +} + +/// PKCS#10 `CertificationRequestInfo` as defined in [RFC 2986 Section 4]. +/// +/// ```text +/// CertificationRequestInfo ::= SEQUENCE { +/// version INTEGER { v1(0) } (v1,...), +/// subject Name, +/// subjectPKInfo SubjectPublicKeyInfo{{ PKInfoAlgorithms }}, +/// attributes [0] Attributes{{ CRIAttributes }} +/// } +/// ``` +/// +/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 +#[derive(Clone, Debug, PartialEq, Eq, Sequence)] +pub struct CertReqInfo<'a> { + /// Certification request version. + pub version: Version, + + /// Subject name. + pub subject: Name<'a>, + + /// Subject public key info. + pub public_key: SubjectPublicKeyInfo<'a>, + + /// 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) + } +} + +/// PKCS#10 `CertificationRequest` as defined in [RFC 2986 Section 4]. +/// +/// ```text +/// CertificationRequest ::= SEQUENCE { +/// certificationRequestInfo CertificationRequestInfo, +/// signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }}, +/// signature BIT STRING +/// } +/// ``` +/// +/// [RFC 2986 Section 4]: https://datatracker.ietf.org/doc/html/rfc2986#section-4 +#[derive(Clone, Debug, PartialEq, Eq, Sequence)] +pub struct CertReq<'a> { + /// Certification request information. + pub info: CertReqInfo<'a>, + + /// Signature algorithm identifier. + pub algorithm: AlgorithmIdentifier<'a>, + + /// Signature. + pub signature: BitStringRef<'a>, +} + +impl<'a> TryFrom<&'a [u8]> for CertReq<'a> { + type Error = der::Error; + + fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> { + Self::from_der(bytes) + } +} + +/// `ExtensionReq` as defined in [RFC 5272 Section 3.1]. +/// +/// ```text +/// ExtensionReq ::= SEQUENCE SIZE (1..MAX) OF Extension +/// ``` +/// +/// [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>>); + +impl<'a> AssociatedOid for ExtensionReq<'a> { + const OID: ObjectIdentifier = ID_EXTENSION_REQ; +} + +impl_newtype!(ExtensionReq<'a>, Vec<Extension<'a>>); diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..e938c13 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,146 @@ +//! X.501 time types as defined in RFC 5280 + +use core::fmt; +use core::time::Duration; +use der::asn1::{GeneralizedTime, UtcTime}; +use der::{Choice, DateTime, Decode, Error, Result, Sequence}; + +#[cfg(feature = "std")] +use std::time::SystemTime; + +/// X.501 `Time` as defined in [RFC 5280 Section 4.1.2.5]. +/// +/// Schema definition from [RFC 5280 Appendix A]: +/// +/// ```text +/// Time ::= CHOICE { +/// utcTime UTCTime, +/// generalTime GeneralizedTime +/// } +/// ``` +/// +/// [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 +#[derive(Choice, Copy, Clone, Debug, Eq, PartialEq)] +pub enum Time { + /// Legacy UTC time (has 2-digit year, valid only through 2050). + #[asn1(type = "UTCTime")] + UtcTime(UtcTime), + + /// Modern [`GeneralizedTime`] encoding with 4-digit year. + #[asn1(type = "GeneralizedTime")] + GeneralTime(GeneralizedTime), +} + +impl Time { + /// Get duration since `UNIX_EPOCH`. + pub fn to_unix_duration(self) -> Duration { + match self { + Time::UtcTime(t) => t.to_unix_duration(), + Time::GeneralTime(t) => t.to_unix_duration(), + } + } + + /// Get Time as DateTime + pub fn to_date_time(&self) -> DateTime { + match self { + Time::UtcTime(t) => t.to_date_time(), + Time::GeneralTime(t) => t.to_date_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(), + } + } +} + +impl fmt::Display for Time { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> { + write!(f, "{}", self.to_date_time()) + } +} + +impl From<UtcTime> for Time { + fn from(time: UtcTime) -> Time { + Time::UtcTime(time) + } +} + +impl From<GeneralizedTime> for Time { + fn from(time: GeneralizedTime) -> Time { + Time::GeneralTime(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() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl From<&Time> for SystemTime { + fn from(time: &Time) -> SystemTime { + time.to_system_time() + } +} + +#[cfg(feature = "std")] +#[cfg_attr(docsrs, doc(cfg(feature = "std")))] +impl TryFrom<SystemTime> for Time { + type Error = Error; + + fn try_from(time: SystemTime) -> Result<Time> { + Ok(GeneralizedTime::try_from(time)?.into()) + } +} + +/// X.501 `Validity` as defined in [RFC 5280 Section 4.1.2.5] +/// +/// ```text +/// Validity ::= SEQUENCE { +/// notBefore Time, +/// notAfter Time +/// } +/// ``` +/// [RFC 5280 Section 4.1.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5 +#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence)] +pub struct Validity { + /// notBefore value + pub not_before: Time, + + /// notAfter value + pub not_after: Time, +} + +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> { + let now = SystemTime::now(); + let then = now + duration; + + Ok(Self { + not_before: Time::try_from(now)?, + not_after: Time::try_from(then)?, + }) + } +} + +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/certificate.rs b/tests/certificate.rs new file mode 100644 index 0000000..e952ca3 --- /dev/null +++ b/tests/certificate.rs @@ -0,0 +1,351 @@ +//! Certificate tests + +use der::{ + asn1::{BitStringRef, ContextSpecific, ObjectIdentifier, UIntRef}, + Decode, DecodeValue, Encode, FixedTag, Header, Reader, Tag, Tagged, +}; +use hex_literal::hex; +use spki::AlgorithmIdentifier; +use x509_cert::Certificate; +use x509_cert::*; + +// TODO - parse and compare extension values +const EXTENSIONS: &[(&str, bool)] = &[ + ("2.5.29.15", true), + ("2.5.29.19", true), + ("2.5.29.33", false), + ("2.5.29.32", false), + ("2.5.29.14", false), + ("2.5.29.31", false), + ("1.3.6.1.5.5.7.1.11", false), + ("1.3.6.1.5.5.7.1.1", false), + ("2.5.29.54", false), + ("2.5.29.35", false), +]; + +///Structure supporting deferred decoding of fields in the Certificate SEQUENCE +pub struct DeferDecodeCertificate<'a> { + /// tbsCertificate TBSCertificate, + pub tbs_certificate: &'a [u8], + /// signatureAlgorithm AlgorithmIdentifier, + pub signature_algorithm: &'a [u8], + /// signature BIT STRING + pub signature: &'a [u8], +} + +impl<'a> DecodeValue<'a> for DeferDecodeCertificate<'a> { + fn decode_value<R: Reader<'a>>( + reader: &mut R, + header: Header, + ) -> der::Result<DeferDecodeCertificate<'a>> { + reader.read_nested(header.length, |reader| { + Ok(Self { + tbs_certificate: reader.tlv_bytes()?, + signature_algorithm: reader.tlv_bytes()?, + signature: reader.tlv_bytes()?, + }) + }) + } +} + +impl FixedTag for DeferDecodeCertificate<'_> { + const TAG: Tag = Tag::Sequence; +} + +///Structure supporting deferred decoding of fields in the TBSCertificate SEQUENCE +pub struct DeferDecodeTbsCertificate<'a> { + /// Decoded field + pub version: u8, + /// Defer decoded field + pub serial_number: &'a [u8], + /// Defer decoded field + pub signature: &'a [u8], + /// Defer decoded field + pub issuer: &'a [u8], + /// Defer decoded field + pub validity: &'a [u8], + /// Defer decoded field + pub subject: &'a [u8], + /// Defer decoded field + pub subject_public_key_info: &'a [u8], + /// Decoded field (never present) + pub issuer_unique_id: Option<BitStringRef<'a>>, + /// Decoded field (never present) + pub subject_unique_id: Option<BitStringRef<'a>>, + /// Defer decoded field + pub extensions: &'a [u8], +} + +impl<'a> DecodeValue<'a> for DeferDecodeTbsCertificate<'a> { + fn decode_value<R: Reader<'a>>( + reader: &mut R, + header: Header, + ) -> der::Result<DeferDecodeTbsCertificate<'a>> { + reader.read_nested(header.length, |reader| { + let version = ContextSpecific::decode_explicit(reader, ::der::TagNumber::N0)? + .map(|cs| cs.value) + .unwrap_or_else(Default::default); + + Ok(Self { + version, + serial_number: reader.tlv_bytes()?, + signature: reader.tlv_bytes()?, + issuer: reader.tlv_bytes()?, + validity: reader.tlv_bytes()?, + subject: reader.tlv_bytes()?, + subject_public_key_info: reader.tlv_bytes()?, + issuer_unique_id: reader.decode()?, + subject_unique_id: reader.decode()?, + extensions: reader.tlv_bytes()?, + }) + }) + } +} + +impl FixedTag for DeferDecodeTbsCertificate<'_> { + const TAG: Tag = Tag::Sequence; +} + +#[test] +fn reencode_cert() { + let der_encoded_cert = + include_bytes!("examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der"); + 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(); + 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(); + 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(); + assert_eq!(defer_cert.signature, reencoded_sig); + + let parsed_coverage_tbs = + DeferDecodeTbsCertificate::from_der(defer_cert.tbs_certificate).unwrap(); + + // TODO - defer decode then reencode version field + + let encoded_serial = parsed_tbs.serial_number.to_vec().unwrap(); + assert_eq!(parsed_coverage_tbs.serial_number, encoded_serial); + + let encoded_signature = parsed_tbs.signature.to_vec().unwrap(); + assert_eq!(parsed_coverage_tbs.signature, encoded_signature); + + let encoded_issuer = parsed_tbs.issuer.to_vec().unwrap(); + assert_eq!(parsed_coverage_tbs.issuer, encoded_issuer); + + let encoded_validity = parsed_tbs.validity.to_vec().unwrap(); + assert_eq!(parsed_coverage_tbs.validity, encoded_validity); + + let encoded_subject = parsed_tbs.subject.to_vec().unwrap(); + assert_eq!(parsed_coverage_tbs.subject, encoded_subject); + + let encoded_subject_public_key_info = parsed_tbs.subject_public_key_info.to_vec().unwrap(); + assert_eq!( + parsed_coverage_tbs.subject_public_key_info, + encoded_subject_public_key_info + ); + + // 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(); + assert_eq!(&parsed_coverage_tbs.extensions[4..], encoded_extensions); +} + +#[test] +fn decode_oversized_oids() { + let o1parse = ObjectIdentifier::from_der(&hex!( + "06252B060104018237150885C8B86B87AFF00383A99F3C96C34081ADE6494D82B0E91D85B2873D" + )) + .unwrap(); + let o1str = o1parse.to_string(); + assert_eq!( + o1str, + "1.3.6.1.4.1.311.21.8.11672683.15464451.6967228.369088.2847561.77.4994205.11305917" + ); + let o1 = ObjectIdentifier::new_unwrap( + "1.3.6.1.4.1.311.21.8.11672683.15464451.6967228.369088.2847561.77.4994205.11305917", + ); + assert_eq!( + 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(); + assert_eq!( + &hex!("06252B060104018237150885C8B86B87AFF00383A99F3C96C34081ADE6494D82B0E91D85B2873D"), + enc_oid.as_slice() + ); +} + +#[test] +fn decode_cert() { + // cloned cert with variety of interesting bits, including subject DN encoded backwards, large + // policy mapping set, large policy set (including one with qualifiers), fairly typical set of + // extensions otherwise + let der_encoded_cert = + include_bytes!("examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + println!("{:?}", cert); + let exts = cert.tbs_certificate.extensions.unwrap(); + for (ext, (oid, crit)) in exts.iter().zip(EXTENSIONS) { + assert_eq!(ext.extn_id.to_string(), *oid); + assert_eq!(ext.critical, *crit); + } + + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + + assert_eq!(cert.tbs_certificate.version, Version::V3); + let target_serial: [u8; 16] = [ + 0x7F, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x49, 0xCF, 0x70, 0x66, 0x4D, 0x00, 0x00, 0x00, + 0x02, + ]; + assert_eq!( + cert.tbs_certificate.serial_number, + UIntRef::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(), + Tag::Null + ); + assert_eq!( + cert.tbs_certificate.signature.parameters.unwrap().is_null(), + true + ); + + let mut counter = 0; + let i = cert.tbs_certificate.issuer.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!(atav.value.printable_string().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(), + "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(), + "PTE IdenTrust Global Common Root CA 1" + ); + } + counter += 1; + } + } + + assert_eq!( + cert.tbs_certificate + .validity + .not_before + .to_unix_duration() + .as_secs(), + 1416524490 + ); + assert_eq!( + cert.tbs_certificate + .validity + .not_after + .to_unix_duration() + .as_secs(), + 1516628593 + ); + + counter = 0; + let i = cert.tbs_certificate.subject.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + for atav in i1 { + // Yes, this cert features RDNs encoded in reverse order + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.3"); + assert_eq!( + atav.value.printable_string().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(), + "TestFPKI" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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"); + } + counter += 1; + } + } + + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .oid + .to_string(), + "1.2.840.113549.1.1.1" + ); + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .parameters + .unwrap() + .tag(), + Tag::Null + ); + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .parameters + .unwrap() + .is_null(), + true + ); + + // TODO - parse and compare public key + + let exts = cert.tbs_certificate.extensions.unwrap(); + for (ext, (oid, crit)) in exts.iter().zip(EXTENSIONS) { + assert_eq!(ext.extn_id.to_string(), *oid); + assert_eq!(ext.critical, *crit); + } + + assert_eq!( + cert.signature_algorithm.oid.to_string(), + "1.2.840.113549.1.1.11" + ); + assert_eq!( + cert.signature_algorithm.parameters.unwrap().tag(), + Tag::Null + ); + assert_eq!(cert.signature_algorithm.parameters.unwrap().is_null(), true); + + assert_eq!( + &hex!("2A892F357BF3EF19E1211986106803FA18E66237802F1B1B0C6756CE678DB01D72CD0A4EB7171C2CDDF110ACD38AA65C35699E869C219AD7550AA4F287BB784F72EF8C9EA0E3DD103EFE5BF182EA36FFBCB45AAE65840263680534789C4F3215AF5454AD48CBC4B7A881E0135401A0BD5A849C11101DD1C66178E762C00DF59DD50F8DE9ED46FC6A0D742AE5697D87DD08DAC5291A75FB13C82FF2865C9E36799EA726137E1814E6A878C9532E8FC3D0A2A942D1CCC668FFCEAC255E6002FDE5ACDF2CE47556BB141C3A797A4BFDB673F6F1C229D7914FFEEF1505EE36F8038137D1B8F90106994BAB3E6FF0F60360A2E32F7A30B7ECEC1502DF3CC725BD6E436BA8F96A1847C9CEBB3F5A5906472292501D59BE1A98475BB1F30B677FAA8A45E351640C85B1B22661D33BD23EC6C0CA33DDD79E1120C7FC869EC4D0175ADB4A258AEAC5E8D2F0F578B8BF4B2C5DCC3269768AAA5B9E26D0592C5BB09C702C72E0A60F66D3EEB2B4983279634D59B0A2011B0E26AE796CC95D3243DF49615434E5CC06C374C3F936C005D360CAE6101F3AE7E97E29A157F5020770D4648D7877EBF8248CF3F3E68F9957A36F92D50616F2C60D3842327EF9BC0312CFF03A48C78E97254C2ADEADCA05069168443D833831FF66295A2EED685F164F1DBE01F8C897E1F63D42851682CBEE7B5A64D7BA2923D33644DBF1F7B3EDCE996F9928F043"), + cert.signature.raw_bytes() + ); +} diff --git a/tests/certreq.rs b/tests/certreq.rs new file mode 100644 index 0000000..547524c --- /dev/null +++ b/tests/certreq.rs @@ -0,0 +1,86 @@ +//! Certification request (`CertReq`) tests + +use der::{Encode, Tag, Tagged}; +use hex_literal::hex; +use x509_cert::request::{CertReq, Version}; + +const RSA_KEY: &[u8] = &hex!("3082010A0282010100BF59F7FE716DDE47C73579CA846EFA8D30AB3612E0D6A524204A72CA8E50C9F459513DF0D73331BED3D7A2DA7A362719E471EE6A9D87827D1024ED44605AB9B48F3B808C5E173B9F3EC4003D57F1718489F5C7A0421C46FBD527A40AB4BA6B9DB16A545D1ECF6E2A5633BD80594EBA4AFEE71F63E1D357C64E9A3FF6B83746A885C373F3527987E4C2B4AF7FE4D4EA16405E5E15285DD938823AA18E2634BAFE847A761CAFABB0401D3FA03A07A9D097CBB0C77156CCFE36131DADF1C109C2823972F0AF21A35F358E788304C0C78B951739D91FABFFD07AA8CD4F69746B3D0EB4587469F9D39F4FBDC761200DFB27DAF69562311D8B191B7EEFAAE2F8D6F8EB0203010001"); +const RSA_SIG: &[u8] = &hex!("2B053CFE81C6542176BD70B373A5FC8DC1F1806A5AB10D25E36690EED1DF57AD5F18EC0CCF165F000245B14157141224B431EC6715EFE937F66B892D11EDF8858EDF67ACCAE9701A2244BECA80705D7CC292BAD9B02001E4572EE492B08473D5AF59CC83DDA1DE5C2BF470FD784495070A9C5AF8EA9A4060C1DBC5C4690CC8DF6D528C55D82EC9C0DF3046BBCAE7542025D7EE170788C9C234132703290A31AC2700E55339590226D5E582EC61869862769FD85B45F287FFDD6DB530995D31F94D7D2C26EF3F48A182C3026CC698F382A72F1A11E3C689953055DAC0DFEBE9CDB163CA3AF33FFC4DA0F6B84B9D7CDD4321CCECD4BAC528DEFF9715FFD9D4731E"); + +/// RSA-2048 `CertReq` encoded as ASN.1 DER +const RSA_2048_DER_EXAMPLE: &[u8] = include_bytes!("examples/rsa2048-csr.der"); + +const NAMES: &[(&str, &str)] = &[ + ("2.5.4.3", "example.com"), + ("2.5.4.7", "Los Angeles"), + ("2.5.4.8", "California"), + ("2.5.4.10", "Example Inc"), + ("2.5.4.6", "US"), +]; + +#[rustfmt::skip] +const EXTENSIONS: &[(&str, &[u8])] = &[ + ("2.5.29.19", &hex!("3000")), // basicConstraints + ("2.5.29.15", &hex!("030205A0")), // keyUsage + ("2.5.29.37", &hex!("301406082B0601050507030106082B06010505070302")), // extKeyUsage + ("2.5.29.17", &hex!("300D820B6578616D706C652E636F6D")), // subjectAltNamec +]; + +#[test] +fn decode_rsa_2048_der() { + let cr = CertReq::try_from(RSA_2048_DER_EXAMPLE).unwrap(); + + // Check the version. + assert_eq!(cr.info.version, Version::V1); + + // Check all the RDNs. + assert_eq!(cr.info.subject.0.len(), NAMES.len()); + 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(), + _ => panic!("unexpected tag"), + }; + + assert_eq!(kind.oid, oid.parse().unwrap()); + assert_eq!(name.0.len(), 1); + assert_eq!(value, *val); + } + + // Check the public key. + 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); + + // Check the attributes (just one; contains extensions). + assert_eq!(cr.info.attributes.len(), 1); + let attribute = cr.info.attributes.get(0).unwrap(); + assert_eq!(attribute.oid, "1.2.840.113549.1.9.14".parse().unwrap()); // extensionRequest + assert_eq!(attribute.values.len(), 1); + + // Check the extensions. + let extensions: x509_cert::ext::Extensions = + attribute.values.get(0).unwrap().decode_into().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!(!ext.critical); + } + + // Check the signature value. + assert_eq!(cr.algorithm.oid, "1.2.840.113549.1.1.11".parse().unwrap()); + assert!(cr.algorithm.parameters.unwrap().is_null()); + assert_eq!(cr.signature.as_bytes().unwrap(), RSA_SIG); +} + +// The following tests currently fail because of a bug in the `der` crate; +// specifically, the `IMPLICIT` tagging on `CertReqInfo::attributes`. + +#[test] +fn encode_rsa_2048_der() { + let cr = CertReq::try_from(RSA_2048_DER_EXAMPLE).unwrap(); + let cr_encoded = cr.to_vec().unwrap(); + assert_eq!(RSA_2048_DER_EXAMPLE, cr_encoded.as_slice()); +} diff --git a/tests/crl.rs b/tests/crl.rs new file mode 100644 index 0000000..e3d7eba --- /dev/null +++ b/tests/crl.rs @@ -0,0 +1,17 @@ +use der::Decode; +use x509_cert::crl::CertificateList; + +#[test] +fn decode_crl() { + // vanilla CRL from PKITS + let der_encoded_cert = include_bytes!("examples/GoodCACRL.crl"); + let crl = CertificateList::from_der(der_encoded_cert).unwrap(); + assert_eq!(2, crl.tbs_cert_list.crl_extensions.unwrap().len()); + assert_eq!(2, crl.tbs_cert_list.revoked_certificates.unwrap().len()); + + // CRL with an entry with no entry extensions + let der_encoded_cert = include_bytes!("examples/tscpbcasha256.crl"); + let crl = CertificateList::from_der(der_encoded_cert).unwrap(); + assert_eq!(2, crl.tbs_cert_list.crl_extensions.unwrap().len()); + assert_eq!(4, crl.tbs_cert_list.revoked_certificates.unwrap().len()); +} diff --git a/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der b/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der Binary files differnew file mode 100644 index 0000000..d6cda5a --- /dev/null +++ b/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der diff --git a/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der b/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der Binary files differnew file mode 100644 index 0000000..08db364 --- /dev/null +++ b/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der diff --git a/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der b/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der Binary files differnew file mode 100644 index 0000000..63dbd46 --- /dev/null +++ b/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der diff --git a/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der b/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der Binary files differnew file mode 100644 index 0000000..bcdf79c --- /dev/null +++ b/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der diff --git a/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der b/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der Binary files differnew file mode 100644 index 0000000..d94106f --- /dev/null +++ b/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der diff --git a/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der b/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der Binary files differnew file mode 100644 index 0000000..a0fba41 --- /dev/null +++ b/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der diff --git a/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der b/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der Binary files differnew file mode 100644 index 0000000..24bdee8 --- /dev/null +++ b/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der diff --git a/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der b/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der Binary files differnew file mode 100644 index 0000000..8f48e0d --- /dev/null +++ b/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der diff --git a/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der b/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der Binary files differnew file mode 100644 index 0000000..ae22bd4 --- /dev/null +++ b/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der diff --git a/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der b/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der Binary files differnew file mode 100644 index 0000000..0aef65d --- /dev/null +++ b/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der diff --git a/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der b/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der Binary files differnew file mode 100644 index 0000000..62e8f41 --- /dev/null +++ b/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der diff --git a/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der b/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der Binary files differnew file mode 100644 index 0000000..936b06a --- /dev/null +++ b/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der diff --git a/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der b/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der Binary files differnew file mode 100644 index 0000000..750c085 --- /dev/null +++ b/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der diff --git a/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der b/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der Binary files differnew file mode 100644 index 0000000..5231873 --- /dev/null +++ b/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der diff --git a/tests/examples/GoodCACRL.crl b/tests/examples/GoodCACRL.crl Binary files differnew file mode 100644 index 0000000..d46110c --- /dev/null +++ b/tests/examples/GoodCACRL.crl diff --git a/tests/examples/GoodCACert.crt b/tests/examples/GoodCACert.crt Binary files differnew file mode 100644 index 0000000..edbfa64 --- /dev/null +++ b/tests/examples/GoodCACert.crt diff --git a/tests/examples/amazon.der b/tests/examples/amazon.der Binary files differnew file mode 100644 index 0000000..cc30bd6 --- /dev/null +++ b/tests/examples/amazon.der diff --git a/tests/examples/amazon.pem b/tests/examples/amazon.pem new file mode 100644 index 0000000..8b41dea --- /dev/null +++ b/tests/examples/amazon.pem @@ -0,0 +1,49 @@ +-----BEGIN CERTIFICATE----- +MIIIwTCCB6mgAwIBAgIQDkI5q4Xi5qJ8Usbem5B42TANBgkqhkiG9w0BAQsFADBE +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMR4wHAYDVQQDExVE +aWdpQ2VydCBHbG9iYWwgQ0EgRzIwHhcNMjExMDA2MDAwMDAwWhcNMjIwOTE5MjM1 +OTU5WjAYMRYwFAYDVQQDDA0qLnBlZy5hMnouY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAv+/uM8Nke+pDU6lWoZALXfrNwH6/B3+8FEfNewD6mN8u +ntzCMH8fPU5Gb1/odQWS7GiBPowoU6smFj4F0kD3Qh4OUpAVbcYS2ad5nVBnwmh8 +Tm/U3DO34FZgxtjz3qxBVKr1ryYO3+1/H2xBuit8W1TSd/s+2joupzAAQq0zn8B3 +6kpi14dYIaZgBw0WuQHaybTlC0cko9x2t43RXxNZgRxqYyh+I+MK19ZOAZgCPAoo +JXyQiUIohTdOw8gnDY5gI4zhHyzvuhifSBeII1WA6MfGdiKV+K0R9LLr0oHYV3F7 +yCQoDN29ZVgXj4UZu64IxIQnuHRN6X2otR3qgUjAoQIDAQABo4IF2TCCBdUwHwYD +VR0jBBgwFoAUJG4rLdBqklFRJWkBqppHponnQCAwHQYDVR0OBBYEFJVFFD46QB6V +FvCCrEVzglhtm/B0MIICowYDVR0RBIICmjCCApaCDGFtYXpvbi5jby51a4ITdWVk +YXRhLmFtYXpvbi5jby51a4IQd3d3LmFtYXpvbi5jby51a4IXb3JpZ2luLXd3dy5h +bWF6b24uY28udWuCDSoucGVnLmEyei5jb22CCmFtYXpvbi5jb22CCGFtem4uY29t +ghF1ZWRhdGEuYW1hem9uLmNvbYINdXMuYW1hem9uLmNvbYIOd3d3LmFtYXpvbi5j +b22CDHd3dy5hbXpuLmNvbYIUY29ycG9yYXRlLmFtYXpvbi5jb22CEWJ1eWJveC5h +bWF6b24uY29tghFpcGhvbmUuYW1hem9uLmNvbYINeXAuYW1hem9uLmNvbYIPaG9t +ZS5hbWF6b24uY29tghVvcmlnaW4td3d3LmFtYXpvbi5jb22CFm9yaWdpbjItd3d3 +LmFtYXpvbi5jb22CIWJ1Y2tleWUtcmV0YWlsLXdlYnNpdGUuYW1hem9uLmNvbYIS +aHVkZGxlcy5hbWF6b24uY29tgglhbWF6b24uZGWCDXd3dy5hbWF6b24uZGWCFG9y +aWdpbi13d3cuYW1hem9uLmRlggxhbWF6b24uY28uanCCCWFtYXpvbi5qcIINd3d3 +LmFtYXpvbi5qcIIQd3d3LmFtYXpvbi5jby5qcIIXb3JpZ2luLXd3dy5hbWF6b24u +Y28uanCCECouYWEucGVnLmEyei5jb22CECouYWIucGVnLmEyei5jb22CECouYWMu +cGVnLmEyei5jb22CGG9yaWdpbi13d3cuYW1hem9uLmNvbS5hdYIRd3d3LmFtYXpv +bi5jb20uYXWCECouYnoucGVnLmEyei5jb22CDWFtYXpvbi5jb20uYXWCGG9yaWdp +bjItd3d3LmFtYXpvbi5jby5qcDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMHcGA1UdHwRwMG4wNaAzoDGGL2h0dHA6Ly9jcmwz +LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbENBRzIuY3JsMDWgM6Axhi9odHRw +Oi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9iYWxDQUcyLmNybDA+BgNV +HSAENzA1MDMGBmeBDAECATApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRpZ2lj +ZXJ0LmNvbS9DUFMwdAYIKwYBBQUHAQEEaDBmMCQGCCsGAQUFBzABhhhodHRwOi8v +b2NzcC5kaWdpY2VydC5jb20wPgYIKwYBBQUHMAKGMmh0dHA6Ly9jYWNlcnRzLmRp +Z2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbENBRzIuY3J0MAwGA1UdEwEB/wQCMAAw +ggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2ACl5vvCeOTkh8FZzn2Old+W+V32c +YAr4+U1dJlwlXceEAAABfFOKWLsAAAQDAEcwRQIhAOIjvEg/ozDhjhiV0fbaYc83 +oPb2I08md/bU7nhfQufgAiAyA6CaJgpUYYQjchPqiS3DzHIIQL0FIkohEcluqPJV +oAB3AFGjsPX9AXmcVm24N3iPDKR6zBsny/eeiEKaDf7UiwXlAAABfFOKWOQAAAQD +AEgwRgIhAJA7QR62SZNgoU0SCfXaPUriW4FrPlVUZbSLl7s+q3W4AiEAg/iCIUET +pZEw8IAvQGC4ggNwRgm97pma3Ug8FhJp0KgAdQBByMqx3yJGShDGoToJQodeTjGL +GwPr60vHaPCQYpYG9gAAAXxTilifAAAEAwBGMEQCIFcuV+EsdfqUxhSV4+pSF5I/ +EVBVin/kOpaTBcSTlHccAiA7IdRwyN09v9bnXKJ2XsMDGfe9RHwWwVoXA/NJMx5A +3DANBgkqhkiG9w0BAQsFAAOCAQEAyLJluG6AFZ5fVgxdS574SZd7dInEumnQUQmt ++D/vbUb4NfiO4aRdPGkGotGHptFW9wxYjvYlSmtWbns5v+0CmpiXadXLNPWZtNjJ +gFR3WTLbHzBtD+C8yL1AQ/L2kOk9PVpq50leD7+dZimurjX3k7CbxaItjHnKAq3V +klINM2LLV//ZqnQxBlHeUiTfnUjKR/0FRDM4zr247HX0BH5CUl3wT5/e6mBqkXkK +Vvl+zzVfQjkeL40KjJJjQ4+N0gsPS8+rBPGHEVXFGPtlbI6pyS3Wo2rTkaIDwd+i +7ckVQviFKPooe4SVfp+kX4xVu9BEI4hlVa2y7qc/mMecBpXT+Q== +-----END CERTIFICATE----- diff --git a/tests/examples/eca.der b/tests/examples/eca.der Binary files differnew file mode 100644 index 0000000..fdd35d5 --- /dev/null +++ b/tests/examples/eca.der diff --git a/tests/examples/eca_policies.ta b/tests/examples/eca_policies.ta Binary files differnew file mode 100755 index 0000000..717364a --- /dev/null +++ b/tests/examples/eca_policies.ta diff --git a/tests/examples/entrust.der b/tests/examples/entrust.der Binary files differnew file mode 100644 index 0000000..dbddf49 --- /dev/null +++ b/tests/examples/entrust.der diff --git a/tests/examples/entrust_dnConstraint.ta b/tests/examples/entrust_dnConstraint.ta Binary files differnew file mode 100755 index 0000000..ff784dd --- /dev/null +++ b/tests/examples/entrust_dnConstraint.ta diff --git a/tests/examples/exostar.der b/tests/examples/exostar.der Binary files differnew file mode 100644 index 0000000..d2047e4 --- /dev/null +++ b/tests/examples/exostar.der diff --git a/tests/examples/exostar_policyFlags.ta b/tests/examples/exostar_policyFlags.ta Binary files differnew file mode 100755 index 0000000..46e5f92 --- /dev/null +++ b/tests/examples/exostar_policyFlags.ta diff --git a/tests/examples/raytheon.der b/tests/examples/raytheon.der Binary files differnew file mode 100644 index 0000000..9b57743 --- /dev/null +++ b/tests/examples/raytheon.der diff --git a/tests/examples/raytheon_pathLenConstraint.ta b/tests/examples/raytheon_pathLenConstraint.ta Binary files differnew file mode 100644 index 0000000..eb0fd7f --- /dev/null +++ b/tests/examples/raytheon_pathLenConstraint.ta diff --git a/tests/examples/rsa2048-crt.der b/tests/examples/rsa2048-crt.der Binary files differnew file mode 100644 index 0000000..6f46ef1 --- /dev/null +++ b/tests/examples/rsa2048-crt.der diff --git a/tests/examples/rsa2048-crt.pem b/tests/examples/rsa2048-crt.pem new file mode 100644 index 0000000..d45142c --- /dev/null +++ b/tests/examples/rsa2048-crt.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDnDCCAoSgAwIBAgIJAKQzLo3paeO7MA0GCSqGSIb3DQEBCwUAMGQxFDASBgNV +BAMMC2V4YW1wbGUuY29tMRQwEgYDVQQHDAtMb3MgQW5nZWxlczETMBEGA1UECAwK +Q2FsaWZvcm5pYTEUMBIGA1UECgwLRXhhbXBsZSBJbmMxCzAJBgNVBAYTAlVTMB4X +DTIyMDEwODE4NDA1N1oXDTIzMDEwODE4NDA1N1owZDEUMBIGA1UEAwwLZXhhbXBs +ZS5jb20xFDASBgNVBAcMC0xvcyBBbmdlbGVzMRMwEQYDVQQIDApDYWxpZm9ybmlh +MRQwEgYDVQQKDAtFeGFtcGxlIEluYzELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC/Wff+cW3eR8c1ecqEbvqNMKs2EuDWpSQgSnLK +jlDJ9FlRPfDXMzG+09ei2no2Jxnkce5qnYeCfRAk7URgWrm0jzuAjF4XO58+xAA9 +V/FxhIn1x6BCHEb71SekCrS6a52xalRdHs9uKlYzvYBZTrpK/ucfY+HTV8ZOmj/2 +uDdGqIXDc/NSeYfkwrSvf+TU6hZAXl4VKF3ZOII6oY4mNLr+hHp2HK+rsEAdP6A6 +B6nQl8uwx3FWzP42Ex2t8cEJwoI5cvCvIaNfNY54gwTAx4uVFznZH6v/0HqozU9p +dGs9DrRYdGn5059PvcdhIA37J9r2lWIxHYsZG37vquL41vjrAgMBAAGjUTBPMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA +kqvA9M0WRVffA9Eb5h813vio3ceQ8JItVHWyvh9vNGOz3d3eywXIOAKMmzQRQUfY +7WMbjCM9ppTKRmfoFbMnDQb1aa93isuCoo5QRSpX6DmN/p4v3uz79p8m8in+xhKQ +1m6et1iwR9cbQxLsmsaVaVTn16xdsL+gq7V4IZXf8CVyxL0mH5FdRmj/nqiWTv6S +I9tIFiEhCqq1P5XGi6TJAg59M8Dlnd/j5eJHTIlADjG0O1LLvAcuc3rq+dYj0mOU +RX4MzusreyKRGdvr2IN2gYCDPOgOiqp3YKkOnXV8/pya1KSGrT51fEYTdUrjJ6dr +430thqsUED++/t+K76IRMw== +-----END CERTIFICATE----- diff --git a/tests/examples/rsa2048-csr.der b/tests/examples/rsa2048-csr.der Binary files differnew file mode 100644 index 0000000..31c9e22 --- /dev/null +++ b/tests/examples/rsa2048-csr.der diff --git a/tests/examples/rsa2048-csr.pem b/tests/examples/rsa2048-csr.pem new file mode 100644 index 0000000..15c7cc4 --- /dev/null +++ b/tests/examples/rsa2048-csr.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIDCTCCAfECAQAwZDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xFDASBgNVBAcMC0xv +cyBBbmdlbGVzMRMwEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQKDAtFeGFtcGxl +IEluYzELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQC/Wff+cW3eR8c1ecqEbvqNMKs2EuDWpSQgSnLKjlDJ9FlRPfDXMzG+09ei2no2 +Jxnkce5qnYeCfRAk7URgWrm0jzuAjF4XO58+xAA9V/FxhIn1x6BCHEb71SekCrS6 +a52xalRdHs9uKlYzvYBZTrpK/ucfY+HTV8ZOmj/2uDdGqIXDc/NSeYfkwrSvf+TU +6hZAXl4VKF3ZOII6oY4mNLr+hHp2HK+rsEAdP6A6B6nQl8uwx3FWzP42Ex2t8cEJ +woI5cvCvIaNfNY54gwTAx4uVFznZH6v/0HqozU9pdGs9DrRYdGn5059PvcdhIA37 +J9r2lWIxHYsZG37vquL41vjrAgMBAAGgYDBeBgkqhkiG9w0BCQ4xUTBPMAkGA1Ud +EwQCMAAwCwYDVR0PBAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAWBgNVHREEDzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAKwU8 +/oHGVCF2vXCzc6X8jcHxgGpasQ0l42aQ7tHfV61fGOwMzxZfAAJFsUFXFBIktDHs +ZxXv6Tf2a4ktEe34hY7fZ6zK6XAaIkS+yoBwXXzCkrrZsCAB5Fcu5JKwhHPVr1nM +g92h3lwr9HD9eESVBwqcWvjqmkBgwdvFxGkMyN9tUoxV2C7JwN8wRrvK51QgJdfu +FweIycI0EycDKQoxrCcA5VM5WQIm1eWC7GGGmGJ2n9hbRfKH/91ttTCZXTH5TX0s +Ju8/SKGCwwJsxpjzgqcvGhHjxomVMFXawN/r6c2xY8o68z/8TaD2uEudfN1DIczs +1LrFKN7/lxX/2dRzHg== +-----END CERTIFICATE REQUEST----- diff --git a/tests/examples/rsa2048-prv.der b/tests/examples/rsa2048-prv.der Binary files differnew file mode 100644 index 0000000..f5c6adc --- /dev/null +++ b/tests/examples/rsa2048-prv.der diff --git a/tests/examples/rsa2048-prv.pem b/tests/examples/rsa2048-prv.pem new file mode 100644 index 0000000..26e7df3 --- /dev/null +++ b/tests/examples/rsa2048-prv.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAv1n3/nFt3kfHNXnKhG76jTCrNhLg1qUkIEpyyo5QyfRZUT3w +1zMxvtPXotp6NicZ5HHuap2Hgn0QJO1EYFq5tI87gIxeFzufPsQAPVfxcYSJ9ceg +QhxG+9UnpAq0umudsWpUXR7PbipWM72AWU66Sv7nH2Ph01fGTpo/9rg3RqiFw3Pz +UnmH5MK0r3/k1OoWQF5eFShd2TiCOqGOJjS6/oR6dhyvq7BAHT+gOgep0JfLsMdx +Vsz+NhMdrfHBCcKCOXLwryGjXzWOeIMEwMeLlRc52R+r/9B6qM1PaXRrPQ60WHRp ++dOfT73HYSAN+yfa9pViMR2LGRt+76ri+Nb46wIDAQABAoIBAQC0ZwMKrTATH4Lt +pLxM7UBkupzAJz44v4r2spnU5CXAsRFAKfCVQxvEOH8Vd3s+8NBVcyB+/bOTT4tX +9SXA3eg1FdDYWf4fU0PIbgt3yiDEkFttD97EVVqK9KQh4UIQe4M5j/CntnOD/oA0 +2ZVXHYU/TWDjVEzE7vz0gDKLzZO3lWrpmUiLd6bglfA0vxpbgQ901e/SWdXlqXzO +ejjRX/htEQ8tDyW9GrOdVs6NRM1KH3+WCUhNp6xBQtNcqCwKzpvmMcPQyVGxAy6P +fB7vqzjSDs1jEREFhWW7266tZgL+AieA+b86eSFDqMJ9LoLhYyHtflR2UAsgae/a ++sB3B4XRAoGBAN3E2HFJDewNSIniM+f1XVk+rvbOTsTcCFvmXd47HFz+ogJ+n6ME +/xwT5OYGsXAEhuduFndkbZAz/s+GpNGu8BBklmUagGXcYKUibt7nhQDRUC8pf9NQ +KPGQOyLsZXfFVs/QxwIQl/QXw8TS0vtqosirY//zPG9gm2jfqmkr34BDAoGBANzj +Lq85a1V4s2UVGj05VBZsP5P9XL9ew7oOthXvffgcwc5mIQDiGsU2rQRn957JES/g +hoPG5LgOjep129mT7h1Ws0+4dMO0uKXbMvOHkhUbdXiumFFYCxfQWiV1AVOMxyeE +jWPZiXKCuaTLQs0VFnQG0dT5cWIfBnOBhUx8lU45AoGAAvNjjd5S+RkUJgGEf0mc +fFuBKHeGRMhItDBUf2h58CLTNQVKSnj+i/kXype8NKlawimM0vnbG1gVw90exEt3 +lkBAYAgCPVi5UHks0Hp0IpamYnpC4STn5o7suoI6t2VAynMUsspVu0G1sSC8/etl +TxY4tmceHr1CVBrlwZB74NECgYByAStaMteMELT+ieq2CL22qP4TgqP4/Y8lm2wt +XCN3CFibD6kfDJPmj7ay3Ho4UOx2+npSzzfDK3fhuBzVan1uVQ5NKhXR4JegusbM +XH9wN3Dk7bAd48Qt8VJlnMMnfTRY2BglneRL3t60CFidArJJBjAMrQXxL7Qjr4i+ +Flr1OQKBgCHey67JrCzv3VqnnCB1iScVMi8ABv11Mw8fwXIdhpgzYeFFAQ4YROuB +YgmIjTW2H6sjZHKVuyUq0wWm+SOByeVmNnh6dNhD6MLWnaGOC2ZSzaM3w0QizJZP +lo2n+xr6iC0aRBV5PkbIxv1eCLwDBO7Rij0NMaljeFUIXdMNb8NT +-----END RSA PRIVATE KEY----- diff --git a/tests/examples/tscpbcasha256.crl b/tests/examples/tscpbcasha256.crl Binary files differnew file mode 100644 index 0000000..e7fe379 --- /dev/null +++ b/tests/examples/tscpbcasha256.crl diff --git a/tests/general_name.rs b/tests/general_name.rs new file mode 100644 index 0000000..5447c5f --- /dev/null +++ b/tests/general_name.rs @@ -0,0 +1,80 @@ +use x509_cert::ext::pkix::name::{GeneralName, GeneralNames}; + +use der::{Decode, Encode}; +use hex_literal::hex; +use rstest::rstest; + +const OTHER_NAME: &[u8] = &hex!("A01B060560865E0202A0120C105249462D472D32303030343033362D30"); +const RFC822_NAME: &[u8] = &hex!("8117456D61696C5F353238343037373733406468732E676F76"); +const DNS_NAME: &[u8] = + &hex!("8222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465"); +const DIRECTORY_NAME: &[u8] = + &hex!("A43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E"); +// TODO: EdiPartyName +const URI: &[u8] = &hex!( + "862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C" +); +const IPADDR: &[u8] = &hex!("87202A02102C000000000000000000000000FFFFFFFF000000000000000000000000"); +// TODO: RegisteredId + +const OTHER_NAMES: &[u8] = &hex!("301da01b060560865e0202a0120c105249462d472d32303030343033362d30"); +const RFC822_NAMES: &[u8] = &hex!("30198117456D61696C5F353238343037373733406468732E676F76"); +const DNS_NAMES: &[u8] = + &hex!("30248222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465"); +const DIRECTORY_NAMES: &[u8] = &hex!("303DA43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E"); +// TODO: EdiPartyName +const URIS: &[u8] = &hex!( + "302C862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C" +); +const IPADDRS: &[u8] = + &hex!("302287202A02102C000000000000000000000000FFFFFFFF000000000000000000000000"); +// TODO: RegisteredId + +#[rstest] +#[case(1, OTHER_NAME)] +#[case(2, RFC822_NAME)] +#[case(3, DNS_NAME)] +#[case(4, DIRECTORY_NAME)] +#[case(5, URI)] +#[case(6, IPADDR)] +fn singular(#[case] idx: usize, #[case] value: &[u8]) { + let decoded = GeneralName::from_der(value).unwrap(); + + match (idx, &decoded) { + (1, GeneralName::OtherName(..)) => (), + (2, GeneralName::Rfc822Name(..)) => (), + (3, GeneralName::DnsName(..)) => (), + (4, GeneralName::DirectoryName(..)) => (), + (5, GeneralName::UniformResourceIdentifier(..)) => (), + (6, GeneralName::IpAddress(..)) => (), + _ => panic!("unexpected decoded value"), + } + + let encoded = decoded.to_vec().unwrap(); + assert_eq!(value, encoded); +} + +#[rstest] +#[case(1, OTHER_NAMES)] +#[case(2, RFC822_NAMES)] +#[case(3, DNS_NAMES)] +#[case(4, DIRECTORY_NAMES)] +#[case(5, URIS)] +#[case(6, IPADDRS)] +fn plural(#[case] idx: usize, #[case] value: &[u8]) { + let decoded = GeneralNames::from_der(value).unwrap(); + + assert_eq!(1, decoded.len()); + match (idx, &decoded[0]) { + (1, GeneralName::OtherName(..)) => (), + (2, GeneralName::Rfc822Name(..)) => (), + (3, GeneralName::DnsName(..)) => (), + (4, GeneralName::DirectoryName(..)) => (), + (5, GeneralName::UniformResourceIdentifier(..)) => (), + (6, GeneralName::IpAddress(..)) => (), + _ => panic!("unexpected decoded value"), + } + + let encoded = decoded.to_vec().unwrap(); + assert_eq!(value, encoded); +} diff --git a/tests/name.rs b/tests/name.rs new file mode 100644 index 0000000..6a4a5fa --- /dev/null +++ b/tests/name.rs @@ -0,0 +1,336 @@ +//! Name tests + +use const_oid::ObjectIdentifier; +use der::asn1::{OctetStringRef, SetOfVec, Utf8StringRef}; +use der::{AnyRef, Decode, Encode, Tag, Tagged}; +use hex_literal::hex; +use x509_cert::attr::AttributeTypeAndValue; +use x509_cert::name::{Name, RdnSequence, RelativeDistinguishedName}; + +#[test] +fn decode_name() { + // 134 64: SEQUENCE { + // 136 11: SET { + // 138 9: SEQUENCE { + // 140 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 145 2: PrintableString 'US' + // : } + // : } + // 149 31: SET { + // 151 29: SEQUENCE { + // 153 3: OBJECT IDENTIFIER organizationName (2 5 4 10) + // 158 22: PrintableString 'Test Certificates 2011' + // : } + // : } + // 182 16: SET { + // 184 14: SEQUENCE { + // 186 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 191 7: PrintableString 'Good CA' + // : } + // : } + // : } + let rdn1 = + Name::from_der(&hex!("3040310B3009060355040613025553311F301D060355040A1316546573742043657274696669636174657320323031313110300E06035504031307476F6F64204341")[..]); + let rdn1a = rdn1.unwrap(); + + let mut counter = 0; + let i = rdn1a.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "Good CA" + ); + } + counter += 1; + } + } +} + +#[test] +fn decode_rdn() { + // 0 11: SET { + // 2 9: SEQUENCE { + // 4 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 9 2: PrintableString 'US' + // : } + // : } + let rdn1 = + RelativeDistinguishedName::from_der(&hex!("310B3009060355040613025553")[..]).unwrap(); + let i = rdn1.0.iter(); + for atav in i { + let oid = atav.oid; + assert_eq!(oid.to_string(), "2.5.4.6"); + let value = atav.value; + assert_eq!(value.tag(), Tag::PrintableString); + let ps = value.printable_string().unwrap(); + assert_eq!(ps.to_string(), "US"); + } + + // 0 31: SET { + // 2 17: SEQUENCE { + // 4 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 9 10: UTF8String 'JOHN SMITH' + // : } + // 21 10: SEQUENCE { + // 23 3: OBJECT IDENTIFIER organizationName (2 5 4 10) + // 28 3: UTF8String '123' + // : } + // : } + + // reordered the encoding so second element above appears first so parsing can succeed + let rdn2a = RelativeDistinguishedName::from_der( + &hex!("311F300A060355040A0C03313233301106035504030C0A4A4F484E20534D495448")[..], + ) + .unwrap(); + let mut i = rdn2a.0.iter(); + let atav1a = i.next().unwrap(); + let oid2 = atav1a.oid; + assert_eq!(oid2.to_string(), "2.5.4.10"); + let value2 = atav1a.value; + assert_eq!(value2.tag(), Tag::Utf8String); + let utf8b = value2.utf8_string().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; + assert_eq!(value1.tag(), Tag::Utf8String); + let utf8a = value1.utf8_string().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_eq!( + reencoded, + &hex!("311F300A060355040A0C03313233301106035504030C0A4A4F484E20534D495448") + ); + + let mut from_scratch2 = RelativeDistinguishedName::default(); + assert!(from_scratch2.0.add(*atav2a).is_ok()); + // fails when caller adds items not in DER lexicographical order + assert!(from_scratch2.0.add(*atav1a).is_err()); + + // allow out-of-order RDNs (see: RustCrypto/formats#625) + assert!(RelativeDistinguishedName::from_der( + &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..], + ) + .is_ok()); +} + +// #[test] +// fn encode_atav() { +// // 0 11: SET { +// // 2 9: SEQUENCE { +// // 4 3: OBJECT IDENTIFIER countryName (2 5 4 6) +// // 9 2: PrintableString 'US' +// // : } +// // : } +// let rdn1 = +// RelativeDistinguishedName::from_der(&hex!("310B3009060355040613025553")[..]).unwrap(); +// +// // Re-encode and compare to reference +// let b1 = rdn1.to_vec().unwrap(); +// assert_eq!(b1, &hex!("310B3009060355040613025553")[..]); +// let mut i = rdn1.iter(); +// let atav1 = i.next().unwrap(); +// +// // 0 31: SET { +// // 2 17: SEQUENCE { +// // 4 3: OBJECT IDENTIFIER commonName (2 5 4 3) +// // 9 10: UTF8String 'JOHN SMITH' +// // : } +// // 21 10: SEQUENCE { +// // 23 3: OBJECT IDENTIFIER organizationName (2 5 4 10) +// // 28 3: UTF8String '123' +// // : } +// // : } +// let rdn2 = RelativeDistinguishedName::from_der( +// &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..], +// ) +// .unwrap(); +// +// // Re-encode and compare to reference +// let b1 = rdn2.to_vec().unwrap(); +// assert_eq!( +// b1, +// &hex!("311F301106035504030C0A4A4F484E20534D495448300A060355040A0C03313233")[..] +// ); +// +// let mut i = rdn2.iter(); +// let atav2 = i.next().unwrap(); +// +// // Create new AttributeTypeAndValue with OID from second item above and value from first +// let atav3: AttributeTypeAndValue = AttributeTypeAndValue { +// oid: atav2.oid, +// value: atav1.value, +// }; +// let b3 = atav3.to_vec().unwrap(); +// assert_eq!(b3, &hex!("3009060355040313025553")[..]); +// } + +/// Tests RdnSequence string serialization and deserialization +#[test] +fn rdns_serde() { + #[allow(clippy::type_complexity)] + let values: &[(&[&str], &str, &[&[AttributeTypeAndValue]])] = &[ + ( + &[ + "CN=foo,SN=bar,C=baz+L=bat", + "commonName=foo,sn=bar,COUNTRYNAME=baz+l=bat", + ], + "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(Utf8StringRef::new("baz").unwrap()), + }, + AttributeTypeAndValue { + oid: const_oid::db::rfc4519::L, + value: AnyRef::from(Utf8StringRef::new("bat").unwrap()), + }, + ], + ], + ), + ( + &["UID=jsmith,DC=example,DC=net"], + "UID=jsmith,DC=example,DC=net", + &[ + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::UID, + value: AnyRef::from(Utf8StringRef::new("jsmith").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("example").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("net").unwrap()), + }], + ], + ), + ( + &["OU=Sales+CN=J. Smith,DC=example,DC=net"], + "OU=Sales+CN=J. Smith,DC=example,DC=net", + &[ + &[ + AttributeTypeAndValue { + oid: const_oid::db::rfc4519::OU, + value: AnyRef::from(Utf8StringRef::new("Sales").unwrap()), + }, + AttributeTypeAndValue { + oid: const_oid::db::rfc4519::CN, + value: AnyRef::from(Utf8StringRef::new("J. Smith").unwrap()), + }, + ], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("example").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("net").unwrap()), + }], + ], + ), + ( + &["CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net"], + "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()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("example").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("net").unwrap()), + }], + ], + ), + ( + &["CN=Before\\0dAfter,DC=example,DC=net"], + "CN=Before\\0dAfter,DC=example,DC=net", + &[ + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::CN, + value: AnyRef::from(Utf8StringRef::new("Before\rAfter").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("example").unwrap()), + }], + &[AttributeTypeAndValue { + oid: const_oid::db::rfc4519::DC, + value: AnyRef::from(Utf8StringRef::new("net").unwrap()), + }], + ], + ), + ( + &["1.3.6.1.4.1.1466.0=#04024869"], + "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()), + }]], + ), + ]; + + for (inputs, output, rdns) in values { + let mut brdns = RdnSequence::default(); + for rdn in rdns.iter() { + let sofv = SetOfVec::try_from(rdn.to_vec()).unwrap(); + brdns.0.push(RelativeDistinguishedName::from(sofv)); + } + + // Check that serialization matches the expected output. + eprintln!("output: {}", output); + assert_eq!(*output, format!("{}", brdns)); + + // Check that all inputs deserializize as expected. + for input in inputs.iter() { + eprintln!("input: {}", input); + + let der = RdnSequence::encode_from_string(input).unwrap(); + let rdns = RdnSequence::from_der(&der).unwrap(); + + for (l, r) in brdns.0.iter().zip(rdns.0.iter()) { + for (ll, rr) in l.0.iter().zip(r.0.iter()) { + assert_eq!(ll, rr); + } + + assert_eq!(l, r); + } + + assert_eq!(brdns, rdns); + } + } +} diff --git a/tests/pkix_extensions.rs b/tests/pkix_extensions.rs new file mode 100644 index 0000000..d6af0b9 --- /dev/null +++ b/tests/pkix_extensions.rs @@ -0,0 +1,1110 @@ +//! Certificate tests +use const_oid::AssociatedOid; +use der::asn1::UIntRef; +use der::{Decode, Encode, ErrorKind, Length, Tag, Tagged}; +use hex_literal::hex; +use x509_cert::ext::pkix::crl::dp::{DistributionPoint, ReasonFlags, Reasons}; +use x509_cert::ext::pkix::name::{DistributionPointName, GeneralName, GeneralNames}; +use x509_cert::ext::pkix::*; +use x509_cert::ext::Extensions; +use x509_cert::name::Name; +use x509_cert::{Certificate, Version}; + +use const_oid::db::rfc5280::*; +use const_oid::db::rfc5912::ID_CE_CERTIFICATE_POLICIES; + +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()); + } + + SubjectKeyIdentifier::OID => { + let decoded = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + KeyUsage::OID => { + let decoded = KeyUsage::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + PrivateKeyUsagePeriod::OID => { + let decoded = PrivateKeyUsagePeriod::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + SubjectAltName::OID => { + let decoded = SubjectAltName::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + IssuerAltName::OID => { + let decoded = IssuerAltName::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + BasicConstraints::OID => { + let decoded = BasicConstraints::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + NameConstraints::OID => { + let decoded = NameConstraints::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + CrlDistributionPoints::OID => { + let decoded = CrlDistributionPoints::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + CertificatePolicies::OID => { + let decoded = CertificatePolicies::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + PolicyMappings::OID => { + let decoded = PolicyMappings::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + AuthorityKeyIdentifier::OID => { + let decoded = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + PolicyConstraints::OID => { + let decoded = PolicyConstraints::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + ExtendedKeyUsage::OID => { + let decoded = ExtendedKeyUsage::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + FreshestCrl::OID => { + let decoded = FreshestCrl::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + InhibitAnyPolicy::OID => { + let decoded = InhibitAnyPolicy::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + AuthorityInfoAccessSyntax::OID => { + let decoded = AuthorityInfoAccessSyntax::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + SubjectInfoAccessSyntax::OID => { + let decoded = SubjectInfoAccessSyntax::from_der(ext.extn_value).unwrap(); + assert_eq!(ext.extn_value, decoded.to_vec().unwrap()); + } + + _ => { + eprintln!("ignoring {} with criticality {}", ext.extn_id, ext.critical); + } + } + } +} + +#[test] +fn decode_general_name() { + // DnsName + let dns_name = GeneralName::from_der(&hex!("820C616D617A6F6E2E636F2E756B")[..]).unwrap(); + match dns_name { + GeneralName::DnsName(dns_name) => assert_eq!(dns_name.to_string(), "amazon.co.uk"), + _ => panic!("Failed to parse DnsName from GeneralName"), + } + + // Rfc822Name + let rfc822 = GeneralName::from_der( + &hex!("811B456D61696C5F38303837323037343440746D612E6F73642E6D696C")[..], + ) + .unwrap(); + match rfc822 { + GeneralName::Rfc822Name(rfc822) => { + assert_eq!(rfc822.to_string(), "Email_808720744@tma.osd.mil") + } + _ => panic!("Failed to parse Rfc822Name from GeneralName"), + } + + // OtherName + let bytes = hex!("A021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C"); + match GeneralName::from_der(&bytes).unwrap() { + GeneralName::OtherName(other_name) => { + let onval = other_name.value.utf8_string().unwrap(); + assert_eq!(onval.to_string(), "Upn_214950130@mil"); + } + _ => panic!("Failed to parse OtherName from GeneralName"), + } +} + +#[test] +fn decode_cert() { + // cloned cert with variety of interesting bits, including subject DN encoded backwards, large + // policy mapping set, large policy set (including one with qualifiers), fairly typical set of + // extensions otherwise + let der_encoded_cert = + include_bytes!("examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + let i = exts.iter(); + let mut counter = 0; + for ext in i { + if 0 == 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(); + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku); + + let reencoded = ku.to_vec().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(); + assert_eq!(true, bc.ca); + assert!(bc.path_len_constraint.is_none()); + + let reencoded = bc.to_vec().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(); + assert_eq!(19, pm.0.len()); + + let reencoded = pm.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + + let subject_domain_policy: [&str; 19] = [ + "2.16.840.1.101.3.2.1.48.2", + "2.16.840.1.101.3.2.1.48.2", + "2.16.840.1.101.3.2.1.48.3", + "2.16.840.1.101.3.2.1.48.3", + "2.16.840.1.101.3.2.1.48.5", + "2.16.840.1.101.3.2.1.48.5", + "2.16.840.1.101.3.2.1.48.4", + "2.16.840.1.101.3.2.1.48.4", + "2.16.840.1.101.3.2.1.48.6", + "2.16.840.1.101.3.2.1.48.6", + "2.16.840.1.101.3.2.1.48.78", + "2.16.840.1.101.3.2.1.48.78", + "2.16.840.1.101.3.2.1.48.78", + "2.16.840.1.101.3.2.1.48.79", + "2.16.840.1.101.3.2.1.48.80", + "2.16.840.1.101.3.2.1.48.99", + "2.16.840.1.101.3.2.1.48.99", + "2.16.840.1.101.3.2.1.48.100", + "2.16.840.1.101.3.2.1.48.100", + ]; + + let issuer_domain_policy: [&str; 19] = [ + "2.16.840.1.113839.0.100.2.1", + "2.16.840.1.113839.0.100.2.2", + "2.16.840.1.113839.0.100.3.1", + "2.16.840.1.113839.0.100.3.2", + "2.16.840.1.113839.0.100.14.1", + "2.16.840.1.113839.0.100.14.2", + "2.16.840.1.113839.0.100.12.1", + "2.16.840.1.113839.0.100.12.2", + "2.16.840.1.113839.0.100.15.1", + "2.16.840.1.113839.0.100.15.2", + "2.16.840.1.113839.0.100.18.0", + "2.16.840.1.113839.0.100.18.1", + "2.16.840.1.113839.0.100.18.2", + "2.16.840.1.113839.0.100.19.1", + "2.16.840.1.113839.0.100.20.1", + "2.16.840.1.113839.0.100.37.1", + "2.16.840.1.113839.0.100.37.2", + "2.16.840.1.113839.0.100.38.1", + "2.16.840.1.113839.0.100.38.2", + ]; + + let mut counter_pm = 0; + for mapping in pm.0 { + assert_eq!( + issuer_domain_policy[counter_pm], + mapping.issuer_domain_policy.to_string() + ); + assert_eq!( + subject_domain_policy[counter_pm], + mapping.subject_domain_policy.to_string() + ); + counter_pm += 1; + } + } else if 3 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_CERTIFICATE_POLICIES.to_string() + ); + assert_eq!(ext.critical, false); + let cps = CertificatePolicies::from_der(ext.extn_value).unwrap(); + assert_eq!(19, cps.0.len()); + + let reencoded = cps.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + + let ids: [&str; 19] = [ + "2.16.840.1.113839.0.100.2.1", + "2.16.840.1.113839.0.100.2.2", + "2.16.840.1.113839.0.100.3.1", + "2.16.840.1.113839.0.100.3.2", + "2.16.840.1.113839.0.100.14.1", + "2.16.840.1.113839.0.100.14.2", + "2.16.840.1.113839.0.100.12.1", + "2.16.840.1.113839.0.100.12.2", + "2.16.840.1.113839.0.100.15.1", + "2.16.840.1.113839.0.100.15.2", + "2.16.840.1.113839.0.100.18.0", + "2.16.840.1.113839.0.100.18.1", + "2.16.840.1.113839.0.100.18.2", + "2.16.840.1.113839.0.100.19.1", + "2.16.840.1.113839.0.100.20.1", + "2.16.840.1.113839.0.100.37.1", + "2.16.840.1.113839.0.100.37.2", + "2.16.840.1.113839.0.100.38.1", + "2.16.840.1.113839.0.100.38.2", + ]; + + let mut cp_counter = 0; + for cp in cps.0 { + assert_eq!(ids[cp_counter], cp.policy_identifier.to_string()); + if 18 == cp_counter { + assert!(cp.policy_qualifiers.is_some()); + let pq = cp.policy_qualifiers.unwrap(); + let mut counter_pq = 0; + 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(); + assert_eq!( + "https://secure.identrust.com/certificates/policy/IGC/index.html", + cpsval.to_string() + ); + } else if 1 == counter_pq { + assert_eq!("1.3.6.1.5.5.7.2.2", pqi.policy_qualifier_id.to_string()); + // TODO VisibleString + } + counter_pq += 1; + } + } else { + assert!(cp.policy_qualifiers.is_none()) + } + + cp_counter += 1; + } + } else if 4 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_SUBJECT_KEY_IDENTIFIER.to_string() + ); + assert_eq!(ext.critical, false); + let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!(Length::new(21), skid.0.len()); + assert_eq!( + &hex!("DBD3DEBF0D7B615B32803BC0206CD7AADD39B8ACFF"), + skid.0.as_bytes() + ); + + let reencoded = skid.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + } else if 5 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_CRL_DISTRIBUTION_POINTS.to_string() + ); + assert_eq!(ext.critical, false); + let crl_dps = CrlDistributionPoints::from_der(ext.extn_value).unwrap(); + assert_eq!(2, crl_dps.0.len()); + + let reencoded = crl_dps.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + + let mut crldp_counter = 0; + for crldp in crl_dps.0 { + let dpn = crldp.distribution_point.unwrap(); + if 0 == crldp_counter { + match dpn { + DistributionPointName::FullName(gns) => { + assert_eq!(1, gns.len()); + let gn = gns.get(0).unwrap(); + match gn { + GeneralName::UniformResourceIdentifier(uri) => { + assert_eq!( + "http://crl-pte.identrust.com.test/crl/IGCRootca1.crl", + uri.to_string() + ); + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } + _ => { + panic!("Expected FullName"); + } + } + } else if 1 == crldp_counter { + match dpn { + DistributionPointName::FullName(gns) => { + assert_eq!(1, gns.len()); + let gn = gns.get(0).unwrap(); + match gn { + GeneralName::UniformResourceIdentifier(uri) => { + assert_eq!("ldap://ldap-pte.identrust.com.test/cn%3DIGC%20Root%20CA1%2Co%3DIdenTrust%2Cc%3DUS%3FcertificateRevocationList%3Bbinary", uri.to_string()); + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } + + crldp_counter += 1; + } + } else if 6 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_PE_SUBJECT_INFO_ACCESS.to_string() + ); + assert_eq!(ext.critical, false); + let sias = SubjectInfoAccessSyntax::from_der(ext.extn_value).unwrap(); + assert_eq!(1, sias.0.len()); + + let reencoded = sias.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + + for sia in sias.0 { + assert_eq!("1.3.6.1.5.5.7.48.5", sia.access_method.to_string()); + let gn = sia.access_location; + match gn { + GeneralName::UniformResourceIdentifier(gn) => { + assert_eq!( + "http://http.cite.fpki-lab.gov.test/bridge/caCertsIssuedBytestFBCA.p7c", + gn.to_string() + ); + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } + } else if 7 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_PE_AUTHORITY_INFO_ACCESS.to_string() + ); + assert_eq!(ext.critical, false); + let aias = AuthorityInfoAccessSyntax::from_der(ext.extn_value).unwrap(); + assert_eq!(2, aias.0.len()); + let mut aia_counter = 0; + + let reencoded = aias.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + + for aia in aias.0 { + if 0 == aia_counter { + assert_eq!("1.3.6.1.5.5.7.48.2", aia.access_method.to_string()); + let gn = aia.access_location; + match gn { + GeneralName::UniformResourceIdentifier(gn) => { + assert_eq!( + "http://apps-stg.identrust.com.test/roots/IGCRootca1.p7c", + gn.to_string() + ); + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } else if 1 == aia_counter { + assert_eq!("1.3.6.1.5.5.7.48.1", aia.access_method.to_string()); + let gn = aia.access_location; + match gn { + GeneralName::UniformResourceIdentifier(gn) => { + assert_eq!( + "http://igcrootpte.ocsp.identrust.com.test:8125", + gn.to_string() + ); + } + _ => { + panic!("Expected UniformResourceIdentifier"); + } + } + } + + aia_counter += 1; + } + } else if 8 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_INHIBIT_ANY_POLICY.to_string() + ); + assert_eq!(ext.critical, false); + let iap = InhibitAnyPolicy::from_der(ext.extn_value).unwrap(); + assert_eq!(0, iap.0); + + let reencoded = iap.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + } else if 9 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_AUTHORITY_KEY_IDENTIFIER.to_string() + ); + assert_eq!(ext.critical, false); + let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!( + &hex!("7C4C863AB80BD589870BEDB7E11BBD2A08BB3D23FF"), + akid.key_identifier.unwrap().as_bytes() + ); + + let reencoded = akid.to_vec().unwrap(); + assert_eq!(ext.extn_value, reencoded); + } + + counter += 1; + } + + let der_encoded_cert = include_bytes!("examples/GoodCACert.crt"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + + assert_eq!(cert.tbs_certificate.version, Version::V3); + let target_serial: [u8; 1] = [2]; + assert_eq!( + cert.tbs_certificate.serial_number, + UIntRef::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(), + Tag::Null + ); + assert_eq!( + cert.tbs_certificate.signature.parameters.unwrap().is_null(), + true + ); + + let mut counter = 0; + let i = cert.tbs_certificate.issuer.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "Trust Anchor" + ); + } + counter += 1; + } + } + + assert_eq!( + cert.tbs_certificate + .validity + .not_before + .to_unix_duration() + .as_secs(), + 1262334600 + ); + assert_eq!( + cert.tbs_certificate + .validity + .not_after + .to_unix_duration() + .as_secs(), + 1924936200 + ); + + counter = 0; + let i = cert.tbs_certificate.subject.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "Good CA" + ); + } + counter += 1; + } + } + + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .oid + .to_string(), + "1.2.840.113549.1.1.1" + ); + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .parameters + .unwrap() + .tag(), + Tag::Null + ); + assert_eq!( + cert.tbs_certificate + .subject_public_key_info + .algorithm + .parameters + .unwrap() + .is_null(), + true + ); + + // TODO - parse and compare public key + + counter = 0; + let exts = cert.tbs_certificate.extensions.unwrap(); + let i = exts.iter(); + for ext in i { + if 0 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_AUTHORITY_KEY_IDENTIFIER.to_string() + ); + assert_eq!(ext.critical, false); + let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!( + akid.key_identifier.unwrap().as_bytes(), + &hex!("E47D5FD15C9586082C05AEBE75B665A7D95DA866")[..] + ); + } else if 1 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_SUBJECT_KEY_IDENTIFIER.to_string() + ); + assert_eq!(ext.critical, false); + let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap(); + assert_eq!( + skid.0.as_bytes(), + &hex!("580184241BBC2B52944A3DA510721451F5AF3AC9")[..] + ); + } 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(); + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, ku); + } else if 3 == counter { + assert_eq!( + ext.extn_id.to_string(), + ID_CE_CERTIFICATE_POLICIES.to_string() + ); + assert_eq!(ext.critical, false); + let r = CertificatePolicies::from_der(ext.extn_value); + let cp = r.unwrap(); + let i = cp.0.iter(); + for p in i { + assert_eq!(p.policy_identifier.to_string(), "2.16.840.1.101.3.2.1.48.1"); + } + } 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(); + assert_eq!(bc.ca, true); + assert_eq!(bc.path_len_constraint, Option::None); + } + + counter += 1; + } + assert_eq!( + cert.signature_algorithm.oid.to_string(), + "1.2.840.113549.1.1.11" + ); + assert_eq!( + cert.signature_algorithm.parameters.unwrap().tag(), + Tag::Null + ); + assert_eq!(cert.signature_algorithm.parameters.unwrap().is_null(), true); + + // TODO - parse and compare signature value + + // This cert adds extended key usage and netscape cert type vs above samples + let der_encoded_cert = include_bytes!("examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds extended key usage and name constraints vs above samples + let der_encoded_cert = include_bytes!("examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds logotype (which is unrecognized) vs above samples + let der_encoded_cert = include_bytes!("examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert features an EC key unlike the above samples + let der_encoded_cert = include_bytes!("examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds issuer alt name vs above samples + let der_encoded_cert = include_bytes!("examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds policy constraints vs above samples + let der_encoded_cert = include_bytes!("examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds subject alt name vs above samples + let der_encoded_cert = include_bytes!("examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds subject directory attributes vs above samples + let der_encoded_cert = + include_bytes!("examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds private key usage period (and an unprocessed Entrust extension) vs above samples + let der_encoded_cert = + include_bytes!("examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds OCSP no check vs above samples + let der_encoded_cert = + include_bytes!("examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); + + // This cert adds PIV NACI indicator vs above samples + let der_encoded_cert = + include_bytes!("examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der"); + let result = Certificate::from_der(der_encoded_cert); + let cert: Certificate = result.unwrap(); + let exts = cert.tbs_certificate.extensions.unwrap(); + spin_over_exts(exts); +} + +#[test] +fn decode_idp() { + use der::TagNumber; + + // IDP from 04A8739769B3C090A11DCDFABA3CF33F4BEF21F3.crl in PKITS 2048 in ficam-scvp-testing repo + let idp = IssuingDistributionPoint::from_der(&hex!("30038201FF")).unwrap(); + assert_eq!(idp.only_contains_ca_certs, true); + assert_eq!(idp.only_contains_attribute_certs, false); + assert_eq!(idp.only_contains_user_certs, false); + assert_eq!(idp.indirect_crl, false); + assert!(idp.only_some_reasons.is_none()); + assert!(idp.distribution_point.is_none()); + + let n = + Name::from_der(&hex!("305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C")).unwrap(); + assert_eq!(4, n.0.len()); + + let gn = + GeneralName::from_der(&hex!("A45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C")).unwrap(); + match gn { + GeneralName::DirectoryName(gn) => { + assert_eq!(4, gn.0.len()); + } + _ => {} + } + + let gns = + GeneralNames::from_der(&hex!("305EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C")).unwrap(); + assert_eq!(1, gns.len()); + let gn = gns.get(0).unwrap(); + match gn { + GeneralName::DirectoryName(gn) => { + assert_eq!(4, gn.0.len()); + } + _ => {} + } + + //TODO - fix decode impl (expecting a SEQUENCE despite this being a CHOICE). Sort out FixedTag implementation. + // let dpn = + // DistributionPointName::from_der(&hex!("A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C")).unwrap(); + // match dpn { + // DistributionPointName::FullName(dpn) => { + // assert_eq!(1, dpn.len()); + // let gn = dpn.get(0).unwrap(); + // match gn { + // GeneralName::DirectoryName(gn) => { + // assert_eq!(4, gn.len()); + // } + // _ => {} + // } + // } + // _ => {} + // } + + let dp = + DistributionPoint::from_der(&hex!("3062A060A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C")).unwrap(); + let dpn = dp.distribution_point.unwrap(); + match dpn { + DistributionPointName::FullName(dpn) => { + assert_eq!(1, dpn.len()); + let gn = dpn.get(0).unwrap(); + match gn { + GeneralName::DirectoryName(gn) => { + assert_eq!(4, gn.0.len()); + } + _ => {} + } + } + _ => {} + } + assert!(dp.crl_issuer.is_none()); + assert!(dp.reasons.is_none()); + + // 0 103: SEQUENCE { + // 2 96: [0] { + // 4 94: [0] { + // 6 92: [4] { + // 8 90: SEQUENCE { + // 10 11: SET { + // 12 9: SEQUENCE { + // 14 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 19 2: PrintableString 'US' + // : } + // : } + // 23 31: SET { + // 25 29: SEQUENCE { + // 27 3: OBJECT IDENTIFIER organizationName (2 5 4 10) + // 32 22: PrintableString 'Test Certificates 2017' + // : } + // : } + // 56 28: SET { + // 58 26: SEQUENCE { + // 60 3: OBJECT IDENTIFIER organizationalUnitName (2 5 4 11) + // 65 19: PrintableString 'onlySomeReasons CA3' + // : } + // : } + // 86 12: SET { + // 88 10: SEQUENCE { + // 90 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 95 3: PrintableString 'CRL' + // : } + // : } + // : } + // : } + // : } + // : } + // 100 3: [3] 07 9F 80 + // : } + // IDP from 54B0D2A6F6AA4780771CC4F9F076F623CEB0F57E.crl in PKITS 2048 in ficam-scvp-testing repo + let idp = + IssuingDistributionPoint::from_der(&hex!("3067A060A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C8303079F80")).unwrap(); + assert_eq!(idp.only_contains_ca_certs, false); + assert_eq!(idp.only_contains_attribute_certs, false); + assert_eq!(idp.only_contains_user_certs, false); + assert_eq!(idp.indirect_crl, false); + assert!(idp.only_some_reasons.is_some()); + assert!(idp.distribution_point.is_some()); + + assert_eq!( + Reasons::Unused + | Reasons::AffiliationChanged + | Reasons::Superseded + | Reasons::CessationOfOperation + | Reasons::CertificateHold + | Reasons::PrivilegeWithdrawn + | Reasons::AaCompromise, + idp.only_some_reasons.unwrap() + ); + + // 930 360: SEQUENCE { + // 934 353: [0] { + // 938 349: [0] { + // 942 117: [4] { + // 944 115: SEQUENCE { + // 946 11: SET { + // 948 9: SEQUENCE { + // 950 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 955 2: PrintableString 'US' + // : } + // : } + // 959 31: SET { + // 961 29: SEQUENCE { + // 963 3: OBJECT IDENTIFIER + // : organizationName (2 5 4 10) + // 968 22: PrintableString 'Test Certificates 2017' + // : } + // : } + // 992 24: SET { + // 994 22: SEQUENCE { + // 996 3: OBJECT IDENTIFIER + // : organizationalUnitName (2 5 4 11) + // 1001 15: PrintableString 'indirectCRL CA5' + // : } + // : } + // 1018 41: SET { + // 1020 39: SEQUENCE { + // 1022 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 1027 32: PrintableString 'indirect CRL for indirectCRL CA6' + // : } + // : } + // : } + // : } + // 1061 117: [4] { + // 1063 115: SEQUENCE { + // 1065 11: SET { + // 1067 9: SEQUENCE { + // 1069 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 1074 2: PrintableString 'US' + // : } + // : } + // 1078 31: SET { + // 1080 29: SEQUENCE { + // 1082 3: OBJECT IDENTIFIER + // : organizationName (2 5 4 10) + // 1087 22: PrintableString 'Test Certificates 2017' + // : } + // : } + // 1111 24: SET { + // 1113 22: SEQUENCE { + // 1115 3: OBJECT IDENTIFIER + // : organizationalUnitName (2 5 4 11) + // 1120 15: PrintableString 'indirectCRL CA5' + // : } + // : } + // 1137 41: SET { + // 1139 39: SEQUENCE { + // 1141 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 1146 32: PrintableString 'indirect CRL for indirectCRL CA7' + // : } + // : } + // : } + // : } + // 1180 109: [4] { + // 1182 107: SEQUENCE { + // 1184 11: SET { + // 1186 9: SEQUENCE { + // 1188 3: OBJECT IDENTIFIER countryName (2 5 4 6) + // 1193 2: PrintableString 'US' + // : } + // : } + // 1197 31: SET { + // 1199 29: SEQUENCE { + // 1201 3: OBJECT IDENTIFIER + // : organizationName (2 5 4 10) + // 1206 22: PrintableString 'Test Certificates 2017' + // : } + // : } + // 1230 24: SET { + // 1232 22: SEQUENCE { + // 1234 3: OBJECT IDENTIFIER + // : organizationalUnitName (2 5 4 11) + // 1239 15: PrintableString 'indirectCRL CA5' + // : } + // : } + // 1256 33: SET { + // 1258 31: SEQUENCE { + // 1260 3: OBJECT IDENTIFIER commonName (2 5 4 3) + // 1265 24: PrintableString 'CRL1 for indirectCRL CA5' + // : } + // : } + // : } + // : } + // : } + // : } + // 1291 1: [4] FF + // : } + // : } + // : } + // IDP from 959528526E54B646AF895E2362D3AD20F4B3284D.crl in PKITS 2048 in ficam-scvp-testing repo + let idp = + IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358401FF")).unwrap(); + assert_eq!(idp.only_contains_ca_certs, false); + assert_eq!(idp.only_contains_attribute_certs, false); + assert_eq!(idp.only_contains_user_certs, false); + assert_eq!(idp.indirect_crl, true); + assert!(idp.only_some_reasons.is_none()); + assert!(idp.distribution_point.is_some()); + let dp = idp.distribution_point.unwrap(); + match dp { + DistributionPointName::FullName(dp) => { + assert_eq!(3, dp.len()); + for gn in dp { + match gn { + GeneralName::DirectoryName(gn) => { + assert_eq!(4, gn.0.len()); + } + _ => { + panic!("Expected DirectoryName") + } + } + } + } + _ => { + panic!("Expected FullName") + } + } + + //--------------------------------- + // Negative tests + //--------------------------------- + // Value contains more than length value indicates + let reason_flags = ReasonFlags::from_der(&hex!("0302079F80")); + let err = reason_flags.err().unwrap(); + assert_eq!( + ErrorKind::TrailingData { + decoded: 4u8.into(), + remaining: 1u8.into() + }, + err.kind() + ); + + // Value incomplete relative to length value + let reason_flags = ReasonFlags::from_der(&hex!("0304079F80")); + let err = reason_flags.err().unwrap(); + assert_eq!( + ErrorKind::Incomplete { + expected_len: 6u8.into(), + actual_len: 5u8.into() + }, + err.kind() + ); + + // Value incomplete relative to length value + let idp = + IssuingDistributionPoint::from_der(&hex!("3067A060A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C8304079F80")); + let err = idp.err().unwrap(); + assert_eq!(err.position().unwrap(), 103u8.into()); + assert_eq!( + ErrorKind::Incomplete { + expected_len: 106u8.into(), + actual_len: 105u8.into() + }, + err.kind() + ); + + // Truncated + let reason_flags = ReasonFlags::from_der(&hex!("0303079F")); + let err = reason_flags.err().unwrap(); + assert_eq!( + ErrorKind::Incomplete { + expected_len: 5u8.into(), + actual_len: 4u8.into() + }, + err.kind() + ); + + // Nonsensical tag where BIT STRING tag should be + let reason_flags = ReasonFlags::from_der(&hex!("FF03079F80")); + let err = reason_flags.err().unwrap(); + assert_eq!(ErrorKind::TagNumberInvalid, err.kind()); + + // INTEGER tag where BIT STRING expected + let reason_flags = ReasonFlags::from_der(&hex!("0203079F80")); + let err = reason_flags.err().unwrap(); + assert_eq!( + ErrorKind::TagUnexpected { + expected: Some(Tag::BitString), + actual: Tag::Integer + }, + err.kind() + ); + + // Context specific tag that should be primitive is constructed + let idp = IssuingDistributionPoint::from_der(&hex!("3003A201FF")); + let err = idp.err().unwrap(); + assert_eq!( + ErrorKind::Noncanonical { + tag: Tag::ContextSpecific { + constructed: true, + number: TagNumber::new(2) + } + }, + err.kind() + ); + + // Boolean value is two bytes long + let idp = + IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358402FFFF")); + let err = idp.err().unwrap(); + assert_eq!(ErrorKind::Length { tag: Tag::Boolean }, err.kind()); + + // Boolean value is neither 0x00 nor 0xFF + let idp = + IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C20434135840175")); + let err = idp.err().unwrap(); + assert_eq!(ErrorKind::Noncanonical { tag: Tag::Boolean }, err.kind()); + + // Tag on second RDN in first name is TeletexString (20) instead of PrintableString (19) (and TeletexString is not supported) + let idp = + IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A14165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358401FF")); + let err = idp.err().unwrap(); + assert_eq!(ErrorKind::TagUnknown { byte: 20u8.into() }, err.kind()); + + // Length on second RDN in first name indicates more bytes than are present + let idp = + IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13995465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358401FF")); + let err = idp.err().unwrap(); + assert_eq!( + ErrorKind::Length { + tag: Tag::PrintableString + }, + err.kind() + ); +} diff --git a/tests/trust_anchor_format.rs b/tests/trust_anchor_format.rs new file mode 100644 index 0000000..bcc3e69 --- /dev/null +++ b/tests/trust_anchor_format.rs @@ -0,0 +1,397 @@ +use der::{Decode, Encode, SliceReader}; +use hex_literal::hex; +use x509_cert::anchor::{CertPolicies, TrustAnchorChoice}; +use x509_cert::ext::pkix::name::GeneralName; + +#[test] +fn decode_ta1() { + // features an ECA cert wrapped in a TrustAnchorInfo that contains a pile of certificate policies + // in the cert path controls field + let der_encoded_tac = include_bytes!("examples/eca_policies.ta"); + let der_encoded_cert = include_bytes!("examples/eca.der"); + + let mut decoder = SliceReader::new(der_encoded_tac).unwrap(); + let tac = TrustAnchorChoice::decode(&mut decoder).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("335BA56F7A55602B814B2614CC79BF4ABA8B32BD"), + tai.key_id.as_bytes() + ); + + let policy_ids: [&str; 42] = [ + "1.2.36.1.334.1.2.1.2", + "1.2.840.113549.5.6.1.3.1.12", + "1.2.840.113549.5.6.1.3.1.18", + "1.3.6.1.4.1.103.100.1.1.3.1", + "1.3.6.1.4.1.13948.1.1.1.2", + "1.3.6.1.4.1.13948.1.1.1.6", + "1.3.6.1.4.1.1569.10.1.1", + "1.3.6.1.4.1.1569.10.1.2", + "1.3.6.1.4.1.16304.3.6.2.12", + "1.3.6.1.4.1.16304.3.6.2.20", + "1.3.6.1.4.1.16334.509.2.6", + "1.3.6.1.4.1.23337.1.1.10", + "1.3.6.1.4.1.23337.1.1.8", + "1.3.6.1.4.1.2396.2.1.2", + "1.3.6.1.4.1.2396.2.1.7", + "1.3.6.1.4.1.24019.1.1.1.18", + "1.3.6.1.4.1.24019.1.1.1.19", + "1.3.6.1.4.1.24019.1.1.1.2", + "1.3.6.1.4.1.24019.1.1.1.7", + "1.3.6.1.4.1.73.15.3.1.12", + "1.3.6.1.4.1.73.15.3.1.5", + "2.16.528.1.1003.1.2.5.1", + "2.16.528.1.1003.1.2.5.2", + "2.16.840.1.101.2.1.11.19", + "2.16.840.1.101.3.2.1.12.2", + "2.16.840.1.101.3.2.1.12.3", + "2.16.840.1.101.3.2.1.3.12", + "2.16.840.1.101.3.2.1.3.13", + "2.16.840.1.101.3.2.1.3.16", + "2.16.840.1.101.3.2.1.3.18", + "2.16.840.1.101.3.2.1.3.24", + "2.16.840.1.101.3.2.1.3.4", + "2.16.840.1.101.3.2.1.3.7", + "2.16.840.1.101.3.2.1.5.4", + "2.16.840.1.101.3.2.1.5.5", + "2.16.840.1.101.3.2.1.6.12", + "2.16.840.1.101.3.2.1.6.4", + "2.16.840.1.113733.1.7.23.3.1.18", + "2.16.840.1.113733.1.7.23.3.1.7", + "2.16.840.1.114027.200.3.10.7.2", + "2.16.840.1.114027.200.3.10.7.4", + "2.16.840.1.114027.200.3.10.7.6", + ]; + + let cert_path = tai.cert_path.as_ref().unwrap(); + let mut counter = 0; + let exts = cert_path.policy_set.as_ref().unwrap(); + let i = exts.0.iter(); + for ext in i { + assert_eq!(policy_ids[counter], ext.policy_identifier.to_string()); + counter += 1; + } + + counter = 0; + let i = cert_path.ta_name.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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"); + } else if 3 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.3"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "ECA Root CA 4" + ); + } + counter += 1; + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta2() { + // features an Entrust cert wrapped in a TrustAnchorInfo that contains an excluded subtree in the + // name constraint in the cert path controls field + let der_encoded_tac = include_bytes!("examples/entrust_dnConstraint.ta"); + let der_encoded_cert = include_bytes!("examples/entrust.der"); + + let mut decoder = SliceReader::new(der_encoded_tac).unwrap(); + let tac = TrustAnchorChoice::decode(&mut decoder).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("1A74551E8A85089F505D3E8A46018A819CF99E1E"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + let mut counter = 0; + let i = cert_path.ta_name.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "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(), + "Entrust Managed Services NFI Root CA" + ); + } + counter += 1; + } + } + + let nc = cert_path.name_constr.as_ref().unwrap(); + counter = 0; + let gsi = nc.excluded_subtrees.as_ref().unwrap().iter(); + for gs in gsi { + match &gs.base { + GeneralName::DirectoryName(dn) => { + let i = dn.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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" + ); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "DoD" + ); + } + counter += 1; + } + } + } + _ => panic!("Unexpected GeneralSubtree type"), + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta3() { + // features an Exostar cert wrapped in a TrustAnchorInfo that contains an excluded subtree in the + // name constraint and policy flags in the cert path controls field + let der_encoded_tac = include_bytes!("examples/exostar_policyFlags.ta"); + let der_encoded_cert = include_bytes!("examples/exostar.der"); + + let mut decoder = SliceReader::new(der_encoded_tac).unwrap(); + let tac = TrustAnchorChoice::decode(&mut decoder).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("2EBE91A6776A373CF5FD1DB6DD78C9A6E5F42220"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + assert_eq!( + CertPolicies::InhibitPolicyMapping + | CertPolicies::RequireExplicitPolicy + | CertPolicies::InhibitAnyPolicy, + cert_path.policy_flags.unwrap() + ); + + let mut counter = 0; + let i = cert_path.ta_name.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "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(), + "Exostar Federated Identity Service Root CA 1" + ); + } + counter += 1; + } + } + + let nc = cert_path.name_constr.as_ref().unwrap(); + counter = 0; + let gsi = nc.excluded_subtrees.as_ref().unwrap().iter(); + for gs in gsi { + match &gs.base { + GeneralName::DirectoryName(dn) => { + let i = dn.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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" + ); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().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(), + "DoD" + ); + } + counter += 1; + } + } + } + _ => panic!("Unexpected GeneralSubtree type"), + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta4() { + // features an Exostar cert wrapped in a TrustAnchorInfo that contains path length constraint in + // the cert path controls field + let der_encoded_tac = include_bytes!("examples/raytheon_pathLenConstraint.ta"); + let der_encoded_cert = include_bytes!("examples/raytheon.der"); + + let mut decoder = SliceReader::new(der_encoded_tac).unwrap(); + let tac = TrustAnchorChoice::decode(&mut decoder).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("283086D556154210425CF07B1C11B28389D47920"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + let mut counter = 0; + let i = cert_path.ta_name.0.iter(); + for rdn in i { + let i1 = rdn.0.iter(); + 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"); + } 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"); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!(atav.value.printable_string().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(), + "RaytheonRoot" + ); + } + counter += 1; + } + } + + let pl = cert_path.path_len_constraint.unwrap(); + if 2 != pl { + panic!("Wrong path length constraint"); + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} diff --git a/tests/validity.rs b/tests/validity.rs new file mode 100644 index 0000000..fcdffcd --- /dev/null +++ b/tests/validity.rs @@ -0,0 +1,136 @@ +//! Validity tests + +use der::Encode; +use hex_literal::hex; +use x509_cert::time::Validity; + +#[test] +fn decode_validity() { + // Decode Validity from GoodCACert.crt in NIST's PKITS certificate collection + // 102 30: SEQUENCE { + // 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( + &hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..], + ) + .unwrap(); + + // Decode Validity from InvalidEEnotAfterDateTest6EE.crt in NIST's PKITS certificate collection + // 97 30: SEQUENCE { + // 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( + &hex!("301E170D3130303130313038333030305A170D3131303130313038333030305A")[..], + ) + .unwrap(); + + // Compare to values from https://www.epochconverter.com/ + assert_eq!(val1.not_before.to_unix_duration().as_secs(), 1262334600); + assert_eq!(val1.not_after.to_unix_duration().as_secs(), 1924936200); + assert_eq!( + val1.not_before.to_unix_duration().as_millis(), + 1262334600000 + ); + assert_eq!(val1.not_after.to_unix_duration().as_millis(), 1924936200000); + + assert_eq!(val2.not_before.to_unix_duration().as_secs(), 1262334600); + assert_eq!(val2.not_after.to_unix_duration().as_secs(), 1293870600); + assert_eq!( + val2.not_before.to_unix_duration().as_millis(), + 1262334600000 + ); + assert_eq!(val2.not_after.to_unix_duration().as_millis(), 1293870600000); + + assert_ne!(val1, val2); + assert_eq!(val1, val1); + + // Decode Validity from ValidGeneralizedTimenotAfterDateTest8EE.crt in NIST's PKITS certificate collection + // 97 32: SEQUENCE { + // 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( + &hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..], + ) + .unwrap(); + assert_eq!(val3.not_before.to_unix_duration().as_secs(), 1262334600); + assert_eq!(val3.not_after.to_unix_duration().as_secs(), 2524651260); + assert_eq!( + val3.not_before.to_unix_duration().as_millis(), + 1262334600000 + ); + assert_eq!(val3.not_after.to_unix_duration().as_millis(), 2524651260000); + + assert_ne!(val1, val3); + assert_eq!(val3, val3); + + // Decode Validity from ValidGeneralizedTimenotBeforeDateTest4EE.crt in NIST's PKITS certificate collection + // 97 32: SEQUENCE { + // 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( + &hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..], + ) + .unwrap(); + assert_eq!(val4.not_before.to_unix_duration().as_secs(), 1009886460); + assert_eq!(val4.not_after.to_unix_duration().as_secs(), 1924936200); + assert_eq!( + val4.not_before.to_unix_duration().as_millis(), + 1009886460000 + ); + assert_eq!(val4.not_after.to_unix_duration().as_millis(), 1924936200000); + + assert_ne!(val4, val3); + assert_eq!(val4, val4); +} + +#[test] +fn encode_validity() { + // Decode Validity from GoodCACert.crt in NIST's PKITS certificate collection then reencode + // 102 30: SEQUENCE { + // 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( + &hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..], + ) + .unwrap(); + let b1 = val1.to_vec().unwrap(); + assert_eq!( + b1, + &hex!("301E170D3130303130313038333030305A170D3330313233313038333030305A")[..] + ); + + // Decode Validity from ValidGeneralizedTimenotAfterDateTest8EE.crt in NIST's PKITS certificate collection + // 97 32: SEQUENCE { + // 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( + &hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..], + ) + .unwrap(); + let b3 = val3.to_vec().unwrap(); + assert_eq!( + b3, + &hex!("3020170D3130303130313038333030305A180F32303530303130313132303130305A")[..] + ); + + // Decode Validity from ValidGeneralizedTimenotBeforeDateTest4EE.crt in NIST's PKITS certificate collection + // 97 32: SEQUENCE { + // 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( + &hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..], + ) + .unwrap(); + let b4 = val4.to_vec().unwrap(); + assert_eq!( + b4, + &hex!("3020180F32303032303130313132303130305A170D3330313233313038333030305A")[..] + ); +} |