diff --git a/.github/workflows/x509-ext.yml b/.github/workflows/x509-ext.yml new file mode 100644 index 000000000..4f78e302d --- /dev/null +++ b/.github/workflows/x509-ext.yml @@ -0,0 +1,58 @@ +name: x509-ext + +on: + pull_request: + paths: + - "const-oid/**" + - "der/**" + - "x509-ext/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: x509-ext + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.57.0 # MSRV + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: ${{ matrix.target }} + override: true + - run: cargo install cargo-hack + - run: cargo hack build --release --target ${{ matrix.target }} --feature-powerset --exclude-features std + + test: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - 1.57.0 # MSRV + - stable + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + - run: cargo install cargo-hack + - run: cargo hack test --release --feature-powerset diff --git a/Cargo.lock b/Cargo.lock index f17d2fec5..c9b10fb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,6 +287,7 @@ version = "0.6.0-pre.1" dependencies = [ "const-oid", "der_derive", + "flagset", "hex-literal", "pem-rfc7468", "proptest", @@ -341,6 +342,12 @@ dependencies = [ "instant", ] +[[package]] +name = "flagset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1207393e01e20804589a3fc9781c9df2a70687cd81362ca58e33b2a726ec83cf" + [[package]] name = "fnv" version = "1.0.7" @@ -547,7 +554,7 @@ dependencies = [ "hex-literal", "spki", "x501", - "x509", + "x509-ext", ] [[package]] @@ -800,6 +807,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rstest" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d912f35156a3f99a66ee3e11ac2e0b3f34ac85a07e05263d05a7e2c8810d616f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1226,6 +1246,19 @@ dependencies = [ "hex-literal", "spki", "x501", + "x509-ext", +] + +[[package]] +name = "x509-ext" +version = "0.1.0" +dependencies = [ + "const-oid", + "der", + "flagset", + "hex-literal", + "rstest", + "x501", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index db7e3434e..0643f6c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,5 +20,6 @@ members = [ "tls_codec", "tls_codec/derive", "x501", - "x509" + "x509", + "x509-ext" ] diff --git a/const-oid/src/lib.rs b/const-oid/src/lib.rs index 36905cb88..3dbddc19a 100644 --- a/const-oid/src/lib.rs +++ b/const-oid/src/lib.rs @@ -241,3 +241,9 @@ impl fmt::Display for ObjectIdentifier { Ok(()) } } + +/// A trait expressing the association of a type with an OID. +pub trait Typed { + /// The OID which identifies an encoded type. + const OID: ObjectIdentifier; +} diff --git a/der/Cargo.toml b/der/Cargo.toml index 71792bead..a9f25c2ec 100644 --- a/der/Cargo.toml +++ b/der/Cargo.toml @@ -16,6 +16,7 @@ edition = "2021" rust-version = "1.57" [dependencies] +flagset = { version = "^0.4.2", optional = true } const-oid = { version = "0.8", optional = true, path = "../const-oid" } der_derive = { version = "=0.6.0-pre.1", optional = true, path = "derive" } pem-rfc7468 = { version = "=0.4.0-pre.0", optional = true, path = "../pem-rfc7468" } diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs index 025585a85..b9ce726a3 100644 --- a/der/src/asn1/bit_string.rs +++ b/der/src/asn1/bit_string.rs @@ -225,6 +225,74 @@ impl<'a> ExactSizeIterator for BitStringIter<'a> { impl<'a> FusedIterator for BitStringIter<'a> {} +#[cfg(feature = "flagset")] +impl FixedTag for flagset::FlagSet { + const TAG: Tag = BitString::TAG; +} + +#[cfg(feature = "flagset")] +impl<'a, T> DecodeValue<'a> for flagset::FlagSet +where + T: flagset::Flags, + T::Type: From, + T::Type: core::ops::Shl, +{ + fn decode_value(decoder: &mut Decoder<'a>, header: Header) -> Result { + let position = decoder.position(); + + let bits = BitString::decode_value(decoder, header)?; + + let mut flags = T::none().bits(); + if bits.bit_len() > core::mem::size_of_val(&flags) * 8 { + return Err(Error::new(ErrorKind::Overlength, position)); + } + + for (i, bit) in bits.bits().enumerate() { + flags |= T::Type::from(bit) << i; + } + + Ok(Self::new_truncated(flags)) + } +} + +#[cfg(feature = "flagset")] +#[inline(always)] +fn encode(set: &flagset::FlagSet) -> (usize, [u8; 16]) +where + T: flagset::Flags, + u128: From, +{ + let bits: u128 = set.bits().into(); + let mut swap = 0u128; + + for i in 0..128 { + let on = bits & (1 << i); + swap |= on >> i << (128 - i - 1); + } + + (bits.leading_zeros() as usize, swap.to_be_bytes()) +} + +#[cfg(feature = "flagset")] +impl EncodeValue for flagset::FlagSet +where + T::Type: From, + T::Type: core::ops::Shl, + u128: From, +{ + fn value_len(&self) -> Result { + let (lead, buff) = encode(self); + let buff = &buff[..buff.len() - lead / 8]; + BitString::new((lead % 8) as u8, buff)?.value_len() + } + + fn encode_value(&self, encoder: &mut Encoder<'_>) -> Result<()> { + let (lead, buff) = encode(self); + let buff = &buff[..buff.len() - lead / 8]; + BitString::new((lead % 8) as u8, buff)?.encode_value(encoder) + } +} + #[cfg(test)] mod tests { use super::{BitString, Result, Tag}; diff --git a/pkcs10/Cargo.toml b/pkcs10/Cargo.toml index a66cadf7d..225a0a852 100644 --- a/pkcs10/Cargo.toml +++ b/pkcs10/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" rust-version = "1.56" [dev-dependencies] -x509 = { version = "0.0.1", path = "../x509" } +x509-ext = { version = "0.1.0", path = "../x509-ext" } hex-literal = "0.3" [dependencies] diff --git a/pkcs10/tests/certreq.rs b/pkcs10/tests/certreq.rs index d3ab85d8e..6d5e3d355 100644 --- a/pkcs10/tests/certreq.rs +++ b/pkcs10/tests/certreq.rs @@ -9,6 +9,7 @@ use der::Document; #[cfg(feature = "pem")] use pkcs10::CertReqDocument; +use x509_ext::Extensions; const RSA_KEY: &[u8] = &hex!("3082010A0282010100BF59F7FE716DDE47C73579CA846EFA8D30AB3612E0D6A524204A72CA8E50C9F459513DF0D73331BED3D7A2DA7A362719E471EE6A9D87827D1024ED44605AB9B48F3B808C5E173B9F3EC4003D57F1718489F5C7A0421C46FBD527A40AB4BA6B9DB16A545D1ECF6E2A5633BD80594EBA4AFEE71F63E1D357C64E9A3FF6B83746A885C373F3527987E4C2B4AF7FE4D4EA16405E5E15285DD938823AA18E2634BAFE847A761CAFABB0401D3FA03A07A9D097CBB0C77156CCFE36131DADF1C109C2823972F0AF21A35F358E788304C0C78B951739D91FABFFD07AA8CD4F69746B3D0EB4587469F9D39F4FBDC761200DFB27DAF69562311D8B191B7EEFAAE2F8D6F8EB0203010001"); const RSA_SIG: &[u8] = &hex!("2B053CFE81C6542176BD70B373A5FC8DC1F1806A5AB10D25E36690EED1DF57AD5F18EC0CCF165F000245B14157141224B431EC6715EFE937F66B892D11EDF8858EDF67ACCAE9701A2244BECA80705D7CC292BAD9B02001E4572EE492B08473D5AF59CC83DDA1DE5C2BF470FD784495070A9C5AF8EA9A4060C1DBC5C4690CC8DF6D528C55D82EC9C0DF3046BBCAE7542025D7EE170788C9C234132703290A31AC2700E55339590226D5E582EC61869862769FD85B45F287FFDD6DB530995D31F94D7D2C26EF3F48A182C3026CC698F382A72F1A11E3C689953055DAC0DFEBE9CDB163CA3AF33FFC4DA0F6B84B9D7CDD4321CCECD4BAC528DEFF9715FFD9D4731E"); @@ -71,7 +72,7 @@ fn decode_rsa_2048_der() { assert_eq!(attribute.values.len(), 1); // Check the extensions. - let extensions: x509::Extensions = attribute.values.get(0).unwrap().decode_into().unwrap(); + let extensions: 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); diff --git a/x509-ext/CHANGELOG.md b/x509-ext/CHANGELOG.md new file mode 100644 index 000000000..d6637e049 --- /dev/null +++ b/x509-ext/CHANGELOG.md @@ -0,0 +1,5 @@ +# 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). diff --git a/x509-ext/Cargo.toml b/x509-ext/Cargo.toml new file mode 100644 index 000000000..ef02e7ab1 --- /dev/null +++ b/x509-ext/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "x509-ext" +version = "0.1.0" # Also update html_root_url in lib.rs when bumping this +edition = "2021" +description = "Pure Rust implementation X.509 Extensions" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/formats/tree/master/x509-ext" +categories = ["cryptography", "data-structures", "encoding", "no-std"] +keywords = ["crypto", "X.509", "extensions"] +readme = "README.md" +rust-version = "1.56" + +[dev-dependencies] +hex-literal = "^0.3.4" +rstest = "^0.12.0" + +[dependencies] +der = { version = "=0.6.0-pre.1", features = ["derive", "alloc", "oid", "flagset"], path = "../der" } +const-oid = { version = "=0.8.0", path = "../const-oid" } +x501 = { version = "=0.1.0-pre.0", path = "../x501" } +flagset = "^0.4.2" + +[features] +std = ["der/std"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/x509-ext/LICENSE-APACHE b/x509-ext/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/x509-ext/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/x509-ext/LICENSE-MIT b/x509-ext/LICENSE-MIT new file mode 100644 index 000000000..b9d3eff60 --- /dev/null +++ b/x509-ext/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2020-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. diff --git a/x509-ext/README.md b/x509-ext/README.md new file mode 100644 index 000000000..cc346a5d7 --- /dev/null +++ b/x509-ext/README.md @@ -0,0 +1,58 @@ +# [RustCrypto]: X.509 + +[![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 +Extensions format as described in [RFC 5280]. + +[Documentation][docs-link] + +## About X.509 Extensions + +X.501 Extensions provide a way to add arbitrary data to PKCS#10 Certification +Requests and X.509 Certificates. The extension format was defined in [RFC 5280]. + +## 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://img.shields.io/crates/v/x509-ext.svg +[crate-link]: https://crates.io/crates/x509-ext +[docs-image]: https://docs.rs/x509-ext/badge.svg +[docs-link]: https://docs.rs/x509-ext/ +[build-image]: https://github.com/RustCrypto/formats/actions/workflows/x509-ext.yml/badge.svg +[build-link]: https://github.com/RustCrypto/formats/actions/workflows/x509-ext.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/x509-ext/src/lib.rs b/x509-ext/src/lib.rs new file mode 100644 index 000000000..3ce094367 --- /dev/null +++ b/x509-ext/src/lib.rs @@ -0,0 +1,56 @@ +#![cfg_attr(not(any(test, feature = "std")), 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", + html_root_url = "https://docs.rs/x509-ext/0.1.0" +)] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms)] + +use der::{asn1::ObjectIdentifier, Sequence}; + +extern crate alloc; + +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)] +pub struct Extension<'a> { + /// extnID OBJECT IDENTIFIER, + pub extn_id: ObjectIdentifier, + + /// critical BOOLEAN DEFAULT FALSE, + #[asn1(default = "Default::default")] + pub critical: bool, + + /// extnValue OCTET STRING + #[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>; diff --git a/x509-ext/src/pkix/authkeyid.rs b/x509-ext/src/pkix/authkeyid.rs new file mode 100644 index 000000000..6a61a7e13 --- /dev/null +++ b/x509-ext/src/pkix/authkeyid.rs @@ -0,0 +1,42 @@ +use super::name::GeneralNames; + +use const_oid::Typed; +use der::asn1::{ObjectIdentifier, UIntBytes}; +use der::Sequence; + +/// Authority key identifier extension 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)] +pub struct AuthorityKeyIdentifier<'a> { + /// keyIdentifier + #[asn1( + context_specific = "0", + optional = "true", + tag_mode = "IMPLICIT", + type = "OCTET STRING" + )] + pub key_identifier: Option<&'a [u8]>, + + /// authorityCertIssuer + #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] + pub authority_cert_issuer: Option>, + + /// authorityCertSerialNumber + #[asn1(context_specific = "2", optional = "true", tag_mode = "IMPLICIT")] + pub authority_cert_serial_number: Option>, +} + +impl Typed for AuthorityKeyIdentifier<'_> { + const OID: ObjectIdentifier = ObjectIdentifier::new("2.5.29.35"); +} diff --git a/x509-ext/src/pkix/keyusage.rs b/x509-ext/src/pkix/keyusage.rs new file mode 100644 index 000000000..9e1fb4cec --- /dev/null +++ b/x509-ext/src/pkix/keyusage.rs @@ -0,0 +1,102 @@ +use core::ops::Deref; + +use const_oid::{ObjectIdentifier, Typed}; +use der::{asn1::BitString, DecodeValue, EncodeValue, FixedTag}; +use flagset::{flags, FlagSet}; + +flags! { + /// The set of valid key usages + pub enum KeyUsages: u16 { + /// DigitalSignature + DigitalSignature, + + /// NonRepudiation + NonRepudiation, + + /// KeyEncipherment + KeyEncipherment, + + /// DataEncipherment + DataEncipherment, + + /// KeyAgreement + KeyAgreement, + + /// KeyCertSign + KeyCertSign, + + /// CRLSign + CRLSign, + + /// EncipherOnly + EncipherOnly, + + /// DecipherOnly + DecipherOnly, + } +} + +/// Key usage extension 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 +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct KeyUsage(FlagSet); + +impl From> for KeyUsage { + fn from(usage: FlagSet) -> Self { + Self(usage) + } +} + +impl From for FlagSet { + fn from(usage: KeyUsage) -> Self { + usage.0 + } +} + +impl Deref for KeyUsage { + type Target = FlagSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Typed for KeyUsage { + const OID: const_oid::ObjectIdentifier = ObjectIdentifier::new("2.5.29.15"); +} + +impl FixedTag for KeyUsage { + const TAG: der::Tag = BitString::TAG; +} + +impl<'a> DecodeValue<'a> for KeyUsage { + fn decode_value(decoder: &mut der::Decoder<'a>, header: der::Header) -> der::Result { + Ok(Self(FlagSet::decode_value(decoder, header)?)) + } +} + +impl<'a> EncodeValue for KeyUsage { + fn value_len(&self) -> der::Result { + self.0.value_len() + } + + fn encode_value(&self, encoder: &mut der::Encoder<'_>) -> der::Result<()> { + self.0.encode_value(encoder) + } +} diff --git a/x509-ext/src/pkix/mod.rs b/x509-ext/src/pkix/mod.rs new file mode 100644 index 000000000..00af99bf7 --- /dev/null +++ b/x509-ext/src/pkix/mod.rs @@ -0,0 +1,15 @@ +//! Public-Key Infrastructure using X.509 (PKIX) Extensions +//! +//! The extensions in this module are defined by [RFC 5280]. +//! +//! [RFC 5280]: https://datatracker.ietf.org/doc/html/rfc5280 + +mod authkeyid; +mod keyusage; +mod subkeyid; + +pub mod name; + +pub use authkeyid::AuthorityKeyIdentifier; +pub use keyusage::{KeyUsage, KeyUsages}; +pub use subkeyid::SubjectKeyIdentifier; diff --git a/x509-ext/src/pkix/name/dirstr.rs b/x509-ext/src/pkix/name/dirstr.rs new file mode 100644 index 000000000..6ac90e7ae --- /dev/null +++ b/x509-ext/src/pkix/name/dirstr.rs @@ -0,0 +1,50 @@ +use der::asn1::{PrintableString, Utf8String}; +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)] +pub enum DirectoryString<'a> { + /// ia5String IA5String (SIZE (1..200)) + #[asn1(type = "PrintableString")] + PrintableString(PrintableString<'a>), + + /// utf8String UTF8String (SIZE (1..200)) + #[asn1(type = "UTF8String")] + Utf8String(Utf8String<'a>), +} diff --git a/x509-ext/src/pkix/name/ediparty.rs b/x509-ext/src/pkix/name/ediparty.rs new file mode 100644 index 000000000..4c90477d8 --- /dev/null +++ b/x509-ext/src/pkix/name/ediparty.rs @@ -0,0 +1,37 @@ +use der::{Decodable, 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)] +pub struct EdiPartyName<'a> { + /// nameAssigner + #[asn1(context_specific = "0", tag_mode = "EXPLICIT", optional = "true")] + pub name_assigner: Option>, + + /// partyName + #[asn1(context_specific = "1", tag_mode = "EXPLICIT")] + pub party_name: DirectoryString<'a>, +} diff --git a/x509-ext/src/pkix/name/general.rs b/x509-ext/src/pkix/name/general.rs new file mode 100644 index 000000000..2ed80b72a --- /dev/null +++ b/x509-ext/src/pkix/name/general.rs @@ -0,0 +1,158 @@ +//! GeneralNames as defined in [RFC 5280 Section 4.2.1.6]. + +use super::{EdiPartyName, OtherName}; + +use der::asn1::{Ia5String, ObjectIdentifier, OctetString}; +use der::Choice; +use x501::name::Name; + +/// 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 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)] +pub enum GeneralName<'a> { + /// otherName [0] OtherName, + #[asn1(context_specific = "0", tag_mode = "IMPLICIT", constructed = "true")] + OtherName(OtherName<'a>), + + /// rfc822Name [1] IA5String, + #[asn1(context_specific = "1", tag_mode = "IMPLICIT")] + Rfc822Name(Ia5String<'a>), + + /// dNSName [2] IA5String, + #[asn1(context_specific = "2", tag_mode = "IMPLICIT")] + DnsName(Ia5String<'a>), + + /// x400Address [3] ORAddress, + // Not supporting x400Address + + /// directoryName [4] Name, + // Note that `Name` is a `CHOICE` and therefore the tag must be explicit. + #[asn1(context_specific = "4", tag_mode = "EXPLICIT", constructed = "true")] + DirectoryName(Name<'a>), + + /// ediPartyName [5] EDIPartyName, + #[asn1(context_specific = "5", tag_mode = "IMPLICIT", constructed = "true")] + EdiPartyName(EdiPartyName<'a>), + + /// uniformResourceIdentifier [6] IA5String, + #[asn1(context_specific = "6", tag_mode = "IMPLICIT")] + UniformResourceIdentifier(Ia5String<'a>), + + /// iPAddress [7] OCTET STRING, + #[asn1(context_specific = "7", tag_mode = "IMPLICIT")] + IpAddress(OctetString<'a>), + + /// registeredID [8] OBJECT IDENTIFIER + #[asn1(context_specific = "8", tag_mode = "IMPLICIT")] + RegisteredId(ObjectIdentifier), +} + +#[cfg(test)] +mod test { + use super::{GeneralName, GeneralNames}; + + use der::{Decodable, Encodable}; + 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/x509-ext/src/pkix/name/mod.rs b/x509-ext/src/pkix/name/mod.rs new file mode 100644 index 000000000..3bcf865d7 --- /dev/null +++ b/x509-ext/src/pkix/name/mod.rs @@ -0,0 +1,13 @@ +//! Name types as defined in [RFC 5280 Section 4.2.1.6]. +//! +//! [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 + +mod dirstr; +mod ediparty; +mod general; +mod other; + +pub use dirstr::DirectoryString; +pub use ediparty::EdiPartyName; +pub use general::{GeneralName, GeneralNames}; +pub use other::OtherName; diff --git a/x509-ext/src/pkix/name/other.rs b/x509-ext/src/pkix/name/other.rs new file mode 100644 index 000000000..c4eadf2b8 --- /dev/null +++ b/x509-ext/src/pkix/name/other.rs @@ -0,0 +1,38 @@ +use der::{asn1::ObjectIdentifier, Any, Decodable, 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)] +pub struct OtherName<'a> { + /// type-id + pub oid: ObjectIdentifier, + + /// value + #[asn1(context_specific = "0", tag_mode = "EXPLICIT")] + pub value: Any<'a>, +} + +#[test] +#[cfg(test)] +fn test() { + use der::{Decodable, Encodable}; + use hex_literal::hex; + + let input = hex!("3021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C"); + let decoded = OtherName::from_der(&input).unwrap(); + eprintln!("{:?}", decoded.value); + + 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/x509-ext/src/pkix/subkeyid.rs b/x509-ext/src/pkix/subkeyid.rs new file mode 100644 index 000000000..66b9ba8c5 --- /dev/null +++ b/x509-ext/src/pkix/subkeyid.rs @@ -0,0 +1,59 @@ +use core::ops::Deref; + +use const_oid::{ObjectIdentifier, Typed}; +use der::{asn1::OctetString, DecodeValue, EncodeValue, FixedTag}; + +/// Subject key identifier extension as defined in [RFC 5280 Section 4.2.1.2]. +/// +/// ```text +/// SubjectKeyIdentifier ::= KeyIdentifier +/// KeyIdentifier ::= OCTET STRING +/// ``` +/// +/// [RFC 5280 Section 4.2.1.2]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SubjectKeyIdentifier<'a>(&'a [u8]); + +impl<'a> From<&'a [u8]> for SubjectKeyIdentifier<'a> { + fn from(bytes: &'a [u8]) -> Self { + Self(bytes) + } +} + +impl AsRef<[u8]> for SubjectKeyIdentifier<'_> { + fn as_ref(&self) -> &[u8] { + self.0 + } +} + +impl Deref for SubjectKeyIdentifier<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl Typed for SubjectKeyIdentifier<'_> { + const OID: const_oid::ObjectIdentifier = ObjectIdentifier::new("2.5.29.14"); +} + +impl<'a> FixedTag for SubjectKeyIdentifier<'a> { + const TAG: der::Tag = OctetString::TAG; +} + +impl<'a> DecodeValue<'a> for SubjectKeyIdentifier<'a> { + fn decode_value(decoder: &mut der::Decoder<'a>, header: der::Header) -> der::Result { + Ok(Self(OctetString::decode_value(decoder, header)?.into())) + } +} + +impl<'a> EncodeValue for SubjectKeyIdentifier<'a> { + fn value_len(&self) -> der::Result { + OctetString::new(self.0)?.value_len() + } + + fn encode_value(&self, encoder: &mut der::Encoder<'_>) -> der::Result<()> { + OctetString::new(self.0)?.encode_value(encoder) + } +} diff --git a/x509/Cargo.toml b/x509/Cargo.toml index f87d03c88..25ba8228b 100644 --- a/x509/Cargo.toml +++ b/x509/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.56" [dependencies] der = { version = "=0.6.0-pre.1", features = ["derive", "alloc"], path = "../der" } +x509-ext = { version = "=0.1.0", path = "../x509-ext" } spki = { version = "=0.6.0-pre.0", path = "../spki" } x501 = { version = "=0.1.0-pre.0", path = "../x501" } diff --git a/x509/src/certificate.rs b/x509/src/certificate.rs index 5fb269557..3aa28f1d5 100644 --- a/x509/src/certificate.rs +++ b/x509/src/certificate.rs @@ -1,21 +1,17 @@ //! Certificate [`Certificate`] and TBSCertificate [`TBSCertificate`] as defined in RFC 5280 -use der::asn1::{BitString, ContextSpecific, ObjectIdentifier, UIntBytes}; +use der::asn1::{BitString, ContextSpecific, UIntBytes}; use der::{Sequence, TagMode, TagNumber}; use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; use x501::name::Name; use x501::time::Validity; +use x509_ext::Extensions; /// returns false in support of integer DEFAULT fields set to 0 pub fn default_zero_u8() -> u8 { 0 } -/// returns false in support of boolean DEFAULT fields -pub fn default_false() -> bool { - false -} - /// returns false in support of integer DEFAULT fields set to 0 pub fn default_zero() -> u32 { 0 @@ -202,43 +198,3 @@ pub struct Certificate<'a> { /// signature BIT STRING pub signature: BitString<'a>, } - -/// 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)] -pub struct Extension<'a> { - /// extnID OBJECT IDENTIFIER, - pub extn_id: ObjectIdentifier, - - /// critical BOOLEAN DEFAULT FALSE, - #[asn1(default = "default_false")] - pub critical: bool, - - /// extnValue OCTET STRING - #[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> = SequenceOf, 10>; -pub type Extensions<'a> = alloc::vec::Vec>; diff --git a/x509/src/extensions_utils.rs b/x509/src/extensions_utils.rs index cf0a68afa..e6b108579 100644 --- a/x509/src/extensions_utils.rs +++ b/x509/src/extensions_utils.rs @@ -1,91 +1,7 @@ //! Utility functions and enums related to X.509 extensions -use alloc::vec::Vec; -use core::fmt; use der::asn1::BitString; -/// Enum representing values from the KeyUsage structure -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum KeyUsageValues { - /// DigitalSignature - DigitalSignature = 0, - /// NonRepudiation - NonRepudiation = 1, - /// KeyEncipherment - KeyEncipherment = 2, - /// DataEncipherment - DataEncipherment = 3, - /// KeyAgreement - KeyAgreement = 4, - /// KeyCertSign - KeyCertSign = 5, - /// CRLSign - CRLSign = 6, - /// EncipherOnly - EncipherOnly = 7, - /// DecipherOnly - DecipherOnly = 8, -} - -impl KeyUsageValues { - fn as_str(self) -> &'static str { - match self { - KeyUsageValues::DigitalSignature => "DigitalSignature", - KeyUsageValues::NonRepudiation => "NonRepudiation", - KeyUsageValues::KeyEncipherment => "KeyEncipherment", - KeyUsageValues::DataEncipherment => "DataEncipherment", - KeyUsageValues::KeyAgreement => "KeyAgreement", - KeyUsageValues::KeyCertSign => "KeyCertSign", - KeyUsageValues::CRLSign => "CRLSign", - KeyUsageValues::EncipherOnly => "EncipherOnly", - KeyUsageValues::DecipherOnly => "DecipherOnly", - } - } -} - -impl fmt::Display for KeyUsageValues { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - f.write_str(self.as_str()) - } -} - -/// Takes a BitString that contains one or two bytes and returns a Vec containing -/// enum values representing the KeyUsage values that were set in the BitString -pub fn get_key_usage_values(ku: &BitString<'_>) -> Vec { - let mut retval: Vec = Vec::new(); - let b = ku.raw_bytes(); - if 0x80 == 0x80 & b[0] { - retval.push(KeyUsageValues::DigitalSignature); - } - if 0x40 == 0x40 & b[0] { - retval.push(KeyUsageValues::NonRepudiation); - } - if 0x20 == 0x20 & b[0] { - retval.push(KeyUsageValues::KeyEncipherment); - } - if 0x10 == 0x10 & b[0] { - retval.push(KeyUsageValues::DataEncipherment); - } - if 0x08 == 0x08 & b[0] { - retval.push(KeyUsageValues::KeyAgreement); - } - if 0x04 == 0x04 & b[0] { - retval.push(KeyUsageValues::KeyCertSign); - } - if 0x02 == 0x02 & b[0] { - retval.push(KeyUsageValues::CRLSign); - } - if 0x01 == 0x01 & b[0] { - retval.push(KeyUsageValues::EncipherOnly); - } - - if 2 == b.len() && 0x80 == 0x80 & b[1] { - retval.push(KeyUsageValues::DecipherOnly); - } - - retval -} - /// ReasonFlags ::= BIT STRING { /// unused (0), /// keyCompromise (1), diff --git a/x509/src/general_name.rs b/x509/src/general_name.rs deleted file mode 100644 index 002132e01..000000000 --- a/x509/src/general_name.rs +++ /dev/null @@ -1,419 +0,0 @@ -//! GeneralNames as defined in [RFC 5280 Section 4.2.1.6]. - -use alloc::string::ToString; -use alloc::vec::Vec; -use der::asn1::{Any, ContextSpecific, Ia5String, ObjectIdentifier, OctetString}; -use der::{Decodable, DecodeValue, Decoder, ErrorKind, Header, Sequence, TagMode, TagNumber}; -use x501::name::Name; - -/// OtherName as defined in [RFC 5280 Section 4.2.1.6] in support of the Subject Alternative Name extension. -/// -/// ```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)] -pub struct OtherName<'a> { - /// type-id OBJECT IDENTIFIER, - pub type_id: ObjectIdentifier, - - /// value [0] EXPLICIT ANY DEFINED BY type-id } - pub value: Any<'a>, -} - -impl<'a> DecodeValue<'a> for OtherName<'a> { - fn decode_value(decoder: &mut Decoder<'a>, _header: Header) -> der::Result { - let type_id = decoder.decode()?; - let value = decoder.decode()?; - Ok(Self { type_id, value }) - } -} - -impl<'a> Sequence<'a> for OtherName<'a> { - fn fields(&self, f: F) -> ::der::Result - where - F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, - { - f(&[&self.type_id, &self.value]) - } -} - -/// GeneralNames as defined in [RFC 5280 Section 4.2.1.6] in support of the Subject Alternative Name extension. -/// -/// ```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> = Vec>; - -/// GeneralName as defined in [RFC 5280 Section 4.2.1.6] in support of the Subject Alternative Name extension. -/// -/// ```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 } -/// ``` -/// -/// [RFC 5280 Section 4.2.1.6]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum GeneralName<'a> { - /// otherName [0] OtherName, - OtherName(OtherName<'a>), - - /// rfc822Name [1] IA5String, - Rfc822Name(Ia5String<'a>), - - /// dNSName [2] IA5String, - DnsName(Ia5String<'a>), - - // x400Address [3] ORAddress, - // Not supporting x400Address - /// directoryName [4] Name, - DirectoryName(Name<'a>), - - // ediPartyName [5] EDIPartyName, - // Not supporting ediPartyName - /// uniformResourceIdentifier [6] IA5String, - UniformResourceIdentifier(Ia5String<'a>), - - /// iPAddress [7] OCTET STRING, - IpAddress(OctetString<'a>), - - /// registeredID [8] OBJECT IDENTIFIER - RegisteredId(ObjectIdentifier), -} - -const OTHER_NAME_TAG: TagNumber = TagNumber::new(0); -const RFC822_NAME_TAG: TagNumber = TagNumber::new(1); -const DNS_NAME_TAG: TagNumber = TagNumber::new(2); -//const X400_ADDRESS_TAG: TagNumber = TagNumber::new(3); -const DIRECTORY_NAME_TAG: TagNumber = TagNumber::new(4); -//const EDIPI_PARTY_NAME_TAG: TagNumber = TagNumber::new(5); -const URI_TAG: TagNumber = TagNumber::new(6); -const IP_ADDRESS_TAG: TagNumber = TagNumber::new(7); -const REGISTERED_ID_TAG: TagNumber = TagNumber::new(8); - -// Custom Decodable to handle implicit context specific fields (this may move to derived later). -impl<'a> Decodable<'a> for GeneralName<'a> { - fn decode(decoder: &mut Decoder<'a>) -> der::Result { - let t = decoder.peek_tag()?; - match t.octet() { - 0xA0 => { - let on = - decoder.context_specific::>(OTHER_NAME_TAG, TagMode::Implicit)?; - Ok(GeneralName::OtherName(on.unwrap())) - } - 0x81 => { - let ia5 = decoder - .context_specific::>(RFC822_NAME_TAG, TagMode::Implicit)?; - Ok(GeneralName::Rfc822Name(ia5.unwrap())) - } - 0x82 => { - let ia5 = - decoder.context_specific::>(DNS_NAME_TAG, TagMode::Implicit)?; - Ok(GeneralName::DnsName(ia5.unwrap())) - } - //0xA3 => Not supporting x400Address, - 0xA4 => { - // Explicit is used because Name is also a CHOICE. Nested CHOICEs are essentially - // EXPLICIT tags. See section 31.2.7 in X.680. - let ia5 = - decoder.context_specific::>(DIRECTORY_NAME_TAG, TagMode::Explicit)?; - Ok(GeneralName::DirectoryName(ia5.unwrap())) - } - //0xA5 => Not supporting ediPartyName, - 0x86 => { - let ia5 = decoder.context_specific::>(URI_TAG, TagMode::Implicit)?; - Ok(GeneralName::UniformResourceIdentifier(ia5.unwrap())) - } - 0x87 => { - let os = decoder - .context_specific::>(IP_ADDRESS_TAG, TagMode::Implicit)?; - Ok(GeneralName::IpAddress(os.unwrap())) - } - 0x88 => { - let os = decoder - .context_specific::(REGISTERED_ID_TAG, TagMode::Implicit)?; - Ok(GeneralName::RegisteredId(os.unwrap())) - } - _ => Err(ErrorKind::TagUnknown { byte: t.octet() }.into()), - } - } -} - -impl<'a> ::der::Encodable for GeneralName<'a> { - fn encode(&self, encoder: &mut ::der::Encoder<'_>) -> ::der::Result<()> { - match self { - Self::OtherName(variant) => ContextSpecific { - tag_number: OTHER_NAME_TAG, - tag_mode: TagMode::Implicit, - value: variant.clone(), - } - .encode(encoder), - Self::Rfc822Name(variant) => ContextSpecific { - tag_number: RFC822_NAME_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encode(encoder), - Self::DnsName(variant) => ContextSpecific { - tag_number: DNS_NAME_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encode(encoder), - // Explicit is used because Name is also a CHOICE. Nested CHOICEs are essentially - // EXPLICIT tags. See section 31.2.7 in X.680. - Self::DirectoryName(variant) => ContextSpecific { - tag_number: DIRECTORY_NAME_TAG, - tag_mode: TagMode::Explicit, - value: variant.clone(), - } - .encode(encoder), - Self::UniformResourceIdentifier(variant) => ContextSpecific { - tag_number: URI_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encode(encoder), - Self::IpAddress(variant) => ContextSpecific { - tag_number: IP_ADDRESS_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::OctetString::new(variant.as_bytes())?, - } - .encode(encoder), - Self::RegisteredId(variant) => ContextSpecific { - tag_number: REGISTERED_ID_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::ObjectIdentifier::new(variant.to_string().as_str()), - } - .encode(encoder), - } - } - fn encoded_len(&self) -> ::der::Result<::der::Length> { - // Could just do variant.encode for the implicitly tagged fields - match self { - Self::OtherName(variant) => ContextSpecific { - tag_number: OTHER_NAME_TAG, - tag_mode: TagMode::Implicit, - value: variant.clone(), - } - .encoded_len(), - Self::Rfc822Name(variant) => ContextSpecific { - tag_number: RFC822_NAME_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encoded_len(), - Self::DnsName(variant) => ContextSpecific { - tag_number: DNS_NAME_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encoded_len(), - // Explicit is used because Name is also a CHOICE. Nested CHOICEs are essentially - // EXPLICIT tags. See section 31.2.7 in X.680. - Self::DirectoryName(variant) => ContextSpecific { - tag_number: DIRECTORY_NAME_TAG, - tag_mode: TagMode::Explicit, - value: variant.clone(), - } - .encoded_len(), - Self::UniformResourceIdentifier(variant) => ContextSpecific { - tag_number: URI_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::Ia5String::new(variant)?, - } - .encoded_len(), - Self::IpAddress(variant) => ContextSpecific { - tag_number: IP_ADDRESS_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::OctetString::new(variant.as_bytes())?, - } - .encoded_len(), - Self::RegisteredId(variant) => ContextSpecific { - tag_number: REGISTERED_ID_TAG, - tag_mode: TagMode::Implicit, - value: ::der::asn1::ObjectIdentifier::new(variant.to_string().as_str()), - } - .encoded_len(), - } - } -} - -#[test] -fn reencode_gn() { - use der::Encodable; - use hex_literal::hex; - - // OtherName - // 0 27: [0] { - // 2 5: OBJECT IDENTIFIER '2 16 862 2 2' - // 9 18: [0] { - // 11 16: UTF8String 'RIF-G-20004036-0' - // : } - // : } - let decoded_other_name = GeneralName::from_der(&hex!( - "A01B060560865E0202A0120C105249462D472D32303030343033362D30" - )) - .unwrap(); - let reencoded_other_name = decoded_other_name.to_vec().unwrap(); - assert_eq!( - &hex!("A01B060560865E0202A0120C105249462D472D32303030343033362D30"), - reencoded_other_name.as_slice() - ); - - let gns_other_name = GeneralNames::from_der(&hex!( - "30198117456D61696C5F353238343037373733406468732E676F76" - )) - .unwrap(); - let reencoded_gns_other_name = gns_other_name.to_vec().unwrap(); - assert_eq!( - &hex!("30198117456D61696C5F353238343037373733406468732E676F76"), - reencoded_gns_other_name.as_slice() - ); - - // Rfc822Name - // 0 23: [1] 'Email_528407773@dhs.gov' - let decoded_rfc822_name = - GeneralName::from_der(&hex!("8117456D61696C5F353238343037373733406468732E676F76")).unwrap(); - let reencoded_rfc822_name = decoded_rfc822_name.to_vec().unwrap(); - assert_eq!( - &hex!("8117456D61696C5F353238343037373733406468732E676F76"), - reencoded_rfc822_name.as_slice() - ); - - let gns_rfc822_name = GeneralNames::from_der(&hex!( - "30198117456D61696C5F353238343037373733406468732E676F76" - )) - .unwrap(); - let reencoded_gns_rfc822_name = gns_rfc822_name.to_vec().unwrap(); - assert_eq!( - &hex!("30198117456D61696C5F353238343037373733406468732E676F76"), - reencoded_gns_rfc822_name.as_slice() - ); - - // DnsName - // 0 34: [2] 'unternehmensnachfolge-in-bayern.de' - let decoded_dns_name = GeneralName::from_der(&hex!( - "8222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465" - )) - .unwrap(); - let reencoded_dns_name = decoded_dns_name.to_vec().unwrap(); - assert_eq!( - &hex!("8222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465"), - reencoded_dns_name.as_slice() - ); - - let gns_dns_name = GeneralNames::from_der(&hex!( - "30248222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465" - )) - .unwrap(); - let reencoded_gns_dns_name = gns_dns_name.to_vec().unwrap(); - assert_eq!( - &hex!("30248222756E7465726E65686D656E736E616368666F6C67652D696E2D62617965726E2E6465"), - reencoded_gns_dns_name.as_slice() - ); - - // DirectoryName - // 0 59: [4] { - // 2 57: SEQUENCE { - // 4 11: SET { - // 6 9: SEQUENCE { - // 8 3: OBJECT IDENTIFIER countryName (2 5 4 6) - // 13 2: PrintableString 'DE' - // : } - // : } - // 17 15: SET { - // 19 13: SEQUENCE { - // 21 3: OBJECT IDENTIFIER stateOrProvinceName (2 5 4 8) - // 26 6: UTF8String 'Bayern' - // : } - // : } - // 34 25: SET { - // 36 23: SEQUENCE { - // 38 3: OBJECT IDENTIFIER organizationName (2 5 4 10) - // 43 16: UTF8String 'Freistaat Bayern' - // : } - // : } - // : } - // : } - let decoded_dn = - GeneralName::from_der(&hex!("A43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E")).unwrap(); - let reencoded_dn = decoded_dn.to_vec().unwrap(); - assert_eq!( - &hex!("A43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E"), - reencoded_dn.as_slice() - ); - - let gns_dn = GeneralNames::from_der(&hex!( - "303DA43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E" - )) - .unwrap(); - let reencoded_gns_dn = gns_dn.to_vec().unwrap(); - assert_eq!( - &hex!("303DA43B3039310B3009060355040613024445310F300D06035504080C0642617965726E31193017060355040A0C104672656973746161742042617965726E"), - reencoded_gns_dn.as_slice() - ); - - // UniformResourceIdentifier - // 0 42: [6] - // : 'http://crl.quovadisglobal.com/qvrca2g3.crl' - let decoded_uri = GeneralName::from_der(&hex!( - "862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C" - )) - .unwrap(); - let reencoded_uri = decoded_uri.to_vec().unwrap(); - assert_eq!( - &hex!("862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C"), - reencoded_uri.as_slice() - ); - - let gns_uri = GeneralNames::from_der(&hex!( - "302C862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C" - )) - .unwrap(); - let reencoded_gns_uri = gns_uri.to_vec().unwrap(); - assert_eq!( - &hex!("302C862A687474703A2F2F63726C2E71756F7661646973676C6F62616C2E636F6D2F71767263613267332E63726C"), - reencoded_gns_uri.as_slice() - ); - - // IP Address - // 0 32: [7] - // : 2A 02 10 2C 00 00 00 00 00 00 00 00 00 00 00 00 - // : FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 - let decoded_ip = GeneralName::from_der(&hex!( - "87202A02102C000000000000000000000000FFFFFFFF000000000000000000000000" - )) - .unwrap(); - let reencoded_ip = decoded_ip.to_vec().unwrap(); - assert_eq!( - &hex!("87202A02102C000000000000000000000000FFFFFFFF000000000000000000000000"), - reencoded_ip.as_slice() - ); - - let gns_ip = GeneralNames::from_der(&hex!( - "302287202A02102C000000000000000000000000FFFFFFFF000000000000000000000000" - )) - .unwrap(); - let reencoded_gns_ip = gns_ip.to_vec().unwrap(); - assert_eq!( - &hex!("302287202A02102C000000000000000000000000FFFFFFFF000000000000000000000000"), - reencoded_gns_ip.as_slice() - ); - - // RegisteredId - // TODO -} diff --git a/x509/src/lib.rs b/x509/src/lib.rs index aadf4848b..3d5f21265 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -16,13 +16,10 @@ extern crate std; mod certificate; pub mod extensions_utils; -mod general_name; pub mod pkix_extensions; pub mod pkix_oids; pub mod trust_anchor_format; -pub use crate::{ - certificate::*, extensions_utils::*, general_name::*, pkix_extensions::*, pkix_oids::*, -}; +pub use crate::{certificate::*, extensions_utils::*, pkix_extensions::*, pkix_oids::*}; pub use der::{self, asn1::ObjectIdentifier}; pub use spki::{self, AlgorithmIdentifier, SubjectPublicKeyInfo}; diff --git a/x509/src/pkix_extensions.rs b/x509/src/pkix_extensions.rs index 69db1048f..5fa836eed 100644 --- a/x509/src/pkix_extensions.rs +++ b/x509/src/pkix_extensions.rs @@ -1,13 +1,11 @@ //! Extensions [`Extensions`] as defined in RFC 5280 use crate::default_zero; -use crate::general_name::GeneralName; -use crate::general_name::GeneralNames; use alloc::vec::Vec; use der::asn1::{ - Any, BitString, ContextSpecific, GeneralizedTime, Ia5String, Null, ObjectIdentifier, - OctetString, UIntBytes, Utf8String, + Any, BitString, ContextSpecific, GeneralizedTime, Ia5String, Null, ObjectIdentifier, UIntBytes, + Utf8String, }; use der::Header; use der::{ @@ -16,6 +14,7 @@ use der::{ }; use x501::attr::AttributeTypeAndValue; use x501::name::RelativeDistinguishedName; +use x509_ext::pkix::name::{GeneralName, GeneralNames}; /// DisplayText as defined in [RFC 5280 Section 4.2.1.4] in support of the Certificate Policies extension. /// @@ -119,34 +118,6 @@ pub struct BasicConstraints { pub path_len_constraint: Option, } -/// Subject key identifier extension as defined in [RFC 5280 Section 4.2.1.2] and as identified by the [`PKIX_CE_SUBJECT_KEY_IDENTIFIER`](constant.PKIX_CE_SUBJECT_KEY_IDENTIFIER.html) OID. -/// -/// ```text -/// SubjectKeyIdentifier ::= KeyIdentifier -/// ``` -/// -/// [RFC 5280 Section 4.2.1.2]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.2 -pub type SubjectKeyIdentifier<'a> = OctetString<'a>; - -/// Key usage extension as defined in [RFC 5280 Section 4.2.1.3] and as identified by the [`PKIX_CE_KEY_USAGE`](constant.PKIX_CE_KEY_USAGE.html) OID. -/// -/// ```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 -pub type KeyUsage<'a> = BitString<'a>; - /// Certificate policies extension as defined in [RFC 5280 Section 4.2.1.4] and as identified by the [`PKIX_CE_CERTIFICATE_POLICIES`](constant.PKIX_CE_CERTIFICATE_POLICIES.html) OID. /// /// ```text @@ -614,89 +585,6 @@ pub enum CRLReason { AaCompromise = 10, } -/// Authority key identifier extension as defined in [RFC 5280 Section 4.2.1.1] and as identified by the [`PKIX_CE_AUTHORITY_KEY_IDENTIFIER`](constant.PKIX_CE_AUTHORITY_KEY_IDENTIFIER.html) OID. -/// -/// ```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)] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AuthorityKeyIdentifier<'a> { - /// keyIdentifier [0] KeyIdentifier OPTIONAL, - //#[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] - pub key_identifier: Option>, - - /// authorityCertIssuer [1] GeneralNames OPTIONAL, - //#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] - pub authority_cert_issuer: Option>, - - /// authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL } - //#[asn1(context_specific = "2", optional = "true", tag_mode = "IMPLICIT")] - pub authority_cert_serial_number: Option>, -} -impl<'a> ::der::Decodable<'a> for AuthorityKeyIdentifier<'a> { - fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { - decoder.sequence(|decoder| { - let key_identifier = - ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N0)? - .map(|cs| cs.value); - let authority_cert_issuer = - ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? - .map(|cs| cs.value); - let authority_cert_serial_number = - ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N2)? - .map(|cs| cs.value); - Ok(Self { - key_identifier, - authority_cert_issuer, - authority_cert_serial_number, - }) - }) - } -} - -const KEY_IDENTIFIER_TAG: TagNumber = TagNumber::new(0); -const AUTHORITY_CERT_ISSUER_TAG: TagNumber = TagNumber::new(1); -const AUTHORITY_CERT_SERIAL_NUMBER: TagNumber = TagNumber::new(2); - -impl<'a> ::der::Sequence<'a> for AuthorityKeyIdentifier<'a> { - fn fields(&self, f: F) -> ::der::Result - where - F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, - { - f(&[ - &self.key_identifier.as_ref().map(|elem| ContextSpecific { - tag_number: KEY_IDENTIFIER_TAG, - tag_mode: TagMode::Implicit, - value: *elem, - }), - &self - .authority_cert_issuer - .as_ref() - .map(|elem| ContextSpecific { - tag_number: AUTHORITY_CERT_ISSUER_TAG, - tag_mode: TagMode::Implicit, - value: elem.clone(), - }), - &self - .authority_cert_serial_number - .as_ref() - .map(|elem| ContextSpecific { - tag_number: AUTHORITY_CERT_SERIAL_NUMBER, - tag_mode: TagMode::Implicit, - value: *elem, - }), - ]) - } -} - /// ReasonFlags as defined in [RFC 5280 Section 4.2.1.13] in support of the CRL distribution points extension. /// /// ```text diff --git a/x509/src/trust_anchor_format.rs b/x509/src/trust_anchor_format.rs index 16e2c7d03..7f0ac42b4 100644 --- a/x509/src/trust_anchor_format.rs +++ b/x509/src/trust_anchor_format.rs @@ -1,6 +1,6 @@ //! Trust anchor-related structures as defined in RFC 5914 -use crate::{Certificate, CertificatePolicies, Extensions, NameConstraints}; +use crate::{Certificate, CertificatePolicies, NameConstraints}; use der::asn1::{BitString, ContextSpecific, OctetString, Utf8String}; use der::{ DecodeValue, Decoder, Encodable, EncodeValue, ErrorKind, FixedTag, Header, Sequence, Tag, @@ -8,6 +8,7 @@ use der::{ }; use spki::SubjectPublicKeyInfo; use x501::name::Name; +use x509_ext::Extensions; /// TrustAnchorInfo ::= SEQUENCE { /// version TrustAnchorInfoVersion DEFAULT v1, diff --git a/x509/tests/pkix_extensions.rs b/x509/tests/pkix_extensions.rs index 262147468..f9dc05584 100644 --- a/x509/tests/pkix_extensions.rs +++ b/x509/tests/pkix_extensions.rs @@ -1,14 +1,12 @@ //! Certificate tests -use der::asn1::{BitString, UIntBytes, Utf8String}; -use der::{Decodable, Encodable, ErrorKind, Length, Tag, Tagged}; +use der::asn1::{BitString, UIntBytes}; +use der::{Decodable, Encodable, ErrorKind, Tag, Tagged}; use hex_literal::hex; use x501::name::Name; -use x509::KeyUsage; use x509::*; -use x509::{ - BasicConstraints, Certificate, CertificatePolicies, GeneralName, OtherName, - SubjectKeyIdentifier, -}; +use x509_ext::pkix::name::{GeneralName, GeneralNames}; +use x509_ext::pkix::*; +use x509_ext::Extensions; fn spin_over_exts<'a>(exts: Extensions<'a>) { let i = exts.iter(); @@ -240,21 +238,10 @@ fn decode_general_name() { } // OtherName - let on = OtherName::from_der( - &hex!("3021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C")[..], - ) - .unwrap(); - - let onval = Utf8String::from_der(on.value.value()).unwrap(); - assert_eq!(onval.to_string(), "Upn_214950130@mil"); - - let other_name = GeneralName::from_der( - &hex!("A021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C")[..], - ) - .unwrap(); - match other_name { + let bytes = hex!("A021060A2B060104018237140203A0130C1155706E5F323134393530313330406D696C"); + match GeneralName::from_der(&bytes).unwrap() { GeneralName::OtherName(other_name) => { - let onval = Utf8String::from_der(other_name.value.value()).unwrap(); + let onval = other_name.value.utf8_string().unwrap(); assert_eq!(onval.to_string(), "Upn_214950130@mil"); } _ => panic!("Failed to parse OtherName from GeneralName"), @@ -278,20 +265,8 @@ fn decode_cert() { assert_eq!(ext.extn_id.to_string(), PKIX_CE_KEY_USAGE.to_string()); assert_eq!(ext.critical, true); - use x509::extensions_utils::KeyUsageValues; let ku = KeyUsage::from_der(ext.extn_value).unwrap(); - let kuv = x509::extensions_utils::get_key_usage_values(&ku); - let mut count = 0; - for v in kuv { - if 0 == count { - assert_eq!(v, KeyUsageValues::KeyCertSign); - } else if 1 == count { - assert_eq!(v, KeyUsageValues::CRLSign); - } else { - panic!("Should not occur"); - } - count += 1; - } + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, *ku); let reencoded = ku.to_vec().unwrap(); assert_eq!(ext.extn_value, reencoded); @@ -440,11 +415,8 @@ fn decode_cert() { ); assert_eq!(ext.critical, false); let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap(); - assert_eq!(Length::new(21), skid.len()); - assert_eq!( - &hex!("DBD3DEBF0D7B615B32803BC0206CD7AADD39B8ACFF"), - skid.as_bytes() - ); + assert_eq!(21, skid.len()); + assert_eq!(&hex!("DBD3DEBF0D7B615B32803BC0206CD7AADD39B8ACFF"), &*skid); let reencoded = skid.to_vec().unwrap(); assert_eq!(ext.extn_value, reencoded); @@ -599,7 +571,7 @@ fn decode_cert() { let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap(); assert_eq!( &hex!("7C4C863AB80BD589870BEDB7E11BBD2A08BB3D23FF"), - akid.key_identifier.unwrap().as_bytes() + akid.key_identifier.unwrap() ); let reencoded = akid.to_vec().unwrap(); @@ -740,7 +712,7 @@ fn decode_cert() { assert_eq!(ext.critical, false); let akid = AuthorityKeyIdentifier::from_der(ext.extn_value).unwrap(); assert_eq!( - akid.key_identifier.unwrap().as_bytes(), + akid.key_identifier.unwrap(), &hex!("E47D5FD15C9586082C05AEBE75B665A7D95DA866")[..] ); } else if 1 == counter { @@ -751,26 +723,14 @@ fn decode_cert() { assert_eq!(ext.critical, false); let skid = SubjectKeyIdentifier::from_der(ext.extn_value).unwrap(); assert_eq!( - skid.as_bytes(), + &*skid, &hex!("580184241BBC2B52944A3DA510721451F5AF3AC9")[..] ); } else if 2 == counter { assert_eq!(ext.extn_id.to_string(), PKIX_CE_KEY_USAGE.to_string()); assert_eq!(ext.critical, true); - use x509::extensions_utils::KeyUsageValues; let ku = KeyUsage::from_der(ext.extn_value).unwrap(); - let kuv = x509::extensions_utils::get_key_usage_values(&ku); - let mut count = 0; - for v in kuv { - if 0 == count { - assert_eq!(v, KeyUsageValues::KeyCertSign); - } else if 1 == count { - assert_eq!(v, KeyUsageValues::CRLSign); - } else { - panic!("Should not occur"); - } - count += 1; - } + assert_eq!(KeyUsages::KeyCertSign | KeyUsages::CRLSign, *ku); } else if 3 == counter { assert_eq!( ext.extn_id.to_string(), diff --git a/x509/tests/trust_anchor_format.rs b/x509/tests/trust_anchor_format.rs index 35e9179a0..79ebad864 100644 --- a/x509/tests/trust_anchor_format.rs +++ b/x509/tests/trust_anchor_format.rs @@ -3,6 +3,7 @@ use hex_literal::hex; use x509::der::{DecodeValue, Encodable}; use x509::trust_anchor_format::TrustAnchorChoice; use x509::*; +use x509_ext::pkix::name::GeneralName; #[test] fn decode_ta1() {