x509-cert-0.2.5/.cargo_vcs_info.json0000644000000001470000000000100125020ustar { "git": { "sha1": "6215a53dba1d2e919287b6d38fd09bd8ca3f7761" }, "path_in_vcs": "x509-cert" }x509-cert-0.2.5/CHANGELOG.md000064400000000000000000000134551046102023000131110ustar 00000000000000# 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.2.5 (2023-12-23) ### Added - Signed Certificate Timestamp (SCT) extension support ([#1134]) ### Changed - Relax dependencies ([#1179]) - Use `SubjectPublicKeyInfoOwned::from_key` ([#1271]) [#1134]: https://github.com/RustCrypto/formats/pull/1134 [#1179]: https://github.com/RustCrypto/formats/pull/1179 [#1271]: https://github.com/RustCrypto/formats/pull/1271 ## 0.2.4 (2023-07-25) ### Added - `add_attribute` to `RequestBuilder` ([#1137]) ### Changed - bump `serde_json` from 1.0.97 to 1.0.99 ([#1122]) - use the shortest name when looking attr OID ([#1130]) - bump `serde_json` from 1.0.100 to 1.0.103 ([#1158]) ### Fixed - RDN string representation ([#1126]) - `Arbitrary` for certificates ([#1150]) [#1122]: https://github.com/RustCrypto/formats/pull/1122 [#1126]: https://github.com/RustCrypto/formats/pull/1126 [#1130]: https://github.com/RustCrypto/formats/pull/1130 [#1137]: https://github.com/RustCrypto/formats/pull/1137 [#1150]: https://github.com/RustCrypto/formats/pull/1150 [#1158]: https://github.com/RustCrypto/formats/pull/1158 ## 0.2.3 (2023-05-30) ### Added - Added `TryFrom` for `RelativeDistinguishedName` ([#1092]) - Load a chain of certificates from a slice ([#1081]) [#1092]: https://github.com/RustCrypto/formats/pull/1092 [#1081]: https://github.com/RustCrypto/formats/pull/1081 ## 0.2.2 (2023-05-19) ### Added - Certificate builder ([#764]) - Support for `RandomizedSigner` in builder ([#1007]) - Provide parsing profiles ([#987]) - Support for `Time::INFINITY` ([#1024]) - Conversion from `std::net::IpAddr` ([#1035]) - `CertReq` builder ([#1034]) - missing extension implementations ([#1050]) - notes about `UTCTime` range being 1970-2049 ([#1052]) - consume the `SignatureBitStringEncoding` trait ([#1048]) ### Changed - use `ErrorKind::Value` for overlength serial ([#988]) - Bump `hex-literal` to v0.4.1 ([#999]) - Builder updates ([#1001]) - better debug info when `zlint` isn't installed ([#1018]) - make SKI optional in leaf certificate ([#1028]) - bump rsa from 0.9.0-pre.2 to 0.9.0 ([#1033]) - bump rsa from 0.9.1 to 0.9.2 ([#1056]) ### Fixed - fix `KeyUsage` bit tests ([#993]) - extraneous PhantomData in `TbsCertificate` ([#1017]) - CI flakiness ([#1042]) - usage of ecdsa signer ([#1043]) [#764]: https://github.com/RustCrypto/formats/pull/764 [#987]: https://github.com/RustCrypto/formats/pull/987 [#988]: https://github.com/RustCrypto/formats/pull/988 [#993]: https://github.com/RustCrypto/formats/pull/993 [#999]: https://github.com/RustCrypto/formats/pull/999 [#1001]: https://github.com/RustCrypto/formats/pull/1001 [#1007]: https://github.com/RustCrypto/formats/pull/1007 [#1017]: https://github.com/RustCrypto/formats/pull/1017 [#1018]: https://github.com/RustCrypto/formats/pull/1018 [#1024]: https://github.com/RustCrypto/formats/pull/1024 [#1028]: https://github.com/RustCrypto/formats/pull/1028 [#1033]: https://github.com/RustCrypto/formats/pull/1033 [#1034]: https://github.com/RustCrypto/formats/pull/1034 [#1035]: https://github.com/RustCrypto/formats/pull/1035 [#1042]: https://github.com/RustCrypto/formats/pull/1042 [#1043]: https://github.com/RustCrypto/formats/pull/1043 [#1048]: https://github.com/RustCrypto/formats/pull/1048 [#1050]: https://github.com/RustCrypto/formats/pull/1050 [#1052]: https://github.com/RustCrypto/formats/pull/1052 [#1056]: https://github.com/RustCrypto/formats/pull/1056 ## 0.2.1 (2023-03-26) ### Added - `FromStr` impls for `RdnSequence` (`Name`), `RelativeDistinguishedName`, and `AttributeTypeAndValue` ([#949]) ### Changed - Deprecate `encode_from_string` functions ([#951]) [#949]: https://github.com/RustCrypto/formats/pull/949 [#951]: https://github.com/RustCrypto/formats/pull/951 ## 0.2.0 (2023-03-18) ### Added - Feature-gated `Arbitrary` impl for `Certificate` ([#761]) - Allow request to be serialized to PEM ([#819]) - `Display` impl for `SerialNumber` ([#820]) - `std` feature implies `const-oid/std` ([#874]) ### Changed - Serial numbers are formatted as `PrintableString` ([#794]) - `SerialNumber` is now a specialized object ([#795]) - MSRV 1.65 ([#805]) - Make types owned instead of reference-based ([#806], [#841]) - Bump `der` to v0.7 ([#899]) - Bump `spki` to v0.7 ([#900]) ### Fixed - Handling of negative serial numbers ([#823], [#831]) ### Removed - `alloc` feature: now unconditionally required ([#841]) [#761]: https://github.com/RustCrypto/formats/pull/761 [#794]: https://github.com/RustCrypto/formats/pull/794 [#795]: https://github.com/RustCrypto/formats/pull/795 [#805]: https://github.com/RustCrypto/formats/pull/805 [#806]: https://github.com/RustCrypto/formats/pull/806 [#819]: https://github.com/RustCrypto/formats/pull/819 [#820]: https://github.com/RustCrypto/formats/pull/820 [#823]: https://github.com/RustCrypto/formats/pull/823 [#831]: https://github.com/RustCrypto/formats/pull/831 [#841]: https://github.com/RustCrypto/formats/pull/841 [#874]: https://github.com/RustCrypto/formats/pull/874 [#899]: https://github.com/RustCrypto/formats/pull/899 [#900]: https://github.com/RustCrypto/formats/pull/900 ## 0.1.1 (2022-12-10) ### Added - Support `TeletexString` in `DirectoryString` ([#692]) - Re-export `spki` ([#701]) - `PemLabel` impl for `Certificate` ([#763]) - `ValueOrd` impl for `Version` and other derived types ([#723]) ### Fixed - `countryName` should always be `PrintableString` ([#760]) [#692]: https://github.com/RustCrypto/formats/pull/692 [#701]: https://github.com/RustCrypto/formats/pull/701 [#723]: https://github.com/RustCrypto/formats/pull/723 [#760]: https://github.com/RustCrypto/formats/pull/760 [#763]: https://github.com/RustCrypto/formats/pull/763 ## 0.1.0 (2022-07-23) - Initial release x509-cert-0.2.5/Cargo.toml0000644000000045430000000000100105040ustar # 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.65" name = "x509-cert" version = "0.2.5" 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-cert" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.arbitrary] version = "1.3" features = ["derive"] optional = true [dependencies.const-oid] version = "0.9.3" features = ["db"] [dependencies.der] version = "0.7.6" features = [ "alloc", "derive", "flagset", "oid", ] [dependencies.sha1] version = "0.10.6" optional = true [dependencies.signature] version = "2.1.0" features = ["rand_core"] optional = true [dependencies.spki] version = "0.7.3" features = ["alloc"] [dependencies.tls_codec] version = "0.4.0" features = ["derive"] optional = true default-features = false [dev-dependencies.ecdsa] version = "0.16.8" features = [ "digest", "pem", ] [dev-dependencies.hex-literal] version = "0.4" [dev-dependencies.p256] version = "0.13.0" [dev-dependencies.rand] version = "0.8.5" [dev-dependencies.rsa] version = "0.9.6" features = ["sha2"] [dev-dependencies.rstest] version = "0.18" [dev-dependencies.sha2] version = "0.10" features = ["oid"] [dev-dependencies.tempfile] version = "3.5.0" [features] arbitrary = [ "dep:arbitrary", "std", "der/arbitrary", "spki/arbitrary", ] builder = [ "std", "sha1/default", "signature", ] default = [ "pem", "std", ] hazmat = [] pem = [ "der/pem", "spki/pem", ] sct = ["dep:tls_codec"] std = [ "const-oid/std", "der/std", "spki/std", "tls_codec?/std", ] x509-cert-0.2.5/Cargo.toml.orig000064400000000000000000000032161046102023000141610ustar 00000000000000[package] name = "x509-cert" version = "0.2.5" 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-cert" categories = ["cryptography", "data-structures", "encoding", "no-std"] keywords = ["crypto"] readme = "README.md" edition = "2021" rust-version = "1.65" [dependencies] const-oid = { version = "0.9.3", features = ["db"] } der = { version = "0.7.6", features = ["alloc", "derive", "flagset", "oid"] } spki = { version = "0.7.3", features = ["alloc"] } # optional dependencies arbitrary = { version = "1.3", features = ["derive"], optional = true } sha1 = { version = "0.10.6", optional = true } signature = { version = "2.1.0", features = ["rand_core"], optional = true } tls_codec = { version = "0.4.0", default-features = false, features = ["derive"], optional = true } [dev-dependencies] hex-literal = "0.4" rand = "0.8.5" rsa = { version = "0.9.6", features = ["sha2"] } ecdsa = { version = "0.16.8", features = ["digest", "pem"] } p256 = "0.13.0" rstest = "0.18" sha2 = { version = "0.10", features = ["oid"] } tempfile = "3.5.0" x509-cert-test-support = { path = "./test-support" } [features] default = ["pem", "std"] std = ["const-oid/std", "der/std", "spki/std", "tls_codec?/std"] arbitrary = ["dep:arbitrary", "std", "der/arbitrary", "spki/arbitrary"] builder = ["std", "sha1/default", "signature"] hazmat = [] pem = ["der/pem", "spki/pem"] sct = ["dep:tls_codec"] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] x509-cert-0.2.5/LICENSE-APACHE000064400000000000000000000251411046102023000132170ustar 00000000000000 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. x509-cert-0.2.5/LICENSE-MIT000064400000000000000000000020651046102023000127270ustar 00000000000000Copyright (c) 2021 The RustCrypto Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. x509-cert-0.2.5/README.md000064400000000000000000000034131046102023000125500ustar 00000000000000# [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.65** 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.65+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats [//]: # (links) [RustCrypto]: https://github.com/rustcrypto [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 x509-cert-0.2.5/src/anchor.rs000064400000000000000000000103031046102023000136740ustar 00000000000000//! 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 alloc::string::String; use der::asn1::OctetString; use der::flagset::{flags, FlagSet}; use der::{Choice, Enumerated, Sequence}; use spki::SubjectPublicKeyInfoOwned; /// Version identifier for TrustAnchorInfo #[derive(Clone, Debug, Default, Copy, PartialEq, Eq, Enumerated)] #[asn1(type = "INTEGER")] #[repr(u8)] pub enum Version { /// Version 1 (default) #[default] V1 = 0, } /// ```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 { #[asn1(default = "Default::default")] pub version: Version, pub pub_key: SubjectPublicKeyInfoOwned, pub key_id: OctetString, #[asn1(optional = "true")] pub ta_title: Option, #[asn1(optional = "true")] pub cert_path: Option, #[asn1(context_specific = "1", tag_mode = "EXPLICIT", optional = "true")] pub extensions: Option, #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] pub ta_title_lang_tag: Option, } /// ```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 { pub ta_name: Name, #[asn1(context_specific = "0", tag_mode = "IMPLICIT", optional = "true")] pub certificate: Option, #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] pub policy_set: Option, #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] pub policy_flags: Option, #[asn1(context_specific = "3", tag_mode = "IMPLICIT", optional = "true")] pub name_constr: Option, #[asn1(context_specific = "4", tag_mode = "IMPLICIT", optional = "true")] pub path_len_constraint: Option, } 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; /// ```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 { Certificate(Certificate), #[asn1(context_specific = "1", tag_mode = "EXPLICIT", constructed = "true")] TbsCertificate(TbsCertificate), #[asn1(context_specific = "2", tag_mode = "EXPLICIT", constructed = "true")] TaInfo(TrustAnchorInfo), } x509-cert-0.2.5/src/attr.rs000064400000000000000000000230051046102023000133770ustar 00000000000000//! Attribute-related definitions as defined in X.501 (and updated by RFC 5280). use alloc::vec::Vec; use const_oid::db::{ rfc4519::{COUNTRY_NAME, DOMAIN_COMPONENT, SERIAL_NUMBER}, Database, DB, }; use core::{ fmt::{self, Write}, str::FromStr, }; use der::{ asn1::{ Any, Ia5StringRef, ObjectIdentifier, PrintableStringRef, SetOfVec, TeletexStringRef, Utf8StringRef, }, Decode, Encode, Error, ErrorKind, Sequence, Tag, Tagged, ValueOrd, }; /// X.501 `AttributeType` as defined in [RFC 5280 Appendix A.1]. /// /// ```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 = Any; /// 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 { pub oid: AttributeType, pub values: SetOfVec, } /// 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 = SetOfVec; /// 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 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)] #[allow(missing_docs)] pub struct AttributeTypeAndValue { pub oid: AttributeType, pub value: AttributeValue, } #[derive(Copy, Clone)] enum Escape { None, Some, Hex(u8), } struct Parser { state: Escape, bytes: Vec, } 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 from_hex(oid: ObjectIdentifier, val: &str) -> Result { // 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); } Ok(Self { oid, value: Any::from_der(&bytes)?, }) } /// Parses the string value in the `NAME=STRING` format. fn from_delimited_str(oid: ObjectIdentifier, val: &str) -> Result { // Undo escaping. let mut parser = Parser::new(); for c in val.bytes() { parser.add(c)?; } let tag = match oid { COUNTRY_NAME => Tag::PrintableString, DOMAIN_COMPONENT => Tag::Ia5String, // Serial numbers are formatted as Printable String as per RFC 5280 Appendix A.1: // https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1 SERIAL_NUMBER => Tag::PrintableString, _ => Tag::Utf8String, }; Ok(Self { oid, value: Any::new(tag, parser.as_bytes())?, }) } /// 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 #[deprecated( since = "0.2.1", note = "use AttributeTypeAndValue::from_str(...)?.to_der()" )] pub fn encode_from_string(s: &str) -> Result, Error> { Self::from_str(s)?.to_der() } } /// Parse an [`AttributeTypeAndValue`] string. /// /// This function follows the rules in [RFC 4514]. /// /// [RFC 4514]: https://datatracker.ietf.org/doc/html/rfc4514 impl FromStr for AttributeTypeAndValue { type Err = Error; fn from_str(s: &str) -> der::Result { 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::from_hex(oid, val), None => Self::from_delimited_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 => PrintableStringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), Tag::Utf8String => Utf8StringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), Tag::Ia5String => Ia5StringRef::try_from(&self.value).ok().map(|s| s.as_str()), Tag::TeletexString => TeletexStringRef::try_from(&self.value) .ok() .map(|s| s.as_str()), _ => None, }; if let (Some(key), Some(val)) = (DB.shortest_name_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_der().or(Err(fmt::Error))?; write!(f, "{}=#", self.oid)?; for c in value { write!(f, "{:02x}", c)?; } } Ok(()) } } /// Helper trait to bring shortest name by oid lookups to Database trait ShortestName { fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&str>; } impl<'a> ShortestName for Database<'a> { fn shortest_name_by_oid(&self, oid: &ObjectIdentifier) -> Option<&'a str> { let mut best_match: Option<&'a str> = None; for m in self.find_names_for_oid(*oid) { if let Some(previous) = best_match { if m.len() < previous.len() { best_match = Some(m); } } else { best_match = Some(m); } } best_match } } x509-cert-0.2.5/src/builder.rs000064400000000000000000000374371046102023000140710ustar 00000000000000//! X509 Certificate builder use alloc::vec; use core::fmt; use der::{asn1::BitString, referenced::OwnedToRef, Encode}; use signature::{rand_core::CryptoRngCore, Keypair, RandomizedSigner, Signer}; use spki::{ DynSignatureAlgorithmIdentifier, EncodePublicKey, SignatureBitStringEncoding, SubjectPublicKeyInfoOwned, SubjectPublicKeyInfoRef, }; use crate::{ certificate::{Certificate, TbsCertificate, Version}, ext::{ pkix::{ AuthorityKeyIdentifier, BasicConstraints, KeyUsage, KeyUsages, SubjectKeyIdentifier, }, AsExtension, Extension, Extensions, }, name::Name, request::{attributes::AsAttribute, CertReq, CertReqInfo, ExtensionReq}, serial_number::SerialNumber, time::Validity, }; /// Error type #[derive(Debug)] #[non_exhaustive] pub enum Error { /// ASN.1 DER-related errors. Asn1(der::Error), /// Public key errors propagated from the [`spki::Error`] type. PublicKey(spki::Error), /// Signing error propagated for the [`signature::Error`] type. Signature(signature::Error), } #[cfg(feature = "std")] impl std::error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Asn1(err) => write!(f, "ASN.1 error: {}", err), Error::PublicKey(err) => write!(f, "public key error: {}", err), Error::Signature(err) => write!(f, "signature error: {}", err), } } } impl From for Error { fn from(err: der::Error) -> Error { Error::Asn1(err) } } impl From for Error { fn from(err: spki::Error) -> Error { Error::PublicKey(err) } } impl From for Error { fn from(err: signature::Error) -> Error { Error::Signature(err) } } type Result = core::result::Result; /// The type of certificate to build #[derive(Clone, Debug, Eq, PartialEq)] pub enum Profile { /// Build a root CA certificate Root, /// Build an intermediate sub CA certificate SubCA { /// issuer Name, /// represents the name signing the certificate issuer: Name, /// pathLenConstraint INTEGER (0..MAX) OPTIONAL /// BasicConstraints as defined in [RFC 5280 Section 4.2.1.9]. path_len_constraint: Option, }, /// Build an end certificate Leaf { /// issuer Name, /// represents the name signing the certificate issuer: Name, /// should the key agreement flag of KeyUsage be enabled enable_key_agreement: bool, /// should the key encipherment flag of KeyUsage be enabled enable_key_encipherment: bool, /// should the subject key identifier extension be included /// /// From [RFC 5280 Section 4.2.1.2]: /// For end entity certificates, subject key identifiers SHOULD be /// derived from the public key. Two common methods for generating key /// identifiers from the public key are identified above. #[cfg(feature = "hazmat")] include_subject_key_identifier: bool, }, #[cfg(feature = "hazmat")] /// Opt-out of the default extensions Manual { /// issuer Name, /// represents the name signing the certificate /// A `None` will make it a self-signed certificate issuer: Option, }, } impl Profile { fn get_issuer(&self, subject: &Name) -> Name { match self { Profile::Root => subject.clone(), Profile::SubCA { issuer, .. } => issuer.clone(), Profile::Leaf { issuer, .. } => issuer.clone(), #[cfg(feature = "hazmat")] Profile::Manual { issuer, .. } => issuer.as_ref().unwrap_or(subject).clone(), } } fn build_extensions( &self, spk: SubjectPublicKeyInfoRef<'_>, issuer_spk: SubjectPublicKeyInfoRef<'_>, tbs: &TbsCertificate, ) -> Result> { #[cfg(feature = "hazmat")] // User opted out of default extensions set. if let Profile::Manual { .. } = self { return Ok(vec::Vec::default()); } let mut extensions: vec::Vec = vec::Vec::new(); match self { #[cfg(feature = "hazmat")] Profile::Leaf { include_subject_key_identifier: false, .. } => {} _ => extensions.push( SubjectKeyIdentifier::try_from(spk)?.to_extension(&tbs.subject, &extensions)?, ), } // Build Authority Key Identifier match self { Profile::Root => {} _ => { extensions.push( AuthorityKeyIdentifier::try_from(issuer_spk.clone())? .to_extension(&tbs.subject, &extensions)?, ); } } // Build Basic Contraints extensions extensions.push(match self { Profile::Root => BasicConstraints { ca: true, path_len_constraint: None, } .to_extension(&tbs.subject, &extensions)?, Profile::SubCA { path_len_constraint, .. } => BasicConstraints { ca: true, path_len_constraint: *path_len_constraint, } .to_extension(&tbs.subject, &extensions)?, Profile::Leaf { .. } => BasicConstraints { ca: false, path_len_constraint: None, } .to_extension(&tbs.subject, &extensions)?, #[cfg(feature = "hazmat")] Profile::Manual { .. } => unreachable!(), }); // Build Key Usage extension match self { Profile::Root | Profile::SubCA { .. } => { extensions.push( KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign) .to_extension(&tbs.subject, &extensions)?, ); } Profile::Leaf { enable_key_agreement, enable_key_encipherment, .. } => { let mut key_usage = KeyUsages::DigitalSignature | KeyUsages::NonRepudiation; if *enable_key_encipherment { key_usage |= KeyUsages::KeyEncipherment; } if *enable_key_agreement { key_usage |= KeyUsages::KeyAgreement; } extensions.push(KeyUsage(key_usage).to_extension(&tbs.subject, &extensions)?); } #[cfg(feature = "hazmat")] Profile::Manual { .. } => unreachable!(), } Ok(extensions) } } /// X509 Certificate builder /// /// ``` /// use der::Decode; /// use x509_cert::spki::SubjectPublicKeyInfoOwned; /// use x509_cert::builder::{CertificateBuilder, Profile}; /// use x509_cert::name::Name; /// use x509_cert::serial_number::SerialNumber; /// use x509_cert::time::Validity; /// use std::str::FromStr; /// /// # const RSA_2048_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-pub.der"); /// # const RSA_2048_PRIV_DER: &[u8] = include_bytes!("../tests/examples/rsa2048-priv.der"); /// # use rsa::{pkcs1v15::SigningKey, pkcs1::DecodeRsaPrivateKey}; /// # use sha2::Sha256; /// # use std::time::Duration; /// # use der::referenced::RefToOwned; /// # fn rsa_signer() -> SigningKey { /// # let private_key = rsa::RsaPrivateKey::from_pkcs1_der(RSA_2048_PRIV_DER).unwrap(); /// # let signing_key = SigningKey::::new_with_prefix(private_key); /// # signing_key /// # } /// /// let serial_number = SerialNumber::from(42u32); /// let validity = Validity::from_now(Duration::new(5, 0)).unwrap(); /// let profile = Profile::Root; /// let subject = Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap(); /// /// let pub_key = SubjectPublicKeyInfoOwned::try_from(RSA_2048_DER).expect("get rsa pub key"); /// /// let mut signer = rsa_signer(); /// let mut builder = CertificateBuilder::new( /// profile, /// serial_number, /// validity, /// subject, /// pub_key, /// &signer, /// ) /// .expect("Create certificate"); /// ``` pub struct CertificateBuilder<'s, S> { tbs: TbsCertificate, extensions: Extensions, cert_signer: &'s S, } impl<'s, S> CertificateBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, S::VerifyingKey: EncodePublicKey, { /// Creates a new certificate builder pub fn new( profile: Profile, serial_number: SerialNumber, mut validity: Validity, subject: Name, subject_public_key_info: SubjectPublicKeyInfoOwned, cert_signer: &'s S, ) -> Result { let verifying_key = cert_signer.verifying_key(); let signer_pub = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; let signature_alg = cert_signer.signature_algorithm_identifier()?; let issuer = profile.get_issuer(&subject); validity.not_before.rfc5280_adjust_utc_time()?; validity.not_after.rfc5280_adjust_utc_time()?; let tbs = TbsCertificate { version: Version::V3, serial_number, signature: signature_alg, issuer, validity, subject, subject_public_key_info, extensions: None, // We will not generate unique identifier because as per RFC5280 Section 4.1.2.8: // CAs conforming to this profile MUST NOT generate // certificates with unique identifiers. // // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.8 issuer_unique_id: None, subject_unique_id: None, }; let extensions = profile.build_extensions( tbs.subject_public_key_info.owned_to_ref(), signer_pub.owned_to_ref(), &tbs, )?; Ok(Self { tbs, extensions, cert_signer, }) } /// Add an extension to this certificate pub fn add_extension(&mut self, extension: &E) -> Result<()> { let ext = extension.to_extension(&self.tbs.subject, &self.extensions)?; self.extensions.push(ext); Ok(()) } } /// Builder for X509 Certificate Requests /// /// ``` /// # use p256::{pkcs8::DecodePrivateKey, NistP256, ecdsa::DerSignature}; /// # const PKCS8_PRIVATE_KEY_DER: &[u8] = include_bytes!("../tests/examples/p256-priv.der"); /// # fn ecdsa_signer() -> ecdsa::SigningKey { /// # let secret_key = p256::SecretKey::from_pkcs8_der(PKCS8_PRIVATE_KEY_DER).unwrap(); /// # ecdsa::SigningKey::from(secret_key) /// # } /// use x509_cert::{ /// builder::{Builder, RequestBuilder}, /// ext::pkix::{name::GeneralName, SubjectAltName}, /// name::Name, /// }; /// use std::str::FromStr; /// /// use std::net::{IpAddr, Ipv4Addr}; /// let subject = Name::from_str("CN=service.domination.world").unwrap(); /// /// let signer = ecdsa_signer(); /// let mut builder = RequestBuilder::new(subject, &signer).expect("Create certificate request"); /// builder /// .add_extension(&SubjectAltName(vec![GeneralName::from(IpAddr::V4( /// Ipv4Addr::new(192, 0, 2, 0), /// ))])) /// .unwrap(); /// /// let cert_req = builder.build::().unwrap(); /// ``` pub struct RequestBuilder<'s, S> { info: CertReqInfo, extension_req: ExtensionReq, req_signer: &'s S, } impl<'s, S> RequestBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, S::VerifyingKey: EncodePublicKey, { /// Creates a new certificate request builder pub fn new(subject: Name, req_signer: &'s S) -> Result { let version = Default::default(); let verifying_key = req_signer.verifying_key(); let public_key = SubjectPublicKeyInfoOwned::from_key(verifying_key)?; let attributes = Default::default(); let extension_req = Default::default(); Ok(Self { info: CertReqInfo { version, subject, public_key, attributes, }, extension_req, req_signer, }) } /// Add an extension to this certificate request pub fn add_extension(&mut self, extension: &E) -> Result<()> { let ext = extension.to_extension(&self.info.subject, &self.extension_req.0)?; self.extension_req.0.push(ext); Ok(()) } /// Add an attribute to this certificate request pub fn add_attribute(&mut self, attribute: &A) -> Result<()> { let attr = attribute.to_attribute()?; self.info.attributes.insert(attr)?; Ok(()) } } /// Trait for X509 builders /// /// This trait defines the interface between builder and the signers. pub trait Builder: Sized { /// The builder's object signer type Signer; /// Type built by this builder type Output: Sized; /// Return a reference to the signer. fn signer(&self) -> &Self::Signer; /// Assemble the final object from signature. fn assemble(self, signature: BitString) -> Result; /// Finalize and return a serialization of the object for signature. fn finalize(&mut self) -> der::Result>; /// Run the object through the signer and build it. fn build(mut self) -> Result where Self::Signer: Signer, Signature: SignatureBitStringEncoding, { let blob = self.finalize()?; let signature = self.signer().try_sign(&blob)?.to_bitstring()?; self.assemble(signature) } /// Run the object through the signer and build it. fn build_with_rng(mut self, rng: &mut impl CryptoRngCore) -> Result where Self::Signer: RandomizedSigner, Signature: SignatureBitStringEncoding, { let blob = self.finalize()?; let signature = self .signer() .try_sign_with_rng(rng, &blob)? .to_bitstring()?; self.assemble(signature) } } impl<'s, S> Builder for CertificateBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, S::VerifyingKey: EncodePublicKey, { type Signer = S; type Output = Certificate; fn signer(&self) -> &Self::Signer { self.cert_signer } fn finalize(&mut self) -> der::Result> { if !self.extensions.is_empty() { self.tbs.extensions = Some(self.extensions.clone()); } if self.tbs.extensions.is_none() { if self.tbs.issuer_unique_id.is_some() || self.tbs.subject_unique_id.is_some() { self.tbs.version = Version::V2; } else { self.tbs.version = Version::V1; } } self.tbs.to_der() } fn assemble(self, signature: BitString) -> Result { let signature_algorithm = self.tbs.signature.clone(); Ok(Certificate { tbs_certificate: self.tbs, signature_algorithm, signature, }) } } impl<'s, S> Builder for RequestBuilder<'s, S> where S: Keypair + DynSignatureAlgorithmIdentifier, S::VerifyingKey: EncodePublicKey, { type Signer = S; type Output = CertReq; fn signer(&self) -> &Self::Signer { self.req_signer } fn finalize(&mut self) -> der::Result> { self.info .attributes .insert(self.extension_req.clone().try_into()?)?; self.info.to_der() } fn assemble(self, signature: BitString) -> Result { let algorithm = self.req_signer.signature_algorithm_identifier()?; Ok(CertReq { info: self.info, algorithm, signature, }) } } x509-cert-0.2.5/src/certificate.rs000064400000000000000000000214731046102023000147160ustar 00000000000000//! Certificate types use crate::{name::Name, serial_number::SerialNumber, time::Validity}; use alloc::vec::Vec; use const_oid::AssociatedOid; use core::{cmp::Ordering, fmt::Debug}; use der::asn1::BitString; use der::{Decode, Enumerated, Error, ErrorKind, Sequence, Tag, ValueOrd}; use spki::{AlgorithmIdentifierOwned, SubjectPublicKeyInfoOwned}; #[cfg(feature = "pem")] use der::{ pem::{self, PemLabel}, DecodePem, }; /// [`Profile`] allows the consumer of this crate to customize the behavior when parsing /// certificates. /// By default, parsing will be made in a rfc5280-compliant manner. pub trait Profile: PartialEq + Debug + Eq + Clone { /// Checks to run when parsing serial numbers fn check_serial_number(serial: &SerialNumber) -> der::Result<()> { // See the note in `SerialNumber::new`: we permit lengths of 21 bytes here, // since some X.509 implementations interpret the limit of 20 bytes to refer // to the pre-encoded value. if serial.inner.len() > SerialNumber::::MAX_DECODE_LEN { Err(Tag::Integer.value_error()) } else { Ok(()) } } } #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, PartialEq, Eq, Clone)] /// Parse certificates with rfc5280-compliant checks pub struct Rfc5280; impl Profile for Rfc5280 {} #[cfg(feature = "hazmat")] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Debug, PartialEq, Eq, Clone)] /// Parse raw x509 certificate and disable all the checks pub struct Raw; #[cfg(feature = "hazmat")] impl Profile for Raw { fn check_serial_number(_serial: &SerialNumber) -> der::Result<()> { Ok(()) } } /// 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 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[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 ValueOrd for Version { fn value_cmp(&self, other: &Self) -> der::Result { (*self as u8).value_cmp(&(*other as u8)) } } impl Default for Version { fn default() -> Self { Self::V1 } } /// X.509 `TbsCertificate` as defined in [RFC 5280 Section 4.1] pub type TbsCertificate = TbsCertificateInner; /// 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 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] pub struct TbsCertificateInner { /// 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: SerialNumber

