aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHasini Gunasinghe <hasinitg@google.com>2022-10-05 19:53:13 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-10-05 19:53:13 +0000
commit9fe68a5c615550bf7b2a3a50268cfd3632e1eff0 (patch)
treea3d3d1d923c1bd6c7e1c6c9aa05adb6e20105ecf
parenteb51940f214233e36ff403999045b84e56c50f16 (diff)
parent144d1492237be1c5428418c6537db7d32beb1e36 (diff)
downloadx509-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>
-rw-r--r--.cargo_vcs_info.json6
-rw-r--r--Android.bp25
-rw-r--r--CHANGELOG.md8
-rw-r--r--Cargo.toml74
-rw-r--r--Cargo.toml.orig34
l---------LICENSE1
-rw-r--r--LICENSE-APACHE201
-rw-r--r--METADATA20
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS1
-rw-r--r--README.md53
-rw-r--r--cargo2android.json9
-rw-r--r--patches/std.diff15
-rw-r--r--src/anchor.rs139
-rw-r--r--src/attr.rs250
-rw-r--r--src/certificate.rs157
-rw-r--r--src/crl.rs86
-rw-r--r--src/ext.rs44
-rw-r--r--src/ext/pkix.rs117
-rw-r--r--src/ext/pkix/access.rs58
-rw-r--r--src/ext/pkix/authkeyid.rs36
-rw-r--r--src/ext/pkix/certpolicy.rs126
-rw-r--r--src/ext/pkix/constraints.rs10
-rw-r--r--src/ext/pkix/constraints/basic.rs24
-rw-r--r--src/ext/pkix/constraints/name.rs68
-rw-r--r--src/ext/pkix/constraints/policy.rs26
-rw-r--r--src/ext/pkix/crl.rs116
-rw-r--r--src/ext/pkix/crl/dp.rs125
-rw-r--r--src/ext/pkix/keyusage.rs105
-rw-r--r--src/ext/pkix/name.rs13
-rw-r--r--src/ext/pkix/name/dirstr.rs49
-rw-r--r--src/ext/pkix/name/dp.rs24
-rw-r--r--src/ext/pkix/name/ediparty.rs36
-rw-r--r--src/ext/pkix/name/general.rs63
-rw-r--r--src/ext/pkix/name/other.rs37
-rw-r--r--src/ext/pkix/policymap.rs39
-rw-r--r--src/lib.rs38
-rw-r--r--src/macros.rs73
-rw-r--r--src/name.rs169
-rw-r--r--src/request.rs106
-rw-r--r--src/time.rs146
-rw-r--r--tests/certificate.rs351
-rw-r--r--tests/certreq.rs86
-rw-r--r--tests/crl.rs17
-rw-r--r--tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.derbin0 -> 2832 bytes
-rw-r--r--tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.derbin0 -> 1195 bytes
-rw-r--r--tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.derbin0 -> 807 bytes
-rw-r--r--tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.derbin0 -> 3976 bytes
-rw-r--r--tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.derbin0 -> 1582 bytes
-rw-r--r--tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.derbin0 -> 929 bytes
-rw-r--r--tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.derbin0 -> 1171 bytes
-rw-r--r--tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.derbin0 -> 1295 bytes
-rw-r--r--tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.derbin0 -> 1783 bytes
-rw-r--r--tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.derbin0 -> 1174 bytes
-rw-r--r--tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.derbin0 -> 1852 bytes
-rw-r--r--tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.derbin0 -> 1752 bytes
-rw-r--r--tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.derbin0 -> 2050 bytes
-rw-r--r--tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.derbin0 -> 1650 bytes
-rw-r--r--tests/examples/GoodCACRL.crlbin0 -> 516 bytes
-rw-r--r--tests/examples/GoodCACert.crtbin0 -> 896 bytes
-rw-r--r--tests/examples/amazon.derbin0 -> 2245 bytes
-rw-r--r--tests/examples/amazon.pem49
-rw-r--r--tests/examples/eca.derbin0 -> 859 bytes
-rwxr-xr-xtests/examples/eca_policies.tabin0 -> 1954 bytes
-rw-r--r--tests/examples/entrust.derbin0 -> 936 bytes
-rwxr-xr-xtests/examples/entrust_dnConstraint.tabin0 -> 1443 bytes
-rw-r--r--tests/examples/exostar.derbin0 -> 972 bytes
-rwxr-xr-xtests/examples/exostar_policyFlags.tabin0 -> 1495 bytes
-rw-r--r--tests/examples/raytheon.derbin0 -> 907 bytes
-rw-r--r--tests/examples/raytheon_pathLenConstraint.tabin0 -> 1324 bytes
-rw-r--r--tests/examples/rsa2048-crt.derbin0 -> 928 bytes
-rw-r--r--tests/examples/rsa2048-crt.pem22
-rw-r--r--tests/examples/rsa2048-csr.derbin0 -> 781 bytes
-rw-r--r--tests/examples/rsa2048-csr.pem19
-rw-r--r--tests/examples/rsa2048-prv.derbin0 -> 1191 bytes
-rw-r--r--tests/examples/rsa2048-prv.pem27
-rw-r--r--tests/examples/tscpbcasha256.crlbin0 -> 660 bytes
-rw-r--r--tests/general_name.rs80
-rw-r--r--tests/name.rs336
-rw-r--r--tests/pkix_extensions.rs1110
-rw-r--r--tests/trust_anchor_format.rs397
-rw-r--r--tests/validity.rs136
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"]
diff --git a/LICENSE b/LICENSE
new file mode 120000
index 0000000..6b579aa
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..45dc4dd
--- /dev/null
+++ b/OWNERS
@@ -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
new file mode 100644
index 0000000..d6cda5a
--- /dev/null
+++ b/tests/examples/026EDA6FA1EDFA8C253936C75B5EEBD954BFF452.fake.der
Binary files differ
diff --git a/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der b/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der
new file mode 100644
index 0000000..08db364
--- /dev/null
+++ b/tests/examples/085B1E2F40254F9C7A2387BE9FF4EC116C326E10.fake.der
Binary files differ
diff --git a/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der b/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der
new file mode 100644
index 0000000..63dbd46
--- /dev/null
+++ b/tests/examples/0954e2343dd5efe0a7f0967d69caf33e5f893720.der
Binary files differ
diff --git a/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der b/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der
new file mode 100644
index 0000000..bcdf79c
--- /dev/null
+++ b/tests/examples/0fcc78fbbca9f32b08b19b032b84f2c86a128f35.der
Binary files differ
diff --git a/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der b/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der
new file mode 100644
index 0000000..d94106f
--- /dev/null
+++ b/tests/examples/15b05c4865410c6b3ff76a4e8f3d87276756bd0c.der
Binary files differ
diff --git a/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der b/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der
new file mode 100644
index 0000000..a0fba41
--- /dev/null
+++ b/tests/examples/16ee54e48c76eaa1052e09010d8faefee95e5ebb.der
Binary files differ
diff --git a/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der b/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der
new file mode 100644
index 0000000..24bdee8
--- /dev/null
+++ b/tests/examples/2049a5b28f104b2c6e1a08546f9cfc0353d6fd30.der
Binary files differ
diff --git a/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der b/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der
new file mode 100644
index 0000000..8f48e0d
--- /dev/null
+++ b/tests/examples/21723e7a0fb61a0bd4a29879b82a02b2fb4ad096.der
Binary files differ
diff --git a/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der b/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der
new file mode 100644
index 0000000..ae22bd4
--- /dev/null
+++ b/tests/examples/284A0A3A9B56DD752DAA2E09E2FADEDB858D9338.fake.der
Binary files differ
diff --git a/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der b/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der
new file mode 100644
index 0000000..0aef65d
--- /dev/null
+++ b/tests/examples/28879DABB0FD11618FB74E47BE049D2933866D53.fake.der
Binary files differ
diff --git a/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der b/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der
new file mode 100644
index 0000000..62e8f41
--- /dev/null
+++ b/tests/examples/288C8BCFEE6B89D110DAE2C9873897BF7FF53382.fake.der
Binary files differ
diff --git a/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der b/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der
new file mode 100644
index 0000000..936b06a
--- /dev/null
+++ b/tests/examples/33D8388EDA1E92475EF19075D2EF4093D1CB6F7F.fake.der
Binary files differ
diff --git a/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der b/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der
new file mode 100644
index 0000000..750c085
--- /dev/null
+++ b/tests/examples/342cd9d3062da48c346965297f081ebc2ef68fdc.der
Binary files differ
diff --git a/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der b/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der
new file mode 100644
index 0000000..5231873
--- /dev/null
+++ b/tests/examples/554D5FF11DA613A155584D8D4AA07F67724D8077.fake.der
Binary files differ
diff --git a/tests/examples/GoodCACRL.crl b/tests/examples/GoodCACRL.crl
new file mode 100644
index 0000000..d46110c
--- /dev/null
+++ b/tests/examples/GoodCACRL.crl
Binary files differ
diff --git a/tests/examples/GoodCACert.crt b/tests/examples/GoodCACert.crt
new file mode 100644
index 0000000..edbfa64
--- /dev/null
+++ b/tests/examples/GoodCACert.crt
Binary files differ
diff --git a/tests/examples/amazon.der b/tests/examples/amazon.der
new file mode 100644
index 0000000..cc30bd6
--- /dev/null
+++ b/tests/examples/amazon.der
Binary files differ
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
new file mode 100644
index 0000000..fdd35d5
--- /dev/null
+++ b/tests/examples/eca.der
Binary files differ
diff --git a/tests/examples/eca_policies.ta b/tests/examples/eca_policies.ta
new file mode 100755
index 0000000..717364a
--- /dev/null
+++ b/tests/examples/eca_policies.ta
Binary files differ
diff --git a/tests/examples/entrust.der b/tests/examples/entrust.der
new file mode 100644
index 0000000..dbddf49
--- /dev/null
+++ b/tests/examples/entrust.der
Binary files differ
diff --git a/tests/examples/entrust_dnConstraint.ta b/tests/examples/entrust_dnConstraint.ta
new file mode 100755
index 0000000..ff784dd
--- /dev/null
+++ b/tests/examples/entrust_dnConstraint.ta
Binary files differ
diff --git a/tests/examples/exostar.der b/tests/examples/exostar.der
new file mode 100644
index 0000000..d2047e4
--- /dev/null
+++ b/tests/examples/exostar.der
Binary files differ
diff --git a/tests/examples/exostar_policyFlags.ta b/tests/examples/exostar_policyFlags.ta
new file mode 100755
index 0000000..46e5f92
--- /dev/null
+++ b/tests/examples/exostar_policyFlags.ta
Binary files differ
diff --git a/tests/examples/raytheon.der b/tests/examples/raytheon.der
new file mode 100644
index 0000000..9b57743
--- /dev/null
+++ b/tests/examples/raytheon.der
Binary files differ
diff --git a/tests/examples/raytheon_pathLenConstraint.ta b/tests/examples/raytheon_pathLenConstraint.ta
new file mode 100644
index 0000000..eb0fd7f
--- /dev/null
+++ b/tests/examples/raytheon_pathLenConstraint.ta
Binary files differ
diff --git a/tests/examples/rsa2048-crt.der b/tests/examples/rsa2048-crt.der
new file mode 100644
index 0000000..6f46ef1
--- /dev/null
+++ b/tests/examples/rsa2048-crt.der
Binary files differ
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
new file mode 100644
index 0000000..31c9e22
--- /dev/null
+++ b/tests/examples/rsa2048-csr.der
Binary files differ
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
new file mode 100644
index 0000000..f5c6adc
--- /dev/null
+++ b/tests/examples/rsa2048-prv.der
Binary files differ
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
new file mode 100644
index 0000000..e7fe379
--- /dev/null
+++ b/tests/examples/tscpbcasha256.crl
Binary files differ
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")[..]
+ );
+}