, pub signature: AlgorithmIdentifierOwned, pub issuer: Name, pub validity: Validity, pub subject: Name, pub subject_public_key_info: SubjectPublicKeyInfoOwned, #[asn1(context_specific = "1", tag_mode = "IMPLICIT", optional = "true")] pub issuer_unique_id: Option, #[asn1(context_specific = "2", tag_mode = "IMPLICIT", optional = "true")] pub subject_unique_id: Option, #[asn1(context_specific = "3", tag_mode = "EXPLICIT", optional = "true")] pub extensions: Option, } impl TbsCertificateInner

{ /// 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<'a, T: Decode<'a> + AssociatedOid>(&'a self) -> Result, Error> { let mut iter = self.filter::().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<'a, T: Decode<'a> + AssociatedOid>( &'a self, ) -> impl 'a + Iterator> { 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.as_bytes())?))) } } /// X.509 certificates are defined in [RFC 5280 Section 4.1]. /// /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 pub type Certificate = CertificateInner; /// 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 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] #[allow(missing_docs)] pub struct CertificateInner { pub tbs_certificate: TbsCertificateInner

, pub signature_algorithm: AlgorithmIdentifierOwned, pub signature: BitString, } #[cfg(feature = "pem")] impl PemLabel for CertificateInner

{ const PEM_LABEL: &'static str = "CERTIFICATE"; } /// `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 = Vec; #[cfg(feature = "pem")] impl CertificateInner

{ /// Parse a chain of pem-encoded certificates from a slice. /// /// Returns the list of certificates. pub fn load_pem_chain(mut input: &[u8]) -> Result, Error> { fn find_boundary(haystack: &[T], needle: &[T]) -> Option where for<'a> &'a [T]: PartialEq, { haystack .windows(needle.len()) .position(|window| window == needle) } let mut certs = Vec::new(); let mut position: usize = 0; let end_boundary = &b"-----END CERTIFICATE-----"[..]; // Strip the trailing whitespaces loop { if input.is_empty() { break; } let last_pos = input.len() - 1; match input.get(last_pos) { Some(b'\r') | Some(b'\n') => { input = &input[..last_pos]; } _ => break, } } while position < input.len() - 1 { let rest = &input[position..]; let end_pos = find_boundary(rest, end_boundary) .ok_or(pem::Error::PostEncapsulationBoundary)? + end_boundary.len(); let cert_buf = &rest[..end_pos]; let cert = Self::from_pem(cert_buf)?; certs.push(cert); position += end_pos; } Ok(certs) } } x509-cert-0.2.5/src/crl.rs000064400000000000000000000055031046102023000132100ustar 00000000000000//! Certificate Revocation List types use crate::ext::Extensions; use crate::name::Name; use crate::serial_number::SerialNumber; use crate::time::Time; use crate::Version; use alloc::vec::Vec; use der::asn1::BitString; use der::{Sequence, ValueOrd}; use spki::AlgorithmIdentifierOwned; /// `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, ValueOrd)] #[allow(missing_docs)] pub struct CertificateList { pub tbs_cert_list: TbsCertList, pub signature_algorithm: AlgorithmIdentifierOwned, pub signature: BitString, } /// Implicit intermediate structure from the ASN.1 definition of `TBSCertList`. /// /// 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, ValueOrd)] #[allow(missing_docs)] pub struct RevokedCert { pub serial_number: SerialNumber, pub revocation_date: Time, pub crl_entry_extensions: Option, } /// `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, ValueOrd)] #[allow(missing_docs)] pub struct TbsCertList { pub version: Version, pub signature: AlgorithmIdentifierOwned, pub issuer: Name, pub this_update: Time, pub next_update: Option

, } impl SerialNumber

{ /// Maximum length in bytes for a [`SerialNumber`] pub const MAX_LEN: Length = Length::new(20); /// See notes in `SerialNumber::new` and `SerialNumber::decode_value`. pub(crate) const MAX_DECODE_LEN: Length = Length::new(21); /// Create a new [`SerialNumber`] from a byte slice. /// /// The byte slice **must** represent a positive integer. pub fn new(bytes: &[u8]) -> Result { let inner = asn1::Uint::new(bytes)?; // The user might give us a 20 byte unsigned integer with a high MSB, // which we'd then encode with 21 octets to preserve the sign bit. // RFC 5280 is ambiguous about whether this is valid, so we limit // `SerialNumber` *encodings* to 20 bytes or fewer while permitting // `SerialNumber` *decodings* to have up to 21 bytes below. if inner.value_len()? > Self::MAX_LEN { return Err(ErrorKind::Overlength.into()); } Ok(Self { inner: inner.into(), _profile: PhantomData, }) } /// Borrow the inner byte slice which contains the least significant bytes /// of a big endian integer value with all leading zeros stripped. pub fn as_bytes(&self) -> &[u8] { self.inner.as_bytes() } } impl EncodeValue for SerialNumber

{ fn value_len(&self) -> Result { self.inner.value_len() } fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { self.inner.encode_value(writer) } } impl<'a, P: Profile> DecodeValue<'a> for SerialNumber

{ fn decode_value>(reader: &mut R, header: Header) -> Result { let inner = Int::decode_value(reader, header)?; let serial = Self { inner, _profile: PhantomData, }; P::check_serial_number(&serial)?; Ok(serial) } } impl FixedTag for SerialNumber

{ const TAG: Tag = ::TAG; } impl Display for SerialNumber { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let mut iter = self.as_bytes().iter().peekable(); while let Some(byte) = iter.next() { match iter.peek() { Some(_) => write!(f, "{:02X}:", byte)?, None => write!(f, "{:02X}", byte)?, } } Ok(()) } } macro_rules! impl_from { ($source:ty) => { impl From<$source> for SerialNumber { fn from(inner: $source) -> SerialNumber { let serial_number = &inner.to_be_bytes()[..]; let serial_number = asn1::Uint::new(serial_number).unwrap(); // This could only fail if the big endian representation was to be more than 20 // bytes long. Because it's only implemented for up to u64 / usize (8 bytes). SerialNumber::new(serial_number.as_bytes()).unwrap() } } }; } impl_from!(u8); impl_from!(u16); impl_from!(u32); impl_from!(u64); impl_from!(usize); // Implement by hand because the derive would create invalid values. // Use the constructor to create a valid value. #[cfg(feature = "arbitrary")] impl<'a, P: Profile> arbitrary::Arbitrary<'a> for SerialNumber

{ fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?; Self::new(u.bytes(len as usize)?).map_err(|_| arbitrary::Error::IncorrectFormat) } fn size_hint(depth: usize) -> (usize, Option) { arbitrary::size_hint::and(u32::size_hint(depth), (0, None)) } } #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use alloc::string::ToString; use super::*; #[test] fn serial_number_invariants() { // Creating a new serial with an oversized encoding (due to high MSB) fails. { let too_big = [0x80; 20]; assert!(SerialNumber::::new(&too_big).is_err()); } // Creating a new serial with the maximum encoding succeeds. { let just_enough = [ 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, ]; assert!(SerialNumber::::new(&just_enough).is_ok()); } } #[test] fn serial_number_display() { { let sn = SerialNumber::new(&[0x11, 0x22, 0x33]).unwrap(); assert_eq!(sn.to_string(), "11:22:33") } { let sn = SerialNumber::new(&[0xAA, 0xBB, 0xCC, 0x01, 0x10, 0x00, 0x11]).unwrap(); // We force the user's serial to be positive if they give us a negative one. assert_eq!(sn.to_string(), "00:AA:BB:CC:01:10:00:11") } { let sn = SerialNumber::new(&[0x00, 0x00, 0x01]).unwrap(); // Leading zeroes are ignored, due to canonicalization. assert_eq!(sn.to_string(), "01") } } } x509-cert-0.2.5/src/time.rs000064400000000000000000000103631046102023000133660ustar 00000000000000//! 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, Sequence, ValueOrd}; #[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 #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derive(Choice, Copy, Clone, Debug, Eq, PartialEq, ValueOrd)] pub enum Time { /// Legacy UTC time (has 2-digit year, valid from 1970 to 2049). /// /// Note: RFC 5280 specifies 1950-2049, however due to common operations working on /// `UNIX_EPOCH` this implementation's lower bound is 1970. #[asn1(type = "UTCTime")] UtcTime(UtcTime), /// Modern [`GeneralizedTime`] encoding with 4-digit year. #[asn1(type = "GeneralizedTime")] GeneralTime(GeneralizedTime), } impl Time { /// Time used for Certificate who do not expire. pub const INFINITY: Time = Time::GeneralTime(GeneralizedTime::from_date_time(DateTime::INFINITY)); /// Get duration since `UNIX_EPOCH`. pub fn to_unix_duration(self) -> Duration { match self { 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")] pub fn to_system_time(&self) -> SystemTime { match self { Time::UtcTime(t) => t.to_system_time(), Time::GeneralTime(t) => t.to_system_time(), } } /// Convert time to UTCTime representation /// As per RFC 5280: 4.1.2.5, date through 2049 should be expressed as UTC Time. #[cfg(feature = "builder")] pub(crate) fn rfc5280_adjust_utc_time(&mut self) -> der::Result<()> { if let Time::GeneralTime(t) = self { let date = t.to_date_time(); if date.year() <= UtcTime::MAX_YEAR { *self = Time::UtcTime(UtcTime::from_date_time(date)?); } } Ok(()) } } impl fmt::Display for Time { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.to_date_time()) } } impl From for Time { fn from(time: UtcTime) -> Time { Time::UtcTime(time) } } impl From for Time { fn from(time: GeneralizedTime) -> Time { Time::GeneralTime(time) } } #[cfg(feature = "std")] impl